1use std::ops::{AddAssign, Deref};
2
3use anathema_geometry::Size;
4use anathema_store::tree::ValueId;
5use anathema_value_resolver::ValueKind;
6use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
7
8use crate::WidgetId;
9
10#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
12pub enum Wrap {
13 #[default]
16 Normal,
17 WordBreak,
19}
20
21impl Wrap {
22 pub fn is_word_wrap(&self) -> bool {
24 matches!(self, Self::Normal)
25 }
26}
27
28impl TryFrom<&ValueKind<'_>> for Wrap {
29 type Error = ();
30
31 fn try_from(value: &ValueKind<'_>) -> Result<Self, Self::Error> {
32 let s = value.as_str().ok_or(())?;
33 match s {
34 "normal" => Ok(Wrap::Normal),
35 "break" => Ok(Wrap::WordBreak),
36 _ => Err(()),
37 }
38 }
39}
40
41impl From<Wrap> for ValueKind<'_> {
42 fn from(value: Wrap) -> Self {
43 let value = match value {
44 Wrap::Normal => "normal",
45 Wrap::WordBreak => "break",
46 };
47
48 ValueKind::Str(value.into())
49 }
50}
51
52#[derive(Debug)]
53pub(crate) struct LineWidth(usize);
54
55impl LineWidth {
56 pub(crate) const ZERO: Self = Self(0);
57
58 pub(crate) fn swap(&mut self, mut new_value: usize) -> u16 {
60 std::mem::swap(&mut self.0, &mut new_value);
61 new_value as u16
62 }
63}
64
65impl Default for LineWidth {
66 fn default() -> Self {
67 LineWidth::ZERO
68 }
69}
70
71impl Deref for LineWidth {
72 type Target = usize;
73
74 fn deref(&self) -> &Self::Target {
75 &self.0
76 }
77}
78
79impl AddAssign<usize> for LineWidth {
80 fn add_assign(&mut self, rhs: usize) {
81 self.0 += rhs;
82 }
83}
84
85#[derive(Debug, Copy, Clone, PartialEq)]
88pub enum ProcessResult {
89 Continue,
91 Break,
94}
95
96#[derive(Debug, Copy, Clone)]
97pub(crate) enum Entry {
98 Newline,
99 LineWidth(u16),
100 Style(ValueId),
101}
102
103#[derive(Debug)]
105pub struct Line<I> {
106 pub width: u16,
107 pub entries: I,
108}
109
110#[derive(Debug)]
112pub enum Segment<'a> {
113 SetStyle(ValueId),
115 Str(&'a str),
117}
118
119#[derive(Debug, Copy, Clone, PartialEq)]
120pub(crate) enum LineEntry {
121 Width(u16),
122 Str(u32, u32),
123 SetStyle(ValueId),
124 Newline,
125}
126
127#[derive(Debug)]
141pub struct Strings {
142 layout: Vec<(u32, Entry)>,
143 lines: Vec<LineEntry>,
144 chomper: Chomper,
145 frozen: bool,
146 bytes: Vec<u8>,
147 max: Size,
148 size: Size,
149 wrap: Wrap,
150 line: usize,
152 current_width: LineWidth,
153}
154
155impl Strings {
156 pub fn new(max: Size, wrap: Wrap) -> Self {
157 Self {
158 layout: vec![],
159 lines: vec![],
160 chomper: Chomper::Continuous(0),
161 frozen: false,
162 bytes: vec![],
163 max,
164 wrap,
165 size: Size::new(0, 1),
166 line: 0,
167 current_width: LineWidth::ZERO,
168 }
169 }
170
171 pub fn add_str(&mut self, s: &str) -> ProcessResult {
173 if self.max.height == 0 || self.max.width == 0 {
174 return ProcessResult::Break;
175 }
176
177 if self.frozen {
178 return ProcessResult::Break;
179 }
180
181 for word in s.split_inclusive(char::is_whitespace) {
182 self.bytes.extend(word.bytes());
183 for c in word.chars() {
184 if let res @ ProcessResult::Break = self.chomp(c) {
185 self.bytes.truncate(self.chomper.index());
186 self.freeze();
187 return res;
188 }
189 }
190 }
191
192 ProcessResult::Continue
193 }
194
195 pub fn lines(&self) -> impl Iterator<Item = Line<impl Iterator<Item = Segment<'_>>>> {
198 let lines = self.lines.split(|e| *e == LineEntry::Newline);
199
200 lines.map(|entries| {
201 let LineEntry::Width(width) = entries[0] else { unreachable!() };
202
203 Line {
204 width,
205 entries: entries[1..].iter().map(|e| match e {
206 LineEntry::Str(from, to) => Segment::Str(
207 std::str::from_utf8(&self.bytes[*from as usize..*to as usize])
208 .expect("only strings written to the byte store"),
209 ),
210 LineEntry::SetStyle(style) => Segment::SetStyle(*style),
211 LineEntry::Width(_) | LineEntry::Newline => unreachable!("consumed already"),
212 }),
213 }
214 })
215 }
216
217 pub fn set_style(&mut self, style: WidgetId) {
218 let index = self.bytes.len();
219 self.layout.push((index as u32, Entry::Style(style)));
220 }
221
222 pub fn finish(&mut self) -> Size {
224 self.frozen = true;
225 self.layout.sort_by(|a, b| a.0.cmp(&b.0));
226
227 let last_line = self.line(self.bytes.len());
228 let last_line_width = last_line.width();
229 self.layout
230 .push((self.bytes.len() as u32, Entry::LineWidth(last_line_width as u16)));
231
232 let mut from = 0;
234 for line in self.layout.split(|e| matches!(e.1, Entry::Newline)) {
235 let width = match line.last() {
237 Some((_, Entry::LineWidth(w))) => *w,
238 _ => unreachable!("the last entry is always the line width"),
239 };
240
241 self.lines.push(LineEntry::Width(width));
242
243 for (i, entry) in line {
244 if from != *i {
246 self.lines.push(LineEntry::Str(from, *i));
247 }
248
249 from = *i;
250
251 match entry {
252 Entry::Style(style) => self.lines.push(LineEntry::SetStyle(*style)),
253 Entry::LineWidth(_) => {}
254 Entry::Newline => unreachable!("consumed by the split"),
255 }
256 }
257 self.lines.push(LineEntry::Newline);
258 }
259
260 self.lines.pop();
261
262 self.update_width();
263 if self.size.width == 0 {
264 self.size = Size::ZERO;
265 }
266 self.size
267 }
268
269 fn freeze(&mut self) {
270 self.frozen = true;
271 }
272
273 fn newline(&mut self) {
274 self.size.height += 1;
275 self.update_width();
276 self.line = match self.chomper {
277 Chomper::Continuous(idx) => {
278 self.layout
279 .push((idx as u32, Entry::LineWidth(self.current_width.swap(0))));
280 self.layout.push((idx as u32, Entry::Newline));
281 idx
282 }
283 Chomper::WordBoundary {
284 word_boundary,
285 current_index,
286 } => {
287 let diff = self.line(current_index).width() - self.line(word_boundary).width();
288 let width = *self.current_width - diff;
289 self.layout.push((word_boundary as u32, Entry::LineWidth(width as u16)));
290 self.layout.push((word_boundary as u32, Entry::Newline));
291 let _ = self.current_width.swap(diff);
292 self.chomper = Chomper::Continuous(current_index);
293 word_boundary
294 }
295 };
296 }
297
298 fn line(&self, index: usize) -> &str {
299 self.str(self.line, index)
300 }
301
302 fn str(&self, offset: usize, index: usize) -> &str {
303 std::str::from_utf8(&self.bytes[offset..index]).expect("only valid strings here")
304 }
305
306 fn update_width(&mut self) {
307 self.size.width = self.size.width.max(*self.current_width as u16);
308 }
309
310 fn chomp(&mut self, c: char) -> ProcessResult {
311 let width = c.width().unwrap_or(0) as u16;
312
313 if width > self.max.width {
317 for _ in 0..c.len_utf8() {
318 self.bytes.pop();
319 }
320 return ProcessResult::Continue;
321 }
322
323 if c == '\n' {
326 self.bytes.pop();
327
328 if self.size.height >= self.max.height {
329 return ProcessResult::Break;
330 }
331
332 self.chomper.force_word_boundary();
333 self.newline();
334 return ProcessResult::Continue;
335 }
336
337 while width + *self.current_width as u16 > self.max.width {
340 if c.is_whitespace() {
341 for _ in 0..c.len_utf8() {
346 self.bytes.pop();
347 }
348
349 self.chomper.force_word_boundary();
350 self.newline();
351
352 return ProcessResult::Continue;
353 }
354
355 if self.size.height >= self.max.height {
356 return ProcessResult::Break;
357 }
358
359 self.newline();
360 }
361
362 self.chomper.chomp(c, self.wrap);
363 self.current_width += width as usize;
364
365 ProcessResult::Continue
366 }
367}
368
369impl Default for Strings {
370 fn default() -> Self {
371 Self::new(Size::ZERO, Wrap::default())
372 }
373}
374
375#[derive(Debug)]
377pub(crate) enum Chomper {
378 Continuous(usize),
379 WordBoundary { word_boundary: usize, current_index: usize },
380}
381
382impl Chomper {
383 pub(crate) fn index(&self) -> usize {
384 match self {
385 Chomper::Continuous(current_index) | Chomper::WordBoundary { current_index, .. } => *current_index,
386 }
387 }
388
389 pub(crate) fn force_word_boundary(&mut self) {
390 if let Chomper::WordBoundary {
391 word_boundary,
392 current_index,
393 } = self
394 {
395 *word_boundary = *current_index;
396 }
397 }
398
399 pub(crate) fn chomp(&mut self, c: char, wrap: Wrap) {
400 let c_len = c.len_utf8();
401
402 if c.is_whitespace() && wrap.is_word_wrap() {
403 match self {
404 Chomper::Continuous(idx) | Chomper::WordBoundary { current_index: idx, .. } => {
405 let new_index = *idx + c_len;
406 *self = Self::WordBoundary {
407 word_boundary: new_index,
408 current_index: new_index,
409 };
410 return;
411 }
412 }
413 }
414
415 match self {
416 Self::Continuous(idx) => *idx += c_len,
417 Self::WordBoundary { current_index, .. } => *current_index += c_len,
418 }
419 }
420}
421
422impl Default for Chomper {
423 fn default() -> Self {
424 Chomper::Continuous(0)
425 }
426}
427
428#[cfg(test)]
429mod test {
430 use super::*;
431
432 fn test_layout(max: Size, input: &[&str], expected: &str, wrap: Wrap) {
433 let mut strings = Strings::new(max, wrap);
434
435 for i in input {
436 if let ProcessResult::Break = strings.add_str(i) {
437 break;
438 }
439 }
440
441 let _size = strings.finish();
442
443 let lines = strings.lines();
444
445 let mut output = String::new();
446 for line in lines {
447 for e in line.entries {
448 match e {
449 Segment::SetStyle(_) => todo!(),
450 Segment::Str(s) => output.push_str(s),
451 }
452 }
453 output.push('\n');
454 }
455 output.pop();
456
457 assert_eq!(&output, expected);
458 }
459
460 #[test]
461 fn word_wrapping_layout() {
462 let inputs: &[(&[&str], &str)] = &[
463 (&["a\nb\nc"], "a\nb\nc"),
464 (&[" 12", "345 12", "345 "], " \n12345\n12345\n"),
465 (&[" 12", "345ć12", "345 "], " \n12345\n12345\n"),
466 (&[" ššš", "šš 12", "345 "], " \nšš\nšš\nš \n12345\n"),
467 (&["1", "23", "45 12", "345 "], "12345\n12345\n"),
468 (&["12345 abcde "], "12345\nabcde\n"),
469 (&["onereallylongword"], "onere\nallyl\nongwo\nrd"),
470 (&["ahello do the"], "ahell\no do \nthe"),
471 (&["hello do the"], "hello\ndo \nthe"),
472 ];
473
474 for (input, expected) in inputs {
475 test_layout(Size::new(5, 10), input, expected, Wrap::Normal);
476 }
477 }
478
479 #[test]
480 fn outliers() {
481 let inputs: &[(&[&str], &str)] = &[
482 (&["š"], ""),
483 (&["\n"], "\n"),
484 (&["\n\n\n"], "\n\n\n"),
485 (&["abc"], "a\nb\nc"),
486 ];
487
488 for (input, expected) in inputs {
489 test_layout(Size::new(1, 10), input, expected, Wrap::Normal);
490 }
491 }
492
493 #[test]
494 fn layout_size() {
495 let inputs: &[(&[&str], &str)] = &[(&["123456789"], "123\n456")];
496
497 for (input, expected) in inputs {
498 test_layout(Size::new(3, 2), input, expected, Wrap::Normal);
499 }
500 }
501
502 #[test]
503 fn word_breaking_layout() {
504 let inputs: &[(&[&str], &str)] = &[(&["123 4567"], "123 4\n567")];
505
506 for (input, expected) in inputs {
507 test_layout(Size::new(5, 3), input, expected, Wrap::WordBreak);
508 }
509 }
510
511 #[test]
512 fn freeze_layout() {
513 let mut strings = Strings::new(Size::new(100, 10), Wrap::Normal);
514
515 assert_eq!(strings.add_str("abc"), ProcessResult::Continue);
516 strings.freeze();
517 assert_eq!(strings.add_str("abc"), ProcessResult::Break);
518 }
519
520 #[test]
521 fn limited_space() {
522 test_layout(Size::new(58, 0), &["meh"], "", Wrap::Normal);
523 }
524
525 #[test]
526 fn wrap_from_attribute() {
527 let mut attributes = anathema_value_resolver::Attributes::empty();
528
529 attributes.set("break", Wrap::WordBreak);
530 let word_break = attributes.get_as::<Wrap>("break").unwrap();
531 assert_eq!(word_break, Wrap::WordBreak);
532
533 attributes.set("normal", Wrap::Normal);
534 let word_break = attributes.get_as::<Wrap>("normal").unwrap();
535 assert_eq!(word_break, Wrap::Normal);
536 }
537}