Skip to main content

cc_data/decode/
screen.rs

1//! Shared caption display model — colour / opacity / edge / font enumerations
2//! and pen attributes, per the CEA-708 conformance model (47 CFR §79.102 (h)–(q),
3//! ANSI/CTA-708-E §8.5, §8.8; `cc-data/docs/decode/cea708-conformance.md`,
4//! `cea708-decode.md`).
5
6/// A CEA-708 colour: 2 bits per RGB component (R, G, B each 0–3), 64 colours
7/// total (§8.8, Tables 30/31).
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize))]
10pub struct Color {
11    /// Red component, 0–3.
12    pub r: u8,
13    /// Green component, 0–3.
14    pub g: u8,
15    /// Blue component, 0–3.
16    pub b: u8,
17}
18
19impl Color {
20    /// Black `(0,0,0)`.
21    pub const BLACK: Color = Color { r: 0, g: 0, b: 0 };
22    /// White `(2,2,2)`.
23    pub const WHITE: Color = Color { r: 2, g: 2, b: 2 };
24
25    /// Construct from three 2-bit components (masked to 0–3).
26    #[must_use]
27    pub const fn new(r: u8, g: u8, b: u8) -> Self {
28        Color {
29            r: r & 0x03,
30            g: g & 0x03,
31            b: b & 0x03,
32        }
33    }
34
35    /// Map this colour onto the minimum 8-colour palette (§8.8, p.92): component
36    /// `1 → 0`, `2 → 2`, `3 → 2` (and `0 → 0`).
37    #[must_use]
38    pub const fn to_8_color(self) -> Color {
39        const fn q(c: u8) -> u8 {
40            match c {
41                0 | 1 => 0,
42                _ => 2,
43            }
44        }
45        Color {
46            r: q(self.r),
47            g: q(self.g),
48            b: q(self.b),
49        }
50    }
51}
52
53/// Opacity of a foreground / background / fill (§8.8; SPC/SWA opacity field,
54/// `cea708-decode.md`). 2-bit field.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize))]
57#[non_exhaustive]
58pub enum Opacity {
59    /// Fully opaque.
60    #[default]
61    Solid,
62    /// Alternates opaque / transparent.
63    Flash,
64    /// Partially transparent (background shows through).
65    Translucent,
66    /// Fully transparent (not rendered).
67    Transparent,
68}
69
70impl Opacity {
71    /// From the 2-bit wire value.
72    #[must_use]
73    pub fn from_bits(v: u8) -> Self {
74        match v & 0x03 {
75            0 => Self::Solid,
76            1 => Self::Flash,
77            2 => Self::Translucent,
78            _ => Self::Transparent,
79        }
80    }
81    /// Label per the project's `name()` convention.
82    #[must_use]
83    pub fn name(&self) -> &'static str {
84        match self {
85            Self::Solid => "solid",
86            Self::Flash => "flash",
87            Self::Translucent => "translucent",
88            Self::Transparent => "transparent",
89        }
90    }
91}
92dvb_common::impl_spec_display!(Opacity);
93
94/// Character edge type (§79.102 (p) / SPA edge-type field; 3-bit). 0–5 defined.
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize))]
97#[non_exhaustive]
98pub enum EdgeType {
99    /// No edge.
100    #[default]
101    None,
102    /// Raised edge.
103    Raised,
104    /// Depressed edge.
105    Depressed,
106    /// Uniform outline.
107    Uniform,
108    /// Left drop shadow.
109    LeftDropShadow,
110    /// Right drop shadow.
111    RightDropShadow,
112}
113
114impl EdgeType {
115    /// From the 3-bit wire value (values 6–7 fold to `None`).
116    #[must_use]
117    pub fn from_bits(v: u8) -> Self {
118        match v & 0x07 {
119            0 => Self::None,
120            1 => Self::Raised,
121            2 => Self::Depressed,
122            3 => Self::Uniform,
123            4 => Self::LeftDropShadow,
124            5 => Self::RightDropShadow,
125            _ => Self::None,
126        }
127    }
128    /// Label per the project's `name()` convention.
129    #[must_use]
130    pub fn name(&self) -> &'static str {
131        match self {
132            Self::None => "none",
133            Self::Raised => "raised",
134            Self::Depressed => "depressed",
135            Self::Uniform => "uniform",
136            Self::LeftDropShadow => "left_drop_shadow",
137            Self::RightDropShadow => "right_drop_shadow",
138        }
139    }
140}
141dvb_common::impl_spec_display!(EdgeType);
142
143/// Pen size (§79.102 (j) / SPA pen-size field; 2-bit). Value 3 is reserved.
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize))]
146#[non_exhaustive]
147pub enum PenSize {
148    /// Small.
149    Small,
150    /// Standard (default).
151    #[default]
152    Standard,
153    /// Large.
154    Large,
155}
156
157impl PenSize {
158    /// From the 2-bit wire value (3 folds to `Standard`).
159    #[must_use]
160    pub fn from_bits(v: u8) -> Self {
161        match v & 0x03 {
162            0 => Self::Small,
163            2 => Self::Large,
164            _ => Self::Standard,
165        }
166    }
167    /// Label per the project's `name()` convention.
168    #[must_use]
169    pub fn name(&self) -> &'static str {
170        match self {
171            Self::Small => "small",
172            Self::Standard => "standard",
173            Self::Large => "large",
174        }
175    }
176}
177dvb_common::impl_spec_display!(PenSize);
178
179/// Pen vertical offset (§79.102 (l) / SPA offset field; 2-bit).
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
181#[cfg_attr(feature = "serde", derive(serde::Serialize))]
182#[non_exhaustive]
183pub enum PenOffset {
184    /// Subscript.
185    Subscript,
186    /// Normal (default).
187    #[default]
188    Normal,
189    /// Superscript.
190    Superscript,
191}
192
193impl PenOffset {
194    /// From the 2-bit wire value (3 folds to `Normal`).
195    #[must_use]
196    pub fn from_bits(v: u8) -> Self {
197        match v & 0x03 {
198            0 => Self::Subscript,
199            2 => Self::Superscript,
200            _ => Self::Normal,
201        }
202    }
203    /// Label per the project's `name()` convention.
204    #[must_use]
205    pub fn name(&self) -> &'static str {
206        match self {
207            Self::Subscript => "subscript",
208            Self::Normal => "normal",
209            Self::Superscript => "superscript",
210        }
211    }
212}
213dvb_common::impl_spec_display!(PenOffset);
214
215/// Font style (§79.102 (k) / SPA font-style field; 3-bit, 0–7).
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
217#[cfg_attr(feature = "serde", derive(serde::Serialize))]
218#[non_exhaustive]
219pub enum FontStyle {
220    /// Default / undefined.
221    #[default]
222    Default,
223    /// Monospaced with serifs.
224    MonospacedSerif,
225    /// Proportionally spaced with serifs.
226    ProportionalSerif,
227    /// Monospaced without serifs.
228    MonospacedSansSerif,
229    /// Proportionally spaced without serifs.
230    ProportionalSansSerif,
231    /// Casual.
232    Casual,
233    /// Cursive.
234    Cursive,
235    /// Small capitals.
236    SmallCapitals,
237}
238
239impl FontStyle {
240    /// From the 3-bit wire value.
241    #[must_use]
242    pub fn from_bits(v: u8) -> Self {
243        match v & 0x07 {
244            0 => Self::Default,
245            1 => Self::MonospacedSerif,
246            2 => Self::ProportionalSerif,
247            3 => Self::MonospacedSansSerif,
248            4 => Self::ProportionalSansSerif,
249            5 => Self::Casual,
250            6 => Self::Cursive,
251            _ => Self::SmallCapitals,
252        }
253    }
254    /// Label per the project's `name()` convention.
255    #[must_use]
256    pub fn name(&self) -> &'static str {
257        match self {
258            Self::Default => "default",
259            Self::MonospacedSerif => "monospaced_serif",
260            Self::ProportionalSerif => "proportional_serif",
261            Self::MonospacedSansSerif => "monospaced_sans_serif",
262            Self::ProportionalSansSerif => "proportional_sans_serif",
263            Self::Casual => "casual",
264            Self::Cursive => "cursive",
265            Self::SmallCapitals => "small_capitals",
266        }
267    }
268}
269dvb_common::impl_spec_display!(FontStyle);
270
271/// Text justification (SWA justify field; 2-bit).
272#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
273#[cfg_attr(feature = "serde", derive(serde::Serialize))]
274#[non_exhaustive]
275pub enum Justify {
276    /// Left-justified (default).
277    #[default]
278    Left,
279    /// Right-justified.
280    Right,
281    /// Centred.
282    Center,
283    /// Fully justified.
284    Full,
285}
286
287impl Justify {
288    /// From the 2-bit wire value.
289    #[must_use]
290    pub fn from_bits(v: u8) -> Self {
291        match v & 0x03 {
292            0 => Self::Left,
293            1 => Self::Right,
294            2 => Self::Center,
295            _ => Self::Full,
296        }
297    }
298    /// Label per the project's `name()` convention.
299    #[must_use]
300    pub fn name(&self) -> &'static str {
301        match self {
302            Self::Left => "left",
303            Self::Right => "right",
304            Self::Center => "center",
305            Self::Full => "full",
306        }
307    }
308}
309dvb_common::impl_spec_display!(Justify);
310
311/// Print direction (SWA print-direction field; 2-bit).
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
313#[cfg_attr(feature = "serde", derive(serde::Serialize))]
314#[non_exhaustive]
315pub enum PrintDirection {
316    /// Left to right (default).
317    #[default]
318    LeftToRight,
319    /// Right to left.
320    RightToLeft,
321    /// Top to bottom.
322    TopToBottom,
323    /// Bottom to top.
324    BottomToTop,
325}
326
327impl PrintDirection {
328    /// From the 2-bit wire value.
329    #[must_use]
330    pub fn from_bits(v: u8) -> Self {
331        match v & 0x03 {
332            0 => Self::LeftToRight,
333            1 => Self::RightToLeft,
334            2 => Self::TopToBottom,
335            _ => Self::BottomToTop,
336        }
337    }
338    /// Label per the project's `name()` convention.
339    #[must_use]
340    pub fn name(&self) -> &'static str {
341        match self {
342            Self::LeftToRight => "left_to_right",
343            Self::RightToLeft => "right_to_left",
344            Self::TopToBottom => "top_to_bottom",
345            Self::BottomToTop => "bottom_to_top",
346        }
347    }
348}
349dvb_common::impl_spec_display!(PrintDirection);
350
351/// Scroll direction (SWA scroll-direction field; 2-bit). Same wire mapping as
352/// [`PrintDirection`].
353#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
354#[cfg_attr(feature = "serde", derive(serde::Serialize))]
355#[non_exhaustive]
356pub enum ScrollDirection {
357    /// Left to right.
358    LeftToRight,
359    /// Right to left.
360    RightToLeft,
361    /// Top to bottom.
362    TopToBottom,
363    /// Bottom to top (default — NTSC roll-up).
364    #[default]
365    BottomToTop,
366}
367
368impl ScrollDirection {
369    /// From the 2-bit wire value.
370    #[must_use]
371    pub fn from_bits(v: u8) -> Self {
372        match v & 0x03 {
373            0 => Self::LeftToRight,
374            1 => Self::RightToLeft,
375            2 => Self::TopToBottom,
376            _ => Self::BottomToTop,
377        }
378    }
379    /// Label per the project's `name()` convention.
380    #[must_use]
381    pub fn name(&self) -> &'static str {
382        match self {
383            Self::LeftToRight => "left_to_right",
384            Self::RightToLeft => "right_to_left",
385            Self::TopToBottom => "top_to_bottom",
386            Self::BottomToTop => "bottom_to_top",
387        }
388    }
389}
390dvb_common::impl_spec_display!(ScrollDirection);
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395    use alloc::string::ToString;
396
397    #[test]
398    fn color_8_color_mapping() {
399        // (1,2,3) → (0,2,2); (3,3,3) → (2,2,2); (1,1,1) → (0,0,0)
400        assert_eq!(Color::new(1, 2, 3).to_8_color(), Color::new(0, 2, 2));
401        assert_eq!(Color::new(3, 3, 3).to_8_color(), Color::WHITE);
402        assert_eq!(Color::new(1, 1, 1).to_8_color(), Color::BLACK);
403    }
404
405    #[test]
406    fn opacity_bits() {
407        assert_eq!(Opacity::from_bits(0), Opacity::Solid);
408        assert_eq!(Opacity::from_bits(3), Opacity::Transparent);
409        assert_eq!(Opacity::Translucent.to_string(), "translucent");
410    }
411
412    #[test]
413    fn edge_bits() {
414        assert_eq!(EdgeType::from_bits(5), EdgeType::RightDropShadow);
415        assert_eq!(EdgeType::from_bits(7), EdgeType::None);
416    }
417
418    #[test]
419    fn font_bits() {
420        assert_eq!(FontStyle::from_bits(7), FontStyle::SmallCapitals);
421        assert_eq!(FontStyle::from_bits(1).to_string(), "monospaced_serif");
422    }
423}