glyph_brush_layout/
linebreak.rs1use std::{
2 fmt,
3 hash::Hash,
4 iter::FusedIterator,
5 str::{self, CharIndices},
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum LineBreak {
12 Soft(usize),
14 Hard(usize),
16}
17
18impl LineBreak {
19 #[inline]
21 pub fn offset(&self) -> usize {
22 match *self {
23 LineBreak::Soft(offset) | LineBreak::Hard(offset) => offset,
24 }
25 }
26}
27
28pub trait LineBreaker: fmt::Debug + Copy + Hash {
31 fn line_breaks<'a>(&self, glyph_info: &'a str) -> Box<dyn Iterator<Item = LineBreak> + 'a>;
32}
33
34#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
36pub enum BuiltInLineBreaker {
37 #[default]
40 UnicodeLineBreaker,
41 AnyCharLineBreaker,
44}
45
46struct AnyCharLineBreakerIter<'a> {
48 chars: CharIndices<'a>,
49 breaks: xi_unicode::LineBreakIterator<'a>,
50 current_break: Option<(usize, bool)>,
51}
52
53impl Iterator for AnyCharLineBreakerIter<'_> {
54 type Item = LineBreak;
55
56 #[inline]
57 fn next(&mut self) -> Option<LineBreak> {
58 let (b_index, c) = self.chars.next()?;
59 let c_len = c.len_utf8();
60 while self.current_break.is_some() {
61 if self.current_break.as_ref().unwrap().0 < b_index + c_len {
62 self.current_break = self.breaks.next();
63 } else {
64 break;
65 }
66 }
67 if let Some((break_index, true)) = self.current_break {
68 if break_index == b_index + c_len {
69 return Some(LineBreak::Hard(break_index));
70 }
71 }
72 Some(LineBreak::Soft(b_index + c_len))
73 }
74}
75
76impl FusedIterator for AnyCharLineBreakerIter<'_> {}
77
78impl LineBreaker for BuiltInLineBreaker {
79 #[inline]
80 fn line_breaks<'a>(&self, text: &'a str) -> Box<dyn Iterator<Item = LineBreak> + 'a> {
81 match *self {
82 BuiltInLineBreaker::UnicodeLineBreaker => Box::new(
83 xi_unicode::LineBreakIterator::new(text).map(|(offset, hard)| {
84 if hard {
85 LineBreak::Hard(offset)
86 } else {
87 LineBreak::Soft(offset)
88 }
89 }),
90 ),
91 BuiltInLineBreaker::AnyCharLineBreaker => {
92 let mut unicode_breaker = xi_unicode::LineBreakIterator::new(text);
93 let current_break = unicode_breaker.next();
94
95 Box::new(AnyCharLineBreakerIter {
96 chars: text.char_indices(),
97 breaks: unicode_breaker,
98 current_break,
99 })
100 }
101 }
102 }
103}
104
105pub(crate) trait EolLineBreak<B: LineBreaker> {
109 fn eol_line_break(&self, line_breaker: &B) -> Option<LineBreak>;
110}
111
112impl<B: LineBreaker> EolLineBreak<B> for char {
113 #[inline]
114 fn eol_line_break(&self, line_breaker: &B) -> Option<LineBreak> {
115 let mut last_end_bytes: [u8; 5] = [b' '; 5];
118 self.encode_utf8(&mut last_end_bytes);
119 let len_utf8 = self.len_utf8();
120 if let Ok(last_end_padded) = str::from_utf8(&last_end_bytes[0..=len_utf8]) {
121 match line_breaker.line_breaks(last_end_padded).next() {
122 l @ Some(LineBreak::Soft(1)) | l @ Some(LineBreak::Hard(1)) => return l,
123 _ => {}
124 }
125 }
126
127 last_end_bytes[len_utf8] = b'a';
129 if let Ok(last_end_padded) = str::from_utf8(&last_end_bytes[0..=len_utf8]) {
130 match line_breaker.line_breaks(last_end_padded).next() {
131 l @ Some(LineBreak::Soft(1)) | l @ Some(LineBreak::Hard(1)) => return l,
132 _ => {}
133 }
134 }
135
136 None
137 }
138}
139
140#[cfg(test)]
141mod eol_line_break {
142 use super::*;
143
144 #[test]
145 fn hard_break_char() {
146 assert_eq!(
147 '\n'.eol_line_break(&BuiltInLineBreaker::default()),
148 Some(LineBreak::Hard(1))
149 );
150 }
151
152 #[test]
153 fn soft_break_char() {
154 assert_eq!(
155 ' '.eol_line_break(&BuiltInLineBreaker::default()),
156 Some(LineBreak::Soft(1))
157 );
158 }
159}