1use crate::error::{Error, Result};
4use broadcast_common::{Parse, Serialize};
5
6pub const SEGMENT_TYPE: u8 = 0x11;
8pub const HEADER_LEN: usize = 6;
10pub const FIXED_LEN: usize = 10;
12pub const OBJECT_ENTRY_BASE_LEN: usize = 6;
14pub const OBJECT_EXTRA_LEN: usize = 2;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20#[repr(u8)]
21#[non_exhaustive]
22pub enum RegionLevelOfCompatibility {
23 Reserved0 = 0x00,
25 Clut2Bit = 0x01,
27 Clut4Bit = 0x02,
29 Clut8Bit = 0x03,
31 Reserved(u8),
33}
34
35impl RegionLevelOfCompatibility {
36 #[must_use]
38 pub fn name(&self) -> &'static str {
39 match self {
40 Self::Reserved0 => "reserved",
41 Self::Clut2Bit => "2-bit CLUT",
42 Self::Clut4Bit => "4-bit CLUT",
43 Self::Clut8Bit => "8-bit CLUT",
44 Self::Reserved(_) => "reserved",
45 }
46 }
47
48 fn to_bits(self) -> u8 {
49 match self {
50 Self::Reserved0 => 0x00,
51 Self::Clut2Bit => 0x01,
52 Self::Clut4Bit => 0x02,
53 Self::Clut8Bit => 0x03,
54 Self::Reserved(v) => v & 0x07,
55 }
56 }
57}
58
59broadcast_common::impl_spec_display!(RegionLevelOfCompatibility, Reserved);
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize))]
64#[repr(u8)]
65#[non_exhaustive]
66pub enum RegionDepth {
67 Reserved0 = 0x00,
69 Depth2Bit = 0x01,
71 Depth4Bit = 0x02,
73 Depth8Bit = 0x03,
75 Reserved(u8),
77}
78
79impl RegionDepth {
80 #[must_use]
82 pub fn name(&self) -> &'static str {
83 match self {
84 Self::Reserved0 => "reserved",
85 Self::Depth2Bit => "2-bit",
86 Self::Depth4Bit => "4-bit",
87 Self::Depth8Bit => "8-bit",
88 Self::Reserved(_) => "reserved",
89 }
90 }
91
92 fn to_bits(self) -> u8 {
93 match self {
94 Self::Reserved0 => 0x00,
95 Self::Depth2Bit => 0x01,
96 Self::Depth4Bit => 0x02,
97 Self::Depth8Bit => 0x03,
98 Self::Reserved(v) => v & 0x07,
99 }
100 }
101}
102
103broadcast_common::impl_spec_display!(RegionDepth, Reserved);
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107#[cfg_attr(feature = "serde", derive(serde::Serialize))]
108#[repr(u8)]
109#[non_exhaustive]
110pub enum ObjectType {
111 BasicBitmap = 0x00,
113 BasicCharacter = 0x01,
115 CompositeString = 0x02,
117 Reserved(u8),
119}
120
121impl ObjectType {
122 #[must_use]
124 pub fn name(&self) -> &'static str {
125 match self {
126 Self::BasicBitmap => "basic_bitmap",
127 Self::BasicCharacter => "basic_character",
128 Self::CompositeString => "composite_string",
129 Self::Reserved(_) => "reserved",
130 }
131 }
132
133 fn to_bits(self) -> u8 {
134 match self {
135 Self::BasicBitmap => 0x00,
136 Self::BasicCharacter => 0x01,
137 Self::CompositeString => 0x02,
138 Self::Reserved(v) => v & 0x03,
139 }
140 }
141}
142
143broadcast_common::impl_spec_display!(ObjectType, Reserved);
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147#[cfg_attr(feature = "serde", derive(serde::Serialize))]
148#[repr(u8)]
149#[non_exhaustive]
150pub enum ObjectProviderFlag {
151 InStream = 0x00,
153 InRom = 0x01,
155 Reserved(u8),
157}
158
159impl ObjectProviderFlag {
160 #[must_use]
162 pub fn name(&self) -> &'static str {
163 match self {
164 Self::InStream => "in_stream",
165 Self::InRom => "in_rom",
166 Self::Reserved(_) => "reserved",
167 }
168 }
169
170 fn to_bits(self) -> u8 {
171 match self {
172 Self::InStream => 0x00,
173 Self::InRom => 0x01,
174 Self::Reserved(v) => v & 0x03,
175 }
176 }
177}
178
179broadcast_common::impl_spec_display!(ObjectProviderFlag, Reserved);
180
181#[derive(Debug, Clone, PartialEq, Eq)]
183#[cfg_attr(feature = "serde", derive(serde::Serialize))]
184pub struct RegionObjectEntry {
185 pub object_id: u16,
187 pub object_type: ObjectType,
189 pub object_provider_flag: ObjectProviderFlag,
191 pub object_horizontal_position: u16,
193 pub object_vertical_position: u16,
195 pub foreground_pixel_code: Option<u8>,
197 pub background_pixel_code: Option<u8>,
199}
200
201impl RegionObjectEntry {
202 fn serialized_len(&self) -> usize {
203 if self.foreground_pixel_code.is_some() {
204 OBJECT_ENTRY_BASE_LEN + OBJECT_EXTRA_LEN
205 } else {
206 OBJECT_ENTRY_BASE_LEN
207 }
208 }
209
210 fn serialize_into(&self, buf: &mut [u8]) {
211 buf[0..2].copy_from_slice(&self.object_id.to_be_bytes());
212 let hpos = self.object_horizontal_position;
213 let vpos = self.object_vertical_position;
214 buf[2] = (self.object_type.to_bits() << 6)
215 | (self.object_provider_flag.to_bits() << 4)
216 | ((hpos >> 8) as u8 & 0x0F);
217 buf[3] = hpos as u8;
218 buf[4] = ((vpos >> 8) as u8 & 0x0F) << 4; buf[5] = vpos as u8;
220 if let (Some(fg), Some(bg)) = (self.foreground_pixel_code, self.background_pixel_code) {
221 buf[6] = fg;
222 buf[7] = bg;
223 }
224 }
225}
226
227#[derive(Debug, Clone, PartialEq, Eq)]
229#[cfg_attr(feature = "serde", derive(serde::Serialize))]
230pub struct RegionCompositionSegment {
231 pub page_id: u16,
233 pub region_id: u8,
235 pub region_version_number: u8,
237 pub region_fill_flag: bool,
239 pub reserved_byte1: u8,
241 pub region_width: u16,
243 pub region_height: u16,
245 pub region_level_of_compatibility: RegionLevelOfCompatibility,
247 pub region_depth: RegionDepth,
249 pub reserved_byte6: u8,
251 pub clut_id: u8,
253 pub region_8bit_pixel_code: u8,
255 pub region_4bit_pixel_code: u8,
257 pub region_2bit_pixel_code: u8,
259 pub reserved_byte9: u8,
261 pub objects: alloc::vec::Vec<RegionObjectEntry>,
263}
264
265fn parse_object_entry(bytes: &[u8]) -> Result<(RegionObjectEntry, usize)> {
266 if bytes.len() < OBJECT_ENTRY_BASE_LEN {
267 return Err(Error::BufferTooShort {
268 need: OBJECT_ENTRY_BASE_LEN,
269 have: bytes.len(),
270 what: "region_object_entry",
271 });
272 }
273 let object_id = u16::from_be_bytes([bytes[0], bytes[1]]);
274 let obj_type_val = (bytes[2] >> 6) & 0x03;
275 let obj_provider_val = (bytes[2] >> 4) & 0x03;
276 let obj_hpos = ((u16::from(bytes[2]) & 0x0F) << 8) | u16::from(bytes[3]);
277 let reserved = (bytes[4] >> 4) & 0x0F;
278 let _ = reserved;
280 let obj_vpos = ((u16::from(bytes[4]) & 0x0F) << 8) | u16::from(bytes[5]);
281
282 let obj_type = match obj_type_val {
283 0x00 => ObjectType::BasicBitmap,
284 0x01 => ObjectType::BasicCharacter,
285 0x02 => ObjectType::CompositeString,
286 v => ObjectType::Reserved(v),
287 };
288 let obj_provider = match obj_provider_val {
289 0x00 => ObjectProviderFlag::InStream,
290 0x01 => ObjectProviderFlag::InRom,
291 v => ObjectProviderFlag::Reserved(v),
292 };
293
294 let has_extra = obj_type_val == 0x01 || obj_type_val == 0x02;
295 if has_extra {
296 if bytes.len() < OBJECT_ENTRY_BASE_LEN + OBJECT_EXTRA_LEN {
297 return Err(Error::BufferTooShort {
298 need: OBJECT_ENTRY_BASE_LEN + OBJECT_EXTRA_LEN,
299 have: bytes.len(),
300 what: "region_object_entry extra",
301 });
302 }
303 Ok((
304 RegionObjectEntry {
305 object_id,
306 object_type: obj_type,
307 object_provider_flag: obj_provider,
308 object_horizontal_position: obj_hpos,
309 object_vertical_position: obj_vpos,
310 foreground_pixel_code: Some(bytes[6]),
311 background_pixel_code: Some(bytes[7]),
312 },
313 OBJECT_ENTRY_BASE_LEN + OBJECT_EXTRA_LEN,
314 ))
315 } else {
316 Ok((
317 RegionObjectEntry {
318 object_id,
319 object_type: obj_type,
320 object_provider_flag: obj_provider,
321 object_horizontal_position: obj_hpos,
322 object_vertical_position: obj_vpos,
323 foreground_pixel_code: None,
324 background_pixel_code: None,
325 },
326 OBJECT_ENTRY_BASE_LEN,
327 ))
328 }
329}
330
331impl<'a> Parse<'a> for RegionCompositionSegment {
332 type Error = Error;
333
334 fn parse(bytes: &'a [u8]) -> Result<Self> {
335 if bytes.len() < HEADER_LEN + FIXED_LEN {
336 return Err(Error::BufferTooShort {
337 need: HEADER_LEN + FIXED_LEN,
338 have: bytes.len(),
339 what: "region_composition_segment",
340 });
341 }
342 if bytes[1] != SEGMENT_TYPE {
343 return Err(Error::UnknownSegmentType(bytes[1]));
344 }
345 let page_id = u16::from_be_bytes([bytes[2], bytes[3]]);
346 let segment_length = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
347 let total = HEADER_LEN + segment_length;
348 if bytes.len() < total {
349 return Err(Error::BufferTooShort {
350 need: total,
351 have: bytes.len(),
352 what: "region_composition_segment data",
353 });
354 }
355 let body = &bytes[HEADER_LEN..HEADER_LEN + segment_length];
356 if body.len() < FIXED_LEN {
357 return Err(Error::BufferTooShort {
358 need: FIXED_LEN,
359 have: body.len(),
360 what: "region_composition_segment body",
361 });
362 }
363 let region_id = body[0];
364 let region_version_number = body[1] >> 4;
365 let region_fill_flag = (body[1] & 0x08) != 0;
366 let reserved_byte1 = body[1] & 0x07;
367 let region_width = u16::from_be_bytes([body[2], body[3]]);
368 let region_height = u16::from_be_bytes([body[4], body[5]]);
369 let rloc_val = (body[6] >> 5) & 0x07;
370 let region_level_of_compatibility = match rloc_val {
371 0x00 => RegionLevelOfCompatibility::Reserved0,
372 0x01 => RegionLevelOfCompatibility::Clut2Bit,
373 0x02 => RegionLevelOfCompatibility::Clut4Bit,
374 0x03 => RegionLevelOfCompatibility::Clut8Bit,
375 v => RegionLevelOfCompatibility::Reserved(v),
376 };
377 let rd_val = (body[6] >> 2) & 0x07;
378 let region_depth = match rd_val {
379 0x00 => RegionDepth::Reserved0,
380 0x01 => RegionDepth::Depth2Bit,
381 0x02 => RegionDepth::Depth4Bit,
382 0x03 => RegionDepth::Depth8Bit,
383 v => RegionDepth::Reserved(v),
384 };
385 let reserved_byte6 = body[6] & 0x03;
386 let clut_id = body[7];
387 let region_8bit_pixel_code = body[8];
388 let region_4bit_pixel_code = body[9] >> 4;
389 let region_2bit_pixel_code = (body[9] >> 2) & 0x03;
390 let reserved_byte9 = body[9] & 0x03;
391
392 let obj_data = &body[FIXED_LEN..];
393 let mut objects = alloc::vec::Vec::new();
394 let mut pos: usize = 0;
395 while pos < obj_data.len() {
396 let (entry, entry_len) = parse_object_entry(&obj_data[pos..])?;
397 objects.push(entry);
398 pos += entry_len;
399 }
400
401 Ok(RegionCompositionSegment {
402 page_id,
403 region_id,
404 region_version_number,
405 region_fill_flag,
406 reserved_byte1,
407 region_width,
408 region_height,
409 region_level_of_compatibility,
410 region_depth,
411 reserved_byte6,
412 clut_id,
413 region_8bit_pixel_code,
414 region_4bit_pixel_code,
415 region_2bit_pixel_code,
416 reserved_byte9,
417 objects,
418 })
419 }
420}
421
422impl Serialize for RegionCompositionSegment {
423 type Error = Error;
424
425 fn serialized_len(&self) -> usize {
426 HEADER_LEN
427 + FIXED_LEN
428 + self
429 .objects
430 .iter()
431 .map(|o| o.serialized_len())
432 .sum::<usize>()
433 }
434
435 fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
436 let len = self.serialized_len();
437 if buf.len() < len {
438 return Err(Error::BufferTooShort {
439 need: len,
440 have: buf.len(),
441 what: "region_composition_segment serialize",
442 });
443 }
444 buf[0] = 0x0F;
445 buf[1] = SEGMENT_TYPE;
446 buf[2..4].copy_from_slice(&self.page_id.to_be_bytes());
447 let seg_len = (len - HEADER_LEN) as u16;
448 buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
449
450 buf[6] = self.region_id;
451 buf[7] = (self.region_version_number << 4)
452 | (u8::from(self.region_fill_flag) << 3)
453 | (self.reserved_byte1 & 0x07);
454 buf[8..10].copy_from_slice(&self.region_width.to_be_bytes());
455 buf[10..12].copy_from_slice(&self.region_height.to_be_bytes());
456 buf[12] = (self.region_level_of_compatibility.to_bits() << 5)
457 | (self.region_depth.to_bits() << 2)
458 | (self.reserved_byte6 & 0x03);
459 buf[13] = self.clut_id;
460 buf[14] = self.region_8bit_pixel_code;
461 buf[15] = (self.region_4bit_pixel_code << 4)
462 | (self.region_2bit_pixel_code << 2)
463 | (self.reserved_byte9 & 0x03);
464
465 let mut off = HEADER_LEN + FIXED_LEN;
466 for obj in &self.objects {
467 obj.serialize_into(&mut buf[off..]);
468 off += obj.serialized_len();
469 }
470 Ok(len)
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477 use broadcast_common::{Parse, Serialize};
478
479 #[test]
480 fn round_trip_bitmap_objects() {
481 let bytes = [
482 0x0F, 0x11, 0x00, 0x01, 0x00, 0x16, 0x01, 0x88, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
483 0x00, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x02, 0x10, 0x14, 0x00,
484 0x14, ];
486 let seg = RegionCompositionSegment::parse(&bytes).unwrap();
487 assert_eq!(seg.region_id, 1);
488 assert!(seg.region_fill_flag);
489 assert_eq!(seg.region_width, 719);
490 assert_eq!(seg.objects.len(), 2);
491 assert_eq!(seg.objects[0].object_id, 1);
492 assert_eq!(seg.objects[0].object_type, ObjectType::BasicBitmap);
493 assert_eq!(seg.objects[0].object_horizontal_position, 10);
494 assert_eq!(seg.objects[1].object_id, 2);
495 assert_eq!(
496 seg.objects[1].object_provider_flag,
497 ObjectProviderFlag::InRom
498 );
499 let out = seg.to_bytes();
500 assert_eq!(out, bytes);
501
502 let mut seg2 = seg.clone();
504 seg2.region_width = 1280;
505 let out2 = seg2.to_bytes();
506 assert_ne!(out2, bytes);
507 let reparse = RegionCompositionSegment::parse(&out2).unwrap();
508 assert_eq!(reparse.region_width, 1280);
509 }
510
511 #[test]
512 fn round_trip_character_object() {
513 let bytes = [
514 0x0F, 0x11, 0x00, 0x01, 0x00, 0x12, 0x01, 0x80, 0x02, 0xCF, 0x00, 0x8F, 0x18, 0x03,
515 0x00, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x00, 0x1E, 0xAA, 0xBB,
516 ];
517 let seg = RegionCompositionSegment::parse(&bytes).unwrap();
518 assert_eq!(seg.objects.len(), 1);
519 assert_eq!(seg.objects[0].object_type, ObjectType::BasicCharacter);
520 assert_eq!(seg.objects[0].foreground_pixel_code, Some(0xAA));
521 assert_eq!(seg.objects[0].background_pixel_code, Some(0xBB));
522 let out = seg.to_bytes();
523 assert_eq!(out, bytes);
524
525 let mut seg2 = seg.clone();
527 seg2.objects[0].foreground_pixel_code = Some(0xCC);
528 let out2 = seg2.to_bytes();
529 assert_ne!(out2, bytes);
530 let reparse = RegionCompositionSegment::parse(&out2).unwrap();
531 assert_eq!(reparse.objects[0].foreground_pixel_code, Some(0xCC));
532 }
533}