use std::fmt;
use crate::text_field::{self, Presentation};
use crate::{
CharacterCodeTable, DiskFormatCode, InvalidFieldValue, Justification, ParseError, TextElement,
Time, fields,
};
pub struct TtiFormat {
#[doc = "Justification Code (JC), 00h-03h"]
pub jc: u8,
#[doc = "Vertical Position (VP), the row of the first subtitle row"]
pub vp: u8,
#[doc = "Double height, only used for teletext subtitles"]
pub dh: bool,
}
#[derive(Debug, PartialEq, Eq)]
pub enum CumulativeStatus {
NotPartOfASet,
FirstInSet,
IntermediateInSet,
LastInSet,
}
impl CumulativeStatus {
pub(crate) fn parse(d: u8) -> Result<CumulativeStatus, ParseError> {
match d {
0 => Ok(CumulativeStatus::NotPartOfASet),
1 => Ok(CumulativeStatus::FirstInSet),
2 => Ok(CumulativeStatus::IntermediateInSet),
3 => Ok(CumulativeStatus::LastInSet),
_ => Err(ParseError::CumulativeStatus),
}
}
pub(crate) fn serialize(&self) -> u8 {
match *self {
CumulativeStatus::NotPartOfASet => 0,
CumulativeStatus::FirstInSet => 1,
CumulativeStatus::IntermediateInSet => 2,
CumulativeStatus::LastInSet => 3,
}
}
}
#[derive(PartialEq, Eq)]
pub struct TtiBlock {
#[doc = "0 Subtitle Group Number. 00h-FFh"]
pub(crate) sgn: u8,
#[doc = "1..2 Subtitle Number. 0000h-FFFFh"]
pub(crate) sn: u16,
#[doc = "3 Extension Block Number. 00h-FFh"]
pub(crate) ebn: u8,
#[doc = "4 Cumulative Status. 00-03h"]
pub(crate) cs: CumulativeStatus,
#[doc = "5..8 Time Code In"]
pub(crate) tci: Time,
#[doc = "9..12 Time Code Out"]
pub(crate) tco: Time,
#[doc = "13 Vertical Position"]
pub(crate) vp: u8,
#[doc = "14 Justification Code"]
pub(crate) jc: u8,
#[doc = "15 Comment Flag"]
pub(crate) cf: u8,
#[doc = "16..127 Text Field, encoded with the Character Code Table"]
pub(crate) tf: Vec<u8>,
#[doc = "Copy of the Character Code Table of the GSI block, to decode the Text Field"]
pub(crate) cct: CharacterCodeTable,
}
impl TtiBlock {
pub fn subtitle_group_number(&self) -> u8 {
self.sgn
}
pub fn subtitle_number(&self) -> u16 {
self.sn
}
pub fn extension_block_number(&self) -> u8 {
self.ebn
}
pub fn cumulative_status(&self) -> &CumulativeStatus {
&self.cs
}
pub fn time_code_in(&self, dfc: &DiskFormatCode) -> Result<Time, InvalidFieldValue<Time>> {
fields::checked_time_code("TCI", &self.tci, dfc)
}
pub fn time_code_out(&self, dfc: &DiskFormatCode) -> Result<Time, InvalidFieldValue<Time>> {
fields::checked_time_code("TCO", &self.tco, dfc)
}
pub fn vertical_position(&self) -> u8 {
self.vp
}
pub fn justification(&self) -> Result<Justification, InvalidFieldValue<u8>> {
match self.jc {
0x00 => Ok(Justification::Unchanged),
0x01 => Ok(Justification::Left),
0x02 => Ok(Justification::Centered),
0x03 => Ok(Justification::Right),
jc => Err(InvalidFieldValue::new(
"JC",
format!("{jc:02X}h is not 00h-03h"),
jc,
)),
}
}
pub fn comment_flag(&self) -> Result<bool, InvalidFieldValue<u8>> {
match self.cf {
0x00 => Ok(false),
0x01 => Ok(true),
cf => Err(InvalidFieldValue::new(
"CF",
format!("{cf:02X}h is not 00h or 01h"),
cf,
)),
}
}
}
impl TtiBlock {
pub fn new(
idx: u16,
tci: Time,
tco: Time,
txt: &str,
opt: TtiFormat,
cct: CharacterCodeTable,
) -> TtiBlock {
let presentation = Presentation::Teletext {
double_height: opt.dh,
};
TtiBlock {
sgn: 0,
sn: idx,
ebn: EBN_LAST_BLOCK,
cs: CumulativeStatus::NotPartOfASet,
tci,
tco,
vp: opt.vp,
jc: opt.jc,
cf: 0,
tf: text_field::encode_truncated_text_field(txt, presentation, cct),
cct, }
}
pub fn text(&self) -> String {
text_field::decode(&self.tf, self.cct)
}
pub fn text_elements(&self) -> Vec<TextElement> {
text_field::decode_elements(&self.tf, self.cct)
}
pub(crate) fn text_field_content(&self) -> &[u8] {
text_field::content(&self.tf)
}
pub fn is_user_data(&self) -> bool {
self.ebn == EBN_USER_DATA
}
pub fn is_text_block(&self) -> bool {
self.ebn <= EBN_LAST_EXTENSION || self.ebn == EBN_LAST_BLOCK
}
pub fn is_last_block(&self) -> bool {
self.ebn == EBN_LAST_BLOCK
}
pub fn is_comment(&self) -> bool {
self.cf == CF_COMMENT
}
#[allow(clippy::vec_init_then_push)]
pub(crate) fn serialize(&self) -> Vec<u8> {
let mut res = vec![];
res.push(self.sgn);
res.push((self.sn & 0xff) as u8);
res.push((self.sn >> 8) as u8);
res.push(self.ebn);
res.push(self.cs.serialize());
res.extend(self.tci.serialize().iter().cloned());
res.extend(self.tco.serialize().iter().cloned());
res.push(self.vp);
res.push(self.jc);
res.push(self.cf);
res.extend(self.tf.iter().cloned());
res
}
}
const EBN_LAST_EXTENSION: u8 = 0xEF;
pub(crate) const MAX_TEXT_BLOCKS: usize = EBN_LAST_EXTENSION as usize + 2;
const EBN_USER_DATA: u8 = 0xFE;
pub(crate) const EBN_LAST_BLOCK: u8 = 0xFF;
const CF_COMMENT: u8 = 0x01;
impl fmt::Debug for TtiBlock {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\n{}-->{} sgn:{} sn:{} ebn:{} cs:{:?} vp:{} jc:{} cf:{} [{}]",
self.tci,
self.tco,
self.sgn,
self.sn,
self.ebn,
self.cs,
self.vp,
self.jc,
self.cf,
self.text()
)
}
}
impl fmt::Display for TtiBlock {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\n{} {} {} {} {:?} [{}]",
self.tci,
self.sgn,
self.sn,
self.ebn,
self.cs,
self.text()
)
}
}