tttocea608: implement word wrapping in roll-up modes.

In roll-up modes, we open new lines when the last column is reached.
This commit implements lookahead on a word basis, in order to avoid
splitting words unless absolutely necessary (when a word won't fit
on a full row)
This commit is contained in:
Mathieu Duponchelle 2021-04-16 22:16:39 +02:00 committed by Sebastian Dröge
parent 95cdd43f4f
commit 7923e26545
2 changed files with 106 additions and 30 deletions

View file

@ -540,6 +540,10 @@ impl TtToCea608 {
ret ret
} }
fn peek_word_length(&self, chars: std::iter::Peekable<std::str::Chars>) -> u32 {
chars.take_while(|c| !c.is_ascii_whitespace()).count() as u32
}
fn generate( fn generate(
&self, &self,
mut state: &mut State, mut state: &mut State,
@ -548,6 +552,7 @@ impl TtToCea608 {
duration: gst::ClockTime, duration: gst::ClockTime,
lines: Lines, lines: Lines,
) -> Result<gst::FlowSuccess, gst::FlowError> { ) -> Result<gst::FlowSuccess, gst::FlowError> {
let origin_column = self.settings.lock().unwrap().origin_column;
let mut row = 13; let mut row = 13;
let mut bufferlist = gst::BufferList::new(); let mut bufferlist = gst::BufferList::new();
let mut_list = bufferlist.get_mut().unwrap(); let mut_list = bufferlist.get_mut().unwrap();
@ -585,7 +590,7 @@ impl TtToCea608 {
Cea608Mode::RollUp2 | Cea608Mode::RollUp3 | Cea608Mode::RollUp4 => { Cea608Mode::RollUp2 | Cea608Mode::RollUp3 | Cea608Mode::RollUp4 => {
state.send_roll_up_preamble = true; state.send_roll_up_preamble = true;
} }
_ => col = self.settings.lock().unwrap().origin_column, _ => col = origin_column,
} }
} }
} }
@ -597,7 +602,7 @@ impl TtToCea608 {
if state.mode != Cea608Mode::PopOn && state.mode != Cea608Mode::PaintOn { if state.mode != Cea608Mode::PopOn && state.mode != Cea608Mode::PaintOn {
state.send_roll_up_preamble = true; state.send_roll_up_preamble = true;
} }
col = self.settings.lock().unwrap().origin_column; col = origin_column;
} }
} }
@ -637,7 +642,7 @@ impl TtToCea608 {
} }
col = line_column; col = line_column;
} else if state.mode == Cea608Mode::PopOn || state.mode == Cea608Mode::PaintOn { } else if state.mode == Cea608Mode::PopOn || state.mode == Cea608Mode::PaintOn {
col = self.settings.lock().unwrap().origin_column; col = origin_column;
} }
for (j, chunk) in line.chunks.iter().enumerate() { for (j, chunk) in line.chunks.iter().enumerate() {
@ -722,36 +727,48 @@ impl TtToCea608 {
col += 1; col += 1;
if col > 31 { if state.mode.is_rollup() {
match state.mode { /* In roll-up mode, we introduce carriage returns automatically.
Cea608Mode::PaintOn | Cea608Mode::PopOn => { * Instead of always wrapping once the last column is reached, we
if chars.peek().is_some() { * want to look ahead and check whether the following word will fit
gst_warning!( * on the current row. If it won't, we insert a carriage return,
CAT, * unless it won't fit on a full row either, in which case it will need
obj: element, * to be broken up.
"Dropping characters after 32nd column: {}", */
c let next_word_length = if c.is_ascii_whitespace() {
); self.peek_word_length(chars.clone())
} } else {
break; 0
} };
Cea608Mode::RollUp2 | Cea608Mode::RollUp3 | Cea608Mode::RollUp4 => {
if prev_char != 0 {
state.cc_data(element, mut_list, prev_char);
prev_char = 0;
}
self.open_line( if (next_word_length <= 32 - origin_column && col + next_word_length > 31)
element, || col > 31
&mut state, {
chunk, if prev_char != 0 {
mut_list, state.cc_data(element, mut_list, prev_char);
&mut col, prev_char = 0;
row as i32,
Some(true),
);
} }
self.open_line(
element,
&mut state,
chunk,
mut_list,
&mut col,
row as i32,
Some(true),
);
} }
} else if col > 31 {
if chars.peek().is_some() {
gst_warning!(
CAT,
obj: element,
"Dropping characters after 32nd column: {}",
c
);
}
break;
} }
} }
} }

View file

@ -371,3 +371,62 @@ fn test_one_timed_buffer_and_eos_roll_up2() {
let event = h.pull_event().unwrap(); let event = h.pull_event().unwrap();
assert_eq!(event.type_(), gst::EventType::Eos); assert_eq!(event.type_(), gst::EventType::Eos);
} }
/* Here we test that tttocea608 introduces carriage returns in
* judicious places and avoids to break words without rhyme or
* reason.
*/
#[test]
fn test_word_wrap_roll_up() {
init();
let mut h = gst_check::Harness::new_parse("tttocea608 mode=roll-up2 origin-column=24");
h.set_src_caps_str("text/x-raw");
while h.events_in_queue() != 0 {
let _event = h.pull_event().unwrap();
}
let inbuf = new_timed_buffer(&"Hello World", gst::SECOND, gst::SECOND);
assert_eq!(h.push(inbuf), Ok(gst::FlowSuccess::Ok));
/* Padding */
loop {
let outbuf = h.pull().unwrap();
if outbuf.pts() + outbuf.duration() >= gst::SECOND {
break;
}
let data = outbuf.map_readable().unwrap();
assert_eq!(&*data, &[0x80, 0x80]);
}
let expected: [(gst::ClockTime, gst::ClockTime, [u8; 2usize]); 11] = [
(1_000_000_000.into(), 33_333_333.into(), [0x94, 0x25]), /* roll_up_2 */
(1_033_333_333.into(), 33_333_334.into(), [0x94, 0x7c]), /* preamble */
(1_066_666_667.into(), 33_333_333.into(), [0xc8, 0xe5]), /* H e */
(1_100_000_000.into(), 33_333_333.into(), [0xec, 0xec]), /* l l */
(1_133_333_333.into(), 33_333_334.into(), [0xef, 0x20]), /* o SPACE */
(1_166_666_667.into(), 33_333_333.into(), [0x94, 0xad]), /* carriage return */
(1_200_000_000.into(), 33_333_333.into(), [0x94, 0x25]), /* roll_up_2 */
(1_233_333_333.into(), 33_333_334.into(), [0x94, 0x7c]), /* preamble */
(1_266_666_667.into(), 33_333_333.into(), [0x57, 0xef]), /* W o */
(1_300_000_000.into(), 33_333_333.into(), [0xf2, 0xec]), /* r l */
(1_333_333_333.into(), 33_333_334.into(), [0x64, 0x80]), /* d nil */
];
for (i, e) in expected.iter().enumerate() {
let outbuf = h.try_pull().unwrap();
assert_eq!(e.0, outbuf.pts(), "Unexpected PTS for {}th buffer", i + 1);
assert_eq!(
e.1,
outbuf.duration(),
"Unexpected duration for {}th buffer",
i + 1
);
let data = outbuf.map_readable().unwrap();
assert_eq!(e.2, &*data);
}
}