use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WrapOpportunity {
pub offset: usize,
pub mandatory: bool,
}
#[must_use]
pub fn wrap_opportunities(text: &str) -> Vec<WrapOpportunity> {
unicode_linebreak::linebreaks(text)
.map(|(offset, opportunity)| WrapOpportunity {
offset,
mandatory: matches!(opportunity, unicode_linebreak::BreakOpportunity::Mandatory),
})
.collect()
}
#[must_use]
pub fn soft_wrap_offsets(text: &str) -> Vec<usize> {
wrap_opportunities(text)
.into_iter()
.filter(|opportunity| !opportunity.mandatory)
.map(|opportunity| opportunity.offset)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn breaks_after_space() {
let offsets = soft_wrap_offsets("Hello world");
assert_eq!(offsets, alloc::vec![6]);
}
#[test]
fn multiple_words() {
let offsets = soft_wrap_offsets("one two three");
assert_eq!(offsets, alloc::vec![4, 8]);
}
#[test]
fn breaks_between_cjk_without_spaces() {
let text = "日本語字幕";
let offsets = soft_wrap_offsets(text);
assert!(!offsets.is_empty());
assert!(offsets.iter().all(|&o| text.is_char_boundary(o)));
}
#[test]
fn no_soft_break_in_single_token() {
assert!(soft_wrap_offsets("indivisible").is_empty());
}
#[test]
fn final_break_is_mandatory() {
let all = wrap_opportunities("hi there");
let last = all.last().expect("at least one opportunity");
assert_eq!(last.offset, "hi there".len());
assert!(last.mandatory);
}
}