pgs_parse/
pgs_pcs_segment.rs

1//! # PGS Presentation Composition Segment (PCS)
2//!
3//! This module defines the `PgsPcsSegment` struct, which represents the Presentation Composition Segment (PCS)
4//! in the Presentation Graphic Stream (PGS) format. The PCS provides the details of how the subtitles (or other
5//! graphic elements) are arranged and displayed on the screen.
6
7use std::rc::Rc;
8
9use crate::{pgs_memory_buffer::{BigEndian, ReadBytes}, pgs_segment_header::PgsSegmentHeader, Error, PgsMemoryBuffer, Result};
10
11/// Enum representing the object cropping flag in a PCS.
12/// This flag indicates whether the object (subtitle image) is cropped and whether a forced cropped image should be used.
13#[derive(Debug, PartialEq)]
14pub enum PgsPcsObjectCroppedFlag {
15    ForceCroppedImage = 0x40,
16    Off = 0x00
17}
18
19impl From<u8> for PgsPcsObjectCroppedFlag {
20    /// Converts a raw `u8` value to the corresponding `PgsPcsObjectCroppedFlag` enum variant.
21    ///
22    /// # Parameters
23    /// - `value`: The raw `u8` value representing the cropped flag.
24    ///
25    /// # Returns
26    /// The corresponding `PgsPcsObjectCroppedFlag` variant.    
27    fn from(value: u8) -> Self {
28        match value {
29            0x40 => PgsPcsObjectCroppedFlag::ForceCroppedImage,
30            _ => PgsPcsObjectCroppedFlag::Off
31        }
32    }
33}
34
35/// Struct representing a composition object in a PCS.
36/// Composition objects describe the individual graphic elements that make up the subtitle image and its placement on the screen.
37#[derive(Debug)]
38pub struct PgsPcsSegmentCompositionObjects {
39    pub object_id: u16,
40    pub window_id: u8,
41    pub object_cropped_flag: PgsPcsObjectCroppedFlag,
42    pub object_horizontal_position: u16,
43    pub object_vertical_position: u16,
44    pub object_cropping_horizontal_position: u16,
45    pub object_cropping_vertical_position: u16,
46    pub object_cropping_width: u16,
47    pub object_cropping_height_position: u16
48}
49
50impl PgsPcsSegmentCompositionObjects {
51    fn new() -> Self {
52        PgsPcsSegmentCompositionObjects {
53            object_id: 0,
54            window_id: 0,
55            object_cropped_flag: PgsPcsObjectCroppedFlag::Off,
56            object_horizontal_position: 0,
57            object_vertical_position: 0,
58            object_cropping_horizontal_position: 0,
59            object_cropping_vertical_position: 0,
60            object_cropping_width: 0,
61            object_cropping_height_position: 0
62        }
63    }
64}
65
66/// Enum representing the composition state of a PCS.
67/// The composition state describes whether the segment starts a new display, refreshes an existing display, or updates a display.
68#[derive(Debug, Clone, Copy, PartialEq)]
69pub enum PgsPcsCompositionState {
70    // Defines a new display.
71    EpochStart,
72    // Defines a display refresh.
73    AcquisitionPoint,
74    // Defines a display update.
75    Normal,
76}
77
78impl From<u8> for PgsPcsCompositionState {
79    /// Converts a raw `u8` value to the corresponding `PgsPcsCompositionState` enum variant.
80    ///
81    /// # Parameters
82    /// - `value`: The raw `u8` value representing the composition state.
83    ///
84    /// # Returns
85    /// The corresponding `PgsPcsCompositionState` variant.    
86    fn from(value: u8) -> Self {
87        match value {
88            0x80 => PgsPcsCompositionState::EpochStart,
89            0x40 => PgsPcsCompositionState::AcquisitionPoint,
90            _ => PgsPcsCompositionState::Normal
91        }
92    }
93}
94
95#[derive(Debug)]
96pub struct PgsPcsSegment {
97    pub header: PgsSegmentHeader,
98    pub width: u16,
99    pub height: u16,
100    pub frame_rate: u8,
101    pub composition_number: u16,
102    pub composition_state: PgsPcsCompositionState,
103    pub palette_update_flag: u8,
104    pub palette_id: u8,
105    pub number_of_composition_objects: u8,
106    pub composition_objects: Vec<PgsPcsSegmentCompositionObjects>
107}
108
109/// Struct representing a Presentation Composition Segment (PCS) in a PGS file.
110/// The PCS defines how individual graphic objects (subtitles, etc.) are displayed on the screen, their position, and composition state.
111impl PgsPcsSegment {
112    fn new(header: PgsSegmentHeader) -> Self {
113        PgsPcsSegment {
114            header,
115            width: 0,
116            height: 0,
117            frame_rate: 0x10,
118            composition_number: 0,
119            composition_state: PgsPcsCompositionState::Normal,
120            palette_update_flag: 0,
121            palette_id: 0,
122            number_of_composition_objects: 0,
123            composition_objects: Vec::new()
124        }
125    }
126
127    /// Creates a new, empty `PgsPcsSegment`.
128    ///
129    /// # Parameters
130    /// - `header`: The segment header for the PCS.
131    ///
132    /// # Returns
133    /// A new `PgsPcsSegment` instance with default values for the composition objects.
134    pub fn from_data(header: PgsSegmentHeader, data: &[u8]) -> Result<Rc<PgsPcsSegment>> {
135        if data.len() < header.segment_length as usize {
136            return Err(Error::InvalidSegmentDataLength);
137        }
138
139        let mut segment = PgsPcsSegment::new(header);
140
141        let mut buffer: PgsMemoryBuffer = PgsMemoryBuffer::from(data);
142        segment.width = buffer.read_u16::<BigEndian>()?;
143        segment.height = buffer.read_u16::<BigEndian>()?;
144        let _ = buffer.read_u8()?;
145        segment.composition_number = buffer.read_u16::<BigEndian>()?;
146        segment.composition_state = PgsPcsCompositionState::from(buffer.read_u8()?);
147        segment.palette_update_flag = buffer.read_u8()?;
148        segment.palette_id = buffer.read_u8()?;
149        segment.number_of_composition_objects = buffer.read_u8()?;
150
151        for _ in 0..segment.number_of_composition_objects {
152            let mut com_obj = PgsPcsSegmentCompositionObjects::new();
153            com_obj.object_id = buffer.read_u16::<BigEndian>()?;
154            com_obj.window_id = buffer.read_u8()?;
155            com_obj.object_cropped_flag = PgsPcsObjectCroppedFlag::from(buffer.read_u8()?);
156            com_obj.object_horizontal_position = buffer.read_u16::<BigEndian>()?;
157            com_obj.object_vertical_position = buffer.read_u16::<BigEndian>()?;
158            if com_obj.object_cropped_flag == PgsPcsObjectCroppedFlag::ForceCroppedImage {
159                com_obj.object_cropping_horizontal_position = buffer.read_u16::<BigEndian>()?;
160                com_obj.object_cropping_vertical_position = buffer.read_u16::<BigEndian>()?;
161                com_obj.object_cropping_width = buffer.read_u16::<BigEndian>()?;
162                com_obj.object_cropping_height_position = buffer.read_u16::<BigEndian>()?;
163            }
164
165            segment.composition_objects.push(com_obj);
166        }
167
168        Ok(Rc::new(segment))
169    }
170}
171
172impl Default for PgsPcsSegment {
173    fn default() -> Self {
174        Self::new(Default::default())
175    }
176}