1use bon::Builder;
2
3use crate::atom::util::ColorRgb;
4
5use super::StsdExtension;
6
7#[derive(Debug, Clone, Default, Builder)]
8pub struct TextSampleEntry {
9 #[builder(default)]
10 pub display_flags: DisplayFlags,
11 #[builder(default)]
12 pub text_justification: TextJustification,
13 #[builder(default)]
14 pub background_color: ColorRgb,
15 #[builder(default)]
16 pub default_text_box: TextBox,
17 #[builder(default)]
18 pub font_number: u16,
19 #[builder(default)]
21 pub font_face: FontFace,
22 #[builder(default)]
23 pub foreground_color: ColorRgb,
24 #[builder(into)]
25 pub font_name: String,
26 #[builder(default)]
27 pub extensions: Vec<StsdExtension>,
28}
29
30#[derive(Debug, Clone, Default)]
31pub struct DisplayFlags {
32 pub disable_auto_scale: bool, pub use_movie_background_color: bool, pub scroll_in: bool, pub scroll_out: bool, pub horizontal_scroll: bool, pub reverse_scroll: bool, pub continuous_scroll: bool, pub drop_shadow: bool, pub anti_alias: bool, pub key_text: bool, }
55
56#[derive(Debug, Clone, Default)]
57pub enum TextJustification {
58 #[default]
59 Left,
60 Centre,
61 Right,
62 Other(i32),
63}
64
65#[derive(Debug, Clone, Default)]
66pub struct TextBox {
67 pub top: u16,
68 pub left: u16,
69 pub bottom: u16,
70 pub right: u16,
71}
72
73#[derive(Debug, Clone, Default)]
74pub struct FontFace {
75 pub bold: bool,
76 pub italic: bool,
77 pub underline: bool,
78 pub outline: bool,
79 pub shadow: bool,
80 pub condense: bool,
81 pub extend: bool,
82}
83
84pub(super) mod serializer {
85 use super::{DisplayFlags, FontFace, TextBox, TextJustification, TextSampleEntry};
86 use crate::atom::{
87 stsd::extension::serializer::serialize_stsd_extensions,
88 util::serializer::{bits::Packer, color_rgb, pascal_string},
89 };
90
91 pub fn serialize_text_sample_entry(text: TextSampleEntry) -> Vec<u8> {
92 let mut data = Vec::new();
93
94 data.extend(display_flags(text.display_flags));
95 data.extend(text_justification(text.text_justification));
96 data.extend(color_rgb(text.background_color));
97 data.extend(text_box(text.default_text_box));
98 data.extend([0u8; 8]); data.extend(text.font_number.to_be_bytes());
100 data.extend(font_face(text.font_face));
101 data.extend([0u8; 2]); data.extend(color_rgb(text.foreground_color));
103 data.extend(pascal_string(text.font_name));
104 data.extend(serialize_stsd_extensions(text.extensions));
105
106 data
107 }
108
109 fn display_flags(d: DisplayFlags) -> [u8; 4] {
110 let mut packer = Packer::from(vec![0u8; 2]); packer.push_n::<1>(0); packer.push_bool(d.key_text);
113 packer.push_bool(d.anti_alias);
114 packer.push_bool(d.drop_shadow);
115 packer.push_n::<2>(0); packer.push_bool(d.continuous_scroll);
117 packer.push_bool(d.reverse_scroll);
118 packer.push_bool(d.horizontal_scroll);
119 packer.push_bool(d.scroll_out);
120 packer.push_bool(d.scroll_in);
121 packer.push_n::<1>(0); packer.push_bool(d.use_movie_background_color);
123 packer.push_n::<1>(0); packer.push_bool(d.disable_auto_scale);
125 packer.push_n::<1>(0); Vec::from(packer)
127 .try_into()
128 .expect("display_flags is 4 bytes")
129 }
130
131 fn text_justification(j: TextJustification) -> [u8; 4] {
132 let value: i32 = match j {
133 TextJustification::Left => 0,
134 TextJustification::Centre => 1,
135 TextJustification::Right => -1,
136 TextJustification::Other(v) => v,
137 };
138 value.to_be_bytes()
139 }
140
141 fn text_box(b: TextBox) -> [u8; 8] {
142 let mut data = Vec::with_capacity(6);
143 data.extend(b.top.to_be_bytes());
144 data.extend(b.left.to_be_bytes());
145 data.extend(b.bottom.to_be_bytes());
146 data.extend(b.right.to_be_bytes());
147 data.try_into().expect("text_box is 8 bytes")
148 }
149
150 fn font_face(f: FontFace) -> [u8; 2] {
151 let mut packer = Packer::from(vec![0u8; 1]); packer.push_n::<1>(0); packer.push_bool(f.extend);
154 packer.push_bool(f.condense);
155 packer.push_bool(f.shadow);
156 packer.push_bool(f.outline);
157 packer.push_bool(f.underline);
158 packer.push_bool(f.italic);
159 packer.push_bool(f.bold);
160 Vec::from(packer).try_into().expect("font_face is 2 bytes")
161 }
162}
163
164pub(super) mod parser {
165 use winnow::{
166 binary::{be_i32, be_u16, bits},
167 combinator::seq,
168 error::{ContextError, ErrMode, StrContext},
169 ModalResult, Parser,
170 };
171
172 use crate::atom::{
173 stsd::{extension::parser::parse_stsd_extensions, SampleEntryData},
174 util::parser::{byte_array, color_rgb, pascal_string, Stream},
175 };
176
177 use super::*;
178
179 pub fn parse_text_sample_entry(input: &mut Stream<'_>) -> ModalResult<SampleEntryData> {
180 seq!(TextSampleEntry {
181 display_flags: bits::bits(display_flags).context(StrContext::Label("display_flags")),
182 text_justification: text_justification.context(StrContext::Label("text_justification")),
183 background_color: color_rgb.context(StrContext::Label("background_color")),
184 default_text_box: text_box.context(StrContext::Label("default_text_box")),
185 _: byte_array::<8>.context(StrContext::Label("reserved")),
186 font_number: be_u16.context(StrContext::Label("font_number")),
187 font_face: bits::bits(font_face).context(StrContext::Label("font_face")),
188 _: byte_array::<2>.context(StrContext::Label("reserved")),
189 foreground_color: color_rgb.context(StrContext::Label("foreground_color")),
190 font_name: pascal_string.context(StrContext::Label("text_name")),
191 extensions: parse_stsd_extensions.context(StrContext::Label("extensions")),
192 })
193 .map(SampleEntryData::Text)
194 .parse_next(input)
195 }
196
197 fn display_flags(input: &mut (Stream, usize)) -> ModalResult<DisplayFlags> {
198 use bits::bool;
199 seq!(DisplayFlags {
200 _: bits::take::<_, usize, _, ErrMode<ContextError>>(8usize).context(StrContext::Label("leading byte 1")),
201 _: bits::take::<_, usize, _, ErrMode<ContextError>>(8usize).context(StrContext::Label("leading byte 2")),
202 _: bits::take::<_, usize, _, ErrMode<ContextError>>(1usize).context(StrContext::Label("leading bit")),
203 key_text: bool.context(StrContext::Label("key_text")),
204 anti_alias: bool.context(StrContext::Label("anti_alias")),
205 drop_shadow: bool.context(StrContext::Label("drop_shadow")),
206 _: bits::take::<_, usize, _, ErrMode<ContextError>>(2usize).context(StrContext::Label("padding")),
207 continuous_scroll: bool.context(StrContext::Label("continuous_scroll")),
208 reverse_scroll: bool.context(StrContext::Label("reverse_scroll")),
209 horizontal_scroll: bool.context(StrContext::Label("horizontal_scroll")),
210 scroll_out: bool.context(StrContext::Label("scroll_out")),
211 scroll_in: bool.context(StrContext::Label("scroll_in")),
212 _: bits::take::<_, usize, _, ErrMode<ContextError>>(1usize).context(StrContext::Label("padding")),
213 use_movie_background_color: bool
214 .context(StrContext::Label("use_movie_background_color")),
215 _: bits::take::<_, usize, _, ErrMode<ContextError>>(1usize).context(StrContext::Label("padding")),
216 disable_auto_scale: bool.context(StrContext::Label("disable_auto_scale")),
217 _: bits::take::<_, usize, _, ErrMode<ContextError>>(1usize).context(StrContext::Label("padding")),
218 })
219 .parse_next(input)
220 }
221
222 fn text_justification(input: &mut Stream<'_>) -> ModalResult<TextJustification> {
223 let text_justification = be_i32.parse_next(input)?;
224 Ok(match text_justification {
225 0 => TextJustification::Left,
226 1 => TextJustification::Centre,
227 -1 => TextJustification::Right,
228 v => TextJustification::Other(v),
229 })
230 }
231
232 fn text_box(input: &mut Stream<'_>) -> ModalResult<TextBox> {
233 seq!(TextBox {
234 top: be_u16.context(StrContext::Label("top")),
235 left: be_u16.context(StrContext::Label("left")),
236 bottom: be_u16.context(StrContext::Label("bottom")),
237 right: be_u16.context(StrContext::Label("right")),
238 })
239 .parse_next(input)
240 }
241
242 fn font_face(input: &mut (Stream, usize)) -> ModalResult<FontFace> {
243 use bits::bool;
244 seq!(FontFace {
245 _: bits::take::<_, usize, _, ErrMode<ContextError>>(8usize).context(StrContext::Label("leading empty byte")),
246 _: bits::take::<_, usize, _, ErrMode<ContextError>>(1usize).context(StrContext::Label("leading empty bit")),
247 extend: bool.context(StrContext::Label("extend")),
248 condense: bool.context(StrContext::Label("condense")),
249 shadow: bool.context(StrContext::Label("shadow")),
250 outline: bool.context(StrContext::Label("outline")),
251 underline: bool.context(StrContext::Label("underline")),
252 italic: bool.context(StrContext::Label("italic")),
253 bold: bool.context(StrContext::Label("bold")),
254 })
255 .parse_next(input)
256 }
257}