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] = (self.dds_version_number << 4)
159            | (u8::from(self.display_window_flag) << 3)
160            | (self.reserved & 0x07);
161        // Fixed body bytes 1-4: display_width, display_height
162        buf[7..9].copy_from_slice(&self.display_width.to_be_bytes());
163        buf[9..11].copy_from_slice(&self.display_height.to_be_bytes());
164
165        // Optional window
166        if self.display_window_flag {
167            let wmin = self.display_window_horizontal_position_minimum.unwrap_or(0);
168            let wmax = self.display_window_horizontal_position_maximum.unwrap_or(0);
169            let vmin = self.display_window_vertical_position_minimum.unwrap_or(0);
170            let vmax = self.display_window_vertical_position_maximum.unwrap_or(0);
171            buf[11..13].copy_from_slice(&wmin.to_be_bytes());
172            buf[13..15].copy_from_slice(&wmax.to_be_bytes());
173            buf[15..17].copy_from_slice(&vmin.to_be_bytes());
174            buf[17..19].copy_from_slice(&vmax.to_be_bytes());
175        }
176        Ok(len)
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use broadcast_common::{Parse, Serialize};
184
185    #[test]
186    fn round_trip_no_window() {
187        let bytes = [
188            0x0F, 0x14, 0x00, 0x01, 0x00, 0x05, 0x30, 0x02, 0xCF, 0x01, 0x1F,
189        ];
190        let seg = DisplayDefinitionSegment::parse(&bytes).unwrap();
191        assert_eq!(seg.dds_version_number, 3);
192        assert!(!seg.display_window_flag);
193        assert_eq!(seg.display_width, 719);
194        assert_eq!(seg.display_height, 287);
195        let out = seg.to_bytes();
196        assert_eq!(out, bytes);
197
198        // Biting test: mutation changes output
199        let mut seg2 = seg.clone();
200        seg2.display_width = 100;
201        let out2 = seg2.to_bytes();
202        assert_ne!(out2, bytes);
203        let reparse = DisplayDefinitionSegment::parse(&out2).unwrap();
204        assert_eq!(reparse.display_width, 100);
205    }
206
207    /// DS-W2 (#1108): sync_byte wasn't checked by any typed segment parser.
208    #[test]
209    fn rejects_bad_sync_byte() {
210        let bytes = [
211            0x00, 0x14, 0x00, 0x01, 0x00, 0x05, 0x30, 0x02, 0xCF, 0x01, 0x1F,
212        ];
213        assert!(matches!(
214            DisplayDefinitionSegment::parse(&bytes),
215            Err(Error::BadSyncByte(0x00))
216        ));
217    }
218
219    #[test]
220    fn round_trip_with_window() {
221        let bytes = [
222            0x0F, 0x14, 0x00, 0x01, 0x00, 0x0D, 0x48, 0x02, 0xCF, 0x01, 0x1F, 0x00, 0x32, 0x02,
223            0x9E, 0x00, 0x14, 0x01, 0x0C,
224        ];
225        let seg = DisplayDefinitionSegment::parse(&bytes).unwrap();
226        assert_eq!(seg.dds_version_number, 4);
227        assert!(seg.display_window_flag);
228        assert_eq!(seg.display_window_horizontal_position_minimum, Some(50));
229        assert_eq!(seg.display_window_horizontal_position_maximum, Some(670));
230        let out = seg.to_bytes();
231        assert_eq!(out, bytes);
232
233        // Biting test: clearing the flag drops window
234        let mut seg2 = seg.clone();
235        seg2.display_window_flag = false;
236        let out2 = seg2.to_bytes();
237        assert_ne!(out2.len(), out.len());
238        let reparse = DisplayDefinitionSegment::parse(&out2).unwrap();
239        assert!(!reparse.display_window_flag);
240    }
241}