Skip to main content

dvb_subtitle/segments/
clut_definition.rs

1//! CLUT Definition Segment โ€” ETSI EN 300 743 ยง7.2.4, Table 16 (segment_type 0x12).
2
3use crate::error::{Error, Result};
4use broadcast_common::{Parse, Serialize};
5
6/// The CLUT_definition_segment segment_type.
7pub const SEGMENT_TYPE: u8 = 0x12;
8/// Header: 6 bytes.
9pub const HEADER_LEN: usize = 6;
10/// Fixed body: CLUT_id(1) + CLUT_version_number(4b)+reserved(4b) = 2 bytes.
11pub const FIXED_LEN: usize = 2;
12/// Entry header: CLUT_entry_id(1) + flags(1) = 2 bytes.
13pub const ENTRY_HEADER_LEN: usize = 2;
14/// Full-range colour: 4 bytes.
15pub const ENTRY_FULL_LEN: usize = 4;
16/// Reduced-range colour: 2 bytes.
17pub const ENTRY_REDUCED_LEN: usize = 2;
18
19/// A single CLUT entry.
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22pub struct ClutEntry {
23    /// CLUT entry number.
24    pub clut_entry_id: u8,
25    /// Whether this loads into the 2-bit/entry CLUT.
26    pub flag_2bit: bool,
27    /// Whether this loads into the 4-bit/entry CLUT.
28    pub flag_4bit: bool,
29    /// Whether this loads into the 8-bit/entry CLUT.
30    pub flag_8bit: bool,
31    /// Reserved bits in the flags byte (bits `[4:1]`, must be preserved for round-trip).
32    pub reserved_flags: u8,
33    /// Whether full 8-bit resolution colour values follow.
34    pub full_range_flag: bool,
35    /// Y output value.
36    pub y_value: u8,
37    /// Cr output value.
38    pub cr_value: u8,
39    /// Cb output value.
40    pub cb_value: u8,
41    /// T (transparency) output value.
42    pub t_value: u8,
43}
44
45impl ClutEntry {
46    fn serialized_len(&self) -> usize {
47        ENTRY_HEADER_LEN
48            + if self.full_range_flag {
49                ENTRY_FULL_LEN
50            } else {
51                ENTRY_REDUCED_LEN
52            }
53    }
54
55    fn serialize_into(&self, buf: &mut [u8]) {
56        buf[0] = self.clut_entry_id;
57        let mut flags: u8 = self.reserved_flags;
58        if self.flag_2bit {
59            flags |= 0x80;
60        }
61        if self.flag_4bit {
62            flags |= 0x40;
63        }
64        if self.flag_8bit {
65            flags |= 0x20;
66        }
67        if self.full_range_flag {
68            flags |= 0x01;
69        }
70        buf[1] = flags;
71        if self.full_range_flag {
72            buf[2] = self.y_value;
73            buf[3] = self.cr_value;
74            buf[4] = self.cb_value;
75            buf[5] = self.t_value;
76        } else {
77            buf[2] = (self.y_value << 2) | (self.cr_value >> 2);
78            buf[3] = (self.cr_value << 6) | (self.cb_value << 2) | self.t_value;
79        }
80    }
81
82    fn parse(bytes: &[u8]) -> Result<(Self, usize)> {
83        if bytes.len() < ENTRY_HEADER_LEN {
84            return Err(Error::BufferTooShort {
85                need: ENTRY_HEADER_LEN,
86                have: bytes.len(),
87                what: "CLUT_entry header",
88            });
89        }
90        let clut_entry_id = bytes[0];
91        let flag_2bit = (bytes[1] & 0x80) != 0;
92        let flag_4bit = (bytes[1] & 0x40) != 0;
93        let flag_8bit = (bytes[1] & 0x20) != 0;
94        let reserved_flags = bytes[1] & 0x1E;
95        let full_range_flag = (bytes[1] & 0x01) != 0;
96
97        let total = ENTRY_HEADER_LEN
98            + if full_range_flag {
99                ENTRY_FULL_LEN
100            } else {
101                ENTRY_REDUCED_LEN
102            };
103        if bytes.len() < total {
104            return Err(Error::BufferTooShort {
105                need: total,
106                have: bytes.len(),
107                what: "CLUT_entry data",
108            });
109        }
110        let (y, cr, cb, t) = if full_range_flag {
111            (bytes[2], bytes[3], bytes[4], bytes[5])
112        } else {
113            let y = bytes[2] >> 2;
114            let cr = ((bytes[2] & 0x03) << 2) | (bytes[3] >> 6);
115            let cb = (bytes[3] >> 2) & 0x0F;
116            let t = bytes[3] & 0x03;
117            (y, cr, cb, t)
118        };
119        Ok((
120            ClutEntry {
121                clut_entry_id,
122                flag_2bit,
123                flag_4bit,
124                flag_8bit,
125                reserved_flags,
126                full_range_flag,
127                y_value: y,
128                cr_value: cr,
129                cb_value: cb,
130                t_value: t,
131            },
132            total,
133        ))
134    }
135}
136
137/// CLUT Definition Segment.
138#[derive(Debug, Clone, PartialEq, Eq)]
139#[cfg_attr(feature = "serde", derive(serde::Serialize))]
140pub struct ClutDefinitionSegment {
141    /// The page_id from the segment header.
142    pub page_id: u16,
143    /// CLUT family identifier.
144    pub clut_id: u8,
145    /// CLUT version number (modulo 16).
146    pub clut_version_number: u8,
147    /// Reserved bits in the body byte 1 (bits `[3:0]`).
148    pub reserved: u8,
149    /// CLUT entries.
150    pub entries: alloc::vec::Vec<ClutEntry>,
151    /// Trailing bytes after the last successfully-parsed entry (preserved for round-trip).
152    #[cfg_attr(feature = "serde", serde(skip))]
153    pub(crate) suffix: alloc::vec::Vec<u8>,
154}
155
156impl<'a> Parse<'a> for ClutDefinitionSegment {
157    type Error = Error;
158
159    fn parse(bytes: &'a [u8]) -> Result<Self> {
160        if bytes.len() < HEADER_LEN + FIXED_LEN {
161            return Err(Error::BufferTooShort {
162                need: HEADER_LEN + FIXED_LEN,
163                have: bytes.len(),
164                what: "CLUT_definition_segment",
165            });
166        }
167        if bytes[1] != SEGMENT_TYPE {
168            return Err(Error::UnknownSegmentType(bytes[1]));
169        }
170        let page_id = u16::from_be_bytes([bytes[2], bytes[3]]);
171        let segment_length = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
172        let total = HEADER_LEN + segment_length;
173        if bytes.len() < total {
174            return Err(Error::BufferTooShort {
175                need: total,
176                have: bytes.len(),
177                what: "CLUT_definition_segment data",
178            });
179        }
180        let body = &bytes[HEADER_LEN..HEADER_LEN + segment_length];
181        if body.len() < FIXED_LEN {
182            return Err(Error::BufferTooShort {
183                need: FIXED_LEN,
184                have: body.len(),
185                what: "CLUT_definition_segment body",
186            });
187        }
188        let clut_id = body[0];
189        let clut_version_number = body[1] >> 4;
190        let reserved = body[1] & 0x0F;
191
192        let rest = &body[FIXED_LEN..];
193        let mut entries = alloc::vec::Vec::new();
194        let mut pos: usize = 0;
195        let mut suffix = alloc::vec::Vec::new();
196        while pos < rest.len() {
197            match ClutEntry::parse(&rest[pos..]) {
198                Ok((entry, entry_len)) => {
199                    entries.push(entry);
200                    pos += entry_len;
201                }
202                Err(_) => {
203                    // Truncated or malformed entry โ€” preserve remainder as raw
204                    suffix.extend_from_slice(&rest[pos..]);
205                    break;
206                }
207            }
208        }
209
210        Ok(ClutDefinitionSegment {
211            page_id,
212            clut_id,
213            clut_version_number,
214            reserved,
215            entries,
216            suffix,
217        })
218    }
219}
220
221impl Serialize for ClutDefinitionSegment {
222    type Error = Error;
223
224    fn serialized_len(&self) -> usize {
225        HEADER_LEN
226            + FIXED_LEN
227            + self
228                .entries
229                .iter()
230                .map(|e| e.serialized_len())
231                .sum::<usize>()
232            + self.suffix.len()
233    }
234
235    fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
236        let len = self.serialized_len();
237        if buf.len() < len {
238            return Err(Error::BufferTooShort {
239                need: len,
240                have: buf.len(),
241                what: "CLUT_definition_segment serialize",
242            });
243        }
244        buf[0] = 0x0F;
245        buf[1] = SEGMENT_TYPE;
246        buf[2..4].copy_from_slice(&self.page_id.to_be_bytes());
247        let seg_len = (len - HEADER_LEN) as u16;
248        buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
249
250        buf[6] = self.clut_id;
251        buf[7] = (self.clut_version_number << 4) | (self.reserved & 0x0F);
252
253        let mut off = HEADER_LEN + FIXED_LEN;
254        for entry in &self.entries {
255            entry.serialize_into(&mut buf[off..]);
256            off += entry.serialized_len();
257        }
258        buf[off..off + self.suffix.len()].copy_from_slice(&self.suffix);
259        Ok(len)
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266    use broadcast_common::{Parse, Serialize};
267
268    #[test]
269    fn round_trip_full_range() {
270        let bytes = [
271            0x0F, 0x12, 0x00, 0x01, 0x00, 0x0E, 0x03, 0x10, 0x00, 0xA1, 0x80, 0x80, 0x80, 0x80,
272            0x01, 0x61, 0xFF, 0xFF, 0xFF, 0x00,
273        ];
274        let seg = ClutDefinitionSegment::parse(&bytes).unwrap();
275        assert_eq!(seg.clut_id, 3);
276        assert_eq!(seg.entries.len(), 2);
277        assert_eq!(seg.entries[0].clut_entry_id, 0);
278        assert!(seg.entries[0].flag_8bit);
279        assert!(seg.entries[0].full_range_flag);
280        assert_eq!(seg.entries[0].y_value, 128);
281        assert_eq!(seg.entries[1].clut_entry_id, 1);
282        assert!(seg.entries[1].flag_4bit);
283        let out = seg.to_bytes();
284        assert_eq!(out, bytes);
285
286        // Biting test
287        let mut seg2 = seg.clone();
288        seg2.clut_id = 5;
289        let out2 = seg2.to_bytes();
290        assert_ne!(out2, bytes);
291        let reparse = ClutDefinitionSegment::parse(&out2).unwrap();
292        assert_eq!(reparse.clut_id, 5);
293    }
294
295    #[test]
296    fn round_trip_reduced_range() {
297        let bytes = [
298            0x0F, 0x12, 0x00, 0x01, 0x00, 0x0A, 0x03, 0x10, 0x00, 0x80, 0x00, 0x00, 0x01, 0x40,
299            0xFD, 0xFC,
300        ];
301        let seg = ClutDefinitionSegment::parse(&bytes).unwrap();
302        assert_eq!(seg.entries.len(), 2);
303        assert!(seg.entries[0].flag_2bit);
304        assert!(!seg.entries[0].full_range_flag);
305        assert_eq!(seg.entries[0].y_value, 0);
306        let out = seg.to_bytes();
307        assert_eq!(out, bytes);
308
309        // Biting test
310        let mut seg2 = seg.clone();
311        seg2.entries[0].flag_2bit = false;
312        seg2.entries[0].flag_8bit = true;
313        let out2 = seg2.to_bytes();
314        assert_ne!(out2, bytes);
315        let reparse = ClutDefinitionSegment::parse(&out2).unwrap();
316        assert!(reparse.entries[0].flag_8bit);
317    }
318
319    #[test]
320    fn tolerates_nonzero_reserved_bits() {
321        let bytes = [
322            0x0F, 0x12, 0x00, 0x01, 0x00,
323            0x06, // seg_len=6 = 2 fixed + 4 entry (2 header + 2 reduced)
324            0x03, 0x1A, // CLUT_id=3, version=1, reserved=0xA
325            0x00, 0x8A, 0x00,
326            0x00, // entry: flag_2bit=1, reserved_flags=0x0A, full=0, Y=Cr=Cb=T=0
327        ];
328        let seg = ClutDefinitionSegment::parse(&bytes).unwrap();
329        assert_eq!(seg.reserved, 0x0A);
330        assert_eq!(seg.entries.len(), 1);
331        assert_eq!(seg.entries[0].reserved_flags, 0x0A);
332        assert!(seg.entries[0].flag_2bit);
333        let out = seg.to_bytes();
334        assert_eq!(out, bytes);
335        let seg2 = ClutDefinitionSegment::parse(&out).unwrap();
336        assert_eq!(seg2.reserved, 0x0A);
337        assert_eq!(seg2.entries[0].reserved_flags, 0x0A);
338    }
339}