Skip to main content

libbitsub_core/pgs/
composition.rs

1//! Presentation Composition Segment parsing.
2
3use super::segment::CompositionState;
4use crate::utils::BigEndianReader;
5
6/// Composition object reference within a presentation.
7#[derive(Debug, Clone, Copy, Default)]
8pub struct CompositionObject {
9    /// Object ID reference
10    pub object_id: u16,
11    /// Window ID reference
12    pub window_id: u8,
13    /// Cropping flag (bit 7 = has cropping)
14    pub cropped_flag: u8,
15    /// Horizontal position within window
16    pub x: u16,
17    /// Vertical position within window
18    pub y: u16,
19    /// Cropping horizontal position (if cropped_flag set)
20    pub crop_x: u16,
21    /// Cropping vertical position (if cropped_flag set)
22    pub crop_y: u16,
23    /// Cropping width (if cropped_flag set)
24    pub crop_width: u16,
25    /// Cropping height (if cropped_flag set)
26    pub crop_height: u16,
27}
28
29impl CompositionObject {
30    /// Check if this object has cropping enabled.
31    #[inline]
32    pub fn has_cropping(&self) -> bool {
33        (self.cropped_flag & 0x80) != 0
34    }
35}
36
37/// Presentation Composition Segment contains display parameters.
38#[derive(Debug, Clone)]
39pub struct PresentationCompositionSegment {
40    /// Video width
41    pub width: u16,
42    /// Video height
43    pub height: u16,
44    /// Frame rate (encoded value)
45    pub frame_rate: u8,
46    /// Composition number (sequence identifier)
47    pub composition_number: u16,
48    /// Composition state (Normal, AcquisitionPoint, EpochStart)
49    pub composition_state: u8,
50    /// Palette update flag (bit 7 = palette only update)
51    pub palette_update_flag: u8,
52    /// Palette ID to use
53    pub palette_id: u8,
54    /// List of composition objects to render
55    pub composition_objects: Vec<CompositionObject>,
56}
57
58impl PresentationCompositionSegment {
59    /// Parse a presentation composition segment from binary data.
60    pub fn parse(reader: &mut BigEndianReader, _length: usize) -> Option<Self> {
61        let width = reader.read_u16()?;
62        let height = reader.read_u16()?;
63        let frame_rate = reader.read_u8()?;
64        let composition_number = reader.read_u16()?;
65        let composition_state = reader.read_u8()?;
66        let palette_update_flag = reader.read_u8()?;
67        let palette_id = reader.read_u8()?;
68
69        let count = reader.read_u8()? as usize;
70        let mut composition_objects = Vec::with_capacity(count);
71
72        for _ in 0..count {
73            let object_id = reader.read_u16()?;
74            let window_id = reader.read_u8()?;
75            let cropped_flag = reader.read_u8()?;
76            let x = reader.read_u16()?;
77            let y = reader.read_u16()?;
78
79            let (crop_x, crop_y, crop_width, crop_height) = if (cropped_flag & 0x80) != 0 {
80                (
81                    reader.read_u16()?,
82                    reader.read_u16()?,
83                    reader.read_u16()?,
84                    reader.read_u16()?,
85                )
86            } else {
87                (0, 0, 0, 0)
88            };
89
90            composition_objects.push(CompositionObject {
91                object_id,
92                window_id,
93                cropped_flag,
94                x,
95                y,
96                crop_x,
97                crop_y,
98                crop_width,
99                crop_height,
100            });
101        }
102
103        Some(Self {
104            width,
105            height,
106            frame_rate,
107            composition_number,
108            composition_state,
109            palette_update_flag,
110            palette_id,
111            composition_objects,
112        })
113    }
114
115    /// Get the composition state enum.
116    pub fn get_composition_state(&self) -> Option<CompositionState> {
117        CompositionState::try_from(self.composition_state).ok()
118    }
119
120    /// Check if this is an epoch start (complete reset).
121    #[inline]
122    pub fn is_epoch_start(&self) -> bool {
123        self.composition_state == CompositionState::EpochStart as u8
124    }
125
126    /// Check if this is an acquisition point (new boundary).
127    #[inline]
128    pub fn is_acquisition_point(&self) -> bool {
129        self.composition_state == CompositionState::AcquisitionPoint as u8
130    }
131
132    /// Check if this is a palette-only update.
133    #[inline]
134    pub fn is_palette_update_only(&self) -> bool {
135        (self.palette_update_flag & 0x80) != 0
136    }
137}