oxitext_layout/
linebreak.rs1use unicode_linebreak::{linebreaks, BreakOpportunity};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum LineBreak {
11 Mandatory,
13 Allowed,
15}
16
17pub struct LineBreaker {
27 breaks: Vec<(usize, LineBreak)>,
28}
29
30impl LineBreaker {
31 pub fn new(text: &str) -> Self {
33 let breaks = linebreaks(text)
34 .map(|(pos, opp)| {
35 let lb = match opp {
36 BreakOpportunity::Mandatory => LineBreak::Mandatory,
37 BreakOpportunity::Allowed => LineBreak::Allowed,
38 };
39 (pos, lb)
40 })
41 .collect();
42 Self { breaks }
43 }
44
45 pub fn breaks(&self) -> &[(usize, LineBreak)] {
47 &self.breaks
48 }
49
50 pub fn iter(&self) -> impl Iterator<Item = &(usize, LineBreak)> {
52 self.breaks.iter()
53 }
54}
55
56impl IntoIterator for LineBreaker {
57 type Item = (usize, LineBreak);
58 type IntoIter = std::vec::IntoIter<(usize, LineBreak)>;
59
60 fn into_iter(self) -> Self::IntoIter {
61 self.breaks.into_iter()
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn space_allows_break() {
71 let lb = LineBreaker::new("hello world");
72 let breaks: Vec<_> = lb.iter().cloned().collect();
73 assert!(
74 !breaks.is_empty(),
75 "should have at least one break opportunity"
76 );
77 }
78
79 #[test]
80 fn newline_is_mandatory() {
81 let lb = LineBreaker::new("hello\nworld");
82 let mandatory = lb.iter().any(|(_, kind)| *kind == LineBreak::Mandatory);
83 assert!(mandatory, "newline should produce a mandatory break");
84 }
85}