Skip to main content

dvb_subtitle/segments/
display_definition.rs

1//! Display Definition Segment — ETSI EN 300 743 §7.2.1, Table 8 (segment_type 0x14).
2//!
3//! Defines the display resolution and optional display window for a subtitle service.
4
5use crate::error::{Error, Result};
6use broadcast_common::{Parse, Serialize};
7
8/// The display_definition_segment segment_type as listed in Table 7.
9pub const SEGMENT_TYPE: u8 = 0x14;
10/// Segment header is sync_byte(1) + segment_type(1) + page_id(2) + segment_length(2) = 6 bytes.
11pub const HEADER_LEN: usize = 6;
12/// Fixed segment body (after header): dds_version_number(4b)+display_window_flag(1b)+reserved(3b) + display_width(2) + display_height(2) = 5 bytes.
13pub const FIXED_BODY_LEN: usize = 5;
14/// Optional display window fields: 4 × u16 = 8 bytes.
15pub const WINDOW_LEN: usize = 8;
16
17/// Display Definition Segment (DDS).
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20pub struct DisplayDefinitionSegment {
21    /// The page_id (from generic segment header).
22    pub page_id: u16,
23    /// Version of this DDS (modulo 16).
24    pub dds_version_number: u8,
25    /// Whether a window is defined (`true`) or the display set fills the full resolution (`false`).
26    pub display_window_flag: bool,
27    /// Reserved bits in body byte 0 (bits `[2:0]`).
28    pub reserved: u8,
29    /// Maximum horizontal width in pixels minus 1 (0..4095).
30    pub display_width: u16,
31    /// Maximum vertical height in lines minus 1 (0..4095).
32    pub display_height: u16,
33    /// Left-most pixel of the display window (present iff `display_window_flag`).
34    pub display_window_horizontal_position_minimum: Option<u16>,
35    /// Right-most pixel of the display window (present iff `display_window_flag`).
36    pub display_window_horizontal_position_maximum: Option<u16>,
37    /// Upper-most line of the display window (present iff `display_window_flag`).
38    pub display_window_vertical_position_minimum: Option<u16>,
39    /// Bottom line of the display window (present iff `display_window_flag`).
40    pub display_window_vertical_position_maximum: Option<u16>,
41}
42
43impl<'a> Parse<'a> for DisplayDefinitionSegment {
44    type Error = Error;
45
46    fn parse(bytes: &'a [u8]) -> Result<Self> {
47        let min = HEADER_LEN + FIXED_BODY_LEN;
48        if bytes.len() < min {
49            return Err(Error::BufferTooShort {
50                need: min,
51                have: bytes.len(),
52                what: "display_definition_segment",
53            });
54        }
55        if bytes[0] != crate::pes_data_field::SYNC_BYTE {
56            return Err(Error::BadSyncByte(bytes[0]));
57        }
58        if bytes[1] != SEGMENT_TYPE {
59            return Err(Error::UnknownSegmentType(bytes[1]));
60        }
61        let page_id = u16::from_be_bytes([bytes[2], bytes[3]]);
62        let segment_length = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
63        let total = HEADER_LEN + segment_length;
64        if bytes.len() < total {
65            return Err(Error::BufferTooShort {
66                need: total,
67                have: bytes.len(),
68                what: "display_definition_segment data",
69            });
70        }
71        let body = &bytes[HEADER_LEN..HEADER_LEN + segment_length];
72        if body.len() < FIXED_BODY_LEN {
73            return Err(Error::BufferTooShort {
74                need: FIXED_BODY_LEN,
75                have: body.len(),
76                what: "display_definition_segment body",
77            });
78        }
79        let b0 = body[0];
80        let dds_version_number = b0 >> 4;
81        let display_window_flag = (b0 & 0x08) != 0;
82        let reserved = b0 & 0x07;
83        let display_width = u16::from_be_bytes([body[1], body[2]]);
84        let display_height = u16::from_be_bytes([body[3], body[4]]);
85
86        if display_window_flag {
87            if body.len() < FIXED_BODY_LEN + WINDOW_LEN {
88                return Err(Error::BufferTooShort {
89                    need: FIXED_BODY_LEN + WINDOW_LEN,
90                    have: body.len(),
91                    what: "display_definition_segment window",
92                });
93            }
94            let dw = &body[FIXED_BODY_LEN..FIXED_BODY_LEN + WINDOW_LEN];
95            Ok(DisplayDefinitionSegment {
96                page_id,
97                dds_version_number,
98                display_window_flag,
99                reserved,
100                display_width,
101                display_height,
102                display_window_horizontal_position_minimum: Some(u16::from_be_bytes([
103                    dw[0], dw[1],
104                ])),
105                display_window_horizontal_position_maximum: Some(u16::from_be_bytes([
106                    dw[2], dw[3],
107                ])),
108                display_window_vertical_position_minimum: Some(u16::from_be_bytes([dw[4], dw[5]])),
109                display_window_vertical_position_maximum: Some(u16::from_be_bytes([dw[6], dw[7]])),
110            })
111        } else {
112            Ok(DisplayDefinitionSegment {
113                page_id,
114                dds_version_number,
115                display_window_flag,
116                reserved,
117                display_width,
118                display_height,
119                display_window_horizontal_position_minimum: None,
120                display_window_horizontal_position_maximum: None,
121                display_window_vertical_position_minimum: None,
122                display_window_vertical_position_maximum: None,
123            })
124        }
125    }
126}
127
128impl Serialize for DisplayDefinitionSegment {
129    type Error = Error;
130
131    fn serialized_len(&self) -> usize {
132        HEADER_LEN
133            + FIXED_BODY_LEN
134            + if self.display_window_flag {
135                WINDOW_LEN
136            } else {
137                0
138            }
139    }
140
141    fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
142        let len = self.serialized_len();
143        if buf.len() < len {
144            return Err(Error::BufferTooShort {
145                need: len,
146                have: buf.len(),
147                what: "display_definition_segment serialize",
148            });
149        }
150        // Generic header
151        buf[0] = 0x0F; // sync_byte
152        buf[1] = SEGMENT_TYPE;
153        buf[2..4].copy_from_slice(&self.page_id.to_be_bytes());
154        let seg_len = crate::segments::check_segment_length(len - HEADER_LEN)?;
155        buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
156
157        // Fixed body byte 0: version(4b) | window_flag(1b) | reserved(3b)
158        buf[6] =
159            (broadcast_common::len::fit_mask(self.dds_version_number, 0x0F, "dds_version_number")?
160                << 4)
161                | (u8::from(self.display_window_flag) << 3)
162                | broadcast_common::len::fit_mask(self.reserved, 0x07, "reserved")?;
163        // Fixed body bytes 1-4: display_width, display_height
164        buf[7..9].copy_from_slice(&self.display_width.to_be_bytes());
165        buf[9..11].copy_from_slice(&self.display_height.to_be_bytes());
166
167        // Optional window
168        if self.display_window_flag {
169            let wmin = self.display_window_horizontal_position_minimum.unwrap_or(0);
170            let wmax = self.display_window_horizontal_position_maximum.unwrap_or(0);
171            let vmin = self.display_window_vertical_position_minimum.unwrap_or(0);
172            let vmax = self.display_window_vertical_position_maximum.unwrap_or(0);
173            buf[11..13].copy_from_slice(&wmin.to_be_bytes());
174            buf[13..15].copy_from_slice(&wmax.to_be_bytes());
175            buf[15..17].copy_from_slice(&vmin.to_be_bytes());
176            buf[17..19].copy_from_slice(&vmax.to_be_bytes());
177        }
178        Ok(len)
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185    use broadcast_common::{Parse, Serialize};
186
187    #[test]
188    fn round_trip_no_window() {
189        let bytes = [
190            0x0F, 0x14, 0x00, 0x01, 0x00, 0x05, 0x30, 0x02, 0xCF, 0x01, 0x1F,
191        ];
192        let seg = DisplayDefinitionSegment::parse(&bytes).unwrap();
193        assert_eq!(seg.dds_version_number, 3);
194        assert!(!seg.display_window_flag);
195        assert_eq!(seg.display_width, 719);
196        assert_eq!(seg.display_height, 287);
197        let out = seg.try_to_bytes().unwrap();
198        assert_eq!(out, bytes);
199
200        // Biting test: mutation changes output
201        let mut seg2 = seg.clone();
202        seg2.display_width = 100;
203        let out2 = seg2.try_to_bytes().unwrap();
204        assert_ne!(out2, bytes);
205        let reparse = DisplayDefinitionSegment::parse(&out2).unwrap();
206        assert_eq!(reparse.display_width, 100);
207    }
208
209    /// DS-W2 (#1108): sync_byte wasn't checked by any typed segment parser.
210    #[test]
211    fn rejects_bad_sync_byte() {
212        let bytes = [
213            0x00, 0x14, 0x00, 0x01, 0x00, 0x05, 0x30, 0x02, 0xCF, 0x01, 0x1F,
214        ];
215        assert!(matches!(
216            DisplayDefinitionSegment::parse(&bytes),
217            Err(Error::BadSyncByte(0x00))
218        ));
219    }
220
221    #[test]
222    fn round_trip_with_window() {
223        let bytes = [
224            0x0F, 0x14, 0x00, 0x01, 0x00, 0x0D, 0x48, 0x02, 0xCF, 0x01, 0x1F, 0x00, 0x32, 0x02,
225            0x9E, 0x00, 0x14, 0x01, 0x0C,
226        ];
227        let seg = DisplayDefinitionSegment::parse(&bytes).unwrap();
228        assert_eq!(seg.dds_version_number, 4);
229        assert!(seg.display_window_flag);
230        assert_eq!(seg.display_window_horizontal_position_minimum, Some(50));
231        assert_eq!(seg.display_window_horizontal_position_maximum, Some(670));
232        let out = seg.try_to_bytes().unwrap();
233        assert_eq!(out, bytes);
234
235        // Biting test: clearing the flag drops window
236        let mut seg2 = seg.clone();
237        seg2.display_window_flag = false;
238        let out2 = seg2.try_to_bytes().unwrap();
239        assert_ne!(out2.len(), out.len());
240        let reparse = DisplayDefinitionSegment::parse(&out2).unwrap();
241        assert!(!reparse.display_window_flag);
242    }
243}