diff --git a/melib/src/email/compose/mime.rs b/melib/src/email/compose/mime.rs index 371c741b5..3fb731ad7 100644 --- a/melib/src/email/compose/mime.rs +++ b/melib/src/email/compose/mime.rs @@ -1,7 +1,7 @@ use super::*; #[cfg(feature = "unicode_algorithms")] -use text_processing::grapheme_clusters::Graphemes; +use text_processing::grapheme_clusters::TextProcessing; pub fn encode_header(value: &str) -> String { let mut ret = String::with_capacity(value.len()); diff --git a/text_processing/src/grapheme_clusters.rs b/text_processing/src/grapheme_clusters.rs index b1f825bfe..f272cebaf 100644 --- a/text_processing/src/grapheme_clusters.rs +++ b/text_processing/src/grapheme_clusters.rs @@ -12,7 +12,7 @@ use crate::wcwidth::{wcwidth, CodePointsIter}; extern crate unicode_segmentation; use self::unicode_segmentation::UnicodeSegmentation; -pub trait Graphemes: UnicodeSegmentation + CodePointsIter { +pub trait TextProcessing: UnicodeSegmentation + CodePointsIter { fn split_graphemes<'a>(&'a self) -> Vec<&'a str> { UnicodeSegmentation::graphemes(self, true).collect::>() } @@ -41,9 +41,15 @@ pub trait Graphemes: UnicodeSegmentation + CodePointsIter { fn grapheme_len(&self) -> usize { self.split_graphemes().len() } + + fn split_lines(&self, width: usize) -> Vec; } -impl Graphemes for str {} +impl TextProcessing for str { + fn split_lines(&self, width: usize) -> Vec { + crate::line_break::linear(self, width) + } +} pub struct WordBreakIter<'s> { input: &'s str, diff --git a/text_processing/src/line_break.rs b/text_processing/src/line_break.rs index 5f42e7b9b..f8790f2b2 100644 --- a/text_processing/src/line_break.rs +++ b/text_processing/src/line_break.rs @@ -699,5 +699,179 @@ mod tests { prev = b.0; } println!("{:?}", &s[prev..]); + + let s = r#"Τ' άστρα τα κοντά -στη γλυκιά σελήνη +την ειδή των κρύβουν - τη διαμαντένια, +άμα φως λαμπρό -στη γή πάσα χύνει, +όλη ασημένια."#; + let breaks = LineBreakCandidateIter::new(s).collect::>(); + let mut prev = 0; + for b in breaks { + println!("{:?}", &s[prev..b.0]); + prev = b.0; + } + println!("{:?}", &s[prev..]); + } +} + +pub use alg::linear; + +mod alg { + use super::*; + use crate::grapheme_clusters::TextProcessing; + fn cost(i: usize, j: usize, width: usize, minima: &Vec, offsets: &Vec) -> usize { + let w = offsets[j] + j - offsets[i] - i - 1; + if w > width { + return 65536 * (w - width); + } + minima[i] + (width - w) * (width - w) + } + + fn smawk( + rows: &mut Vec, + columns: &mut Vec, + minima: &mut Vec, + breaks: &mut Vec, + width: usize, + offsets: &Vec, + ) { + let mut stack = Vec::new(); + let mut i = 0; + while i < rows.len() { + if stack.len() > 0 { + let c = columns[stack.len() - 1]; + if cost(*stack.iter().last().unwrap(), c, width, minima, offsets) + < cost(rows[i], c, width, minima, offsets) + { + if stack.len() < columns.len() { + stack.push(rows[i]); + } + i += 1; + } else { + stack.pop(); + } + } else { + stack.push(rows[i]); + i += 1; + } + } + let rows = &mut stack; + if columns.len() > 1 { + let mut odd_columns = columns.iter().skip(1).step_by(2).cloned().collect(); + smawk(rows, &mut odd_columns, minima, breaks, width, offsets); + for (i, o) in odd_columns.into_iter().enumerate() { + columns[2 * i + 1] = o; + } + } + let mut i = 0; + let mut j = 0; + while j < columns.len() { + let end = if j + 1 < columns.len() { + breaks[columns[j + 1]] + } else { + *rows.iter().last().unwrap() + }; + let c = cost(rows[i], columns[j], width, minima, offsets); + if c < minima[columns[j]] { + minima[columns[j]] = c; + breaks[columns[j]] = rows[i]; + } + if rows[i] < end { + i += 1; + } else { + j += 2; + } + } + } + + pub fn linear(text: &str, width: usize) -> Vec { + let mut words = Vec::new(); + let breaks = + LineBreakCandidateIter::new(text).collect::>(); + { + let mut prev = 0; + for b in breaks { + if text[prev..b.0].ends_with("\n") && text[b.0..].starts_with("\n") { + words.push(text[prev..b.0].trim_end_matches("\n")); + words.push("\n\n"); + } else if &text[prev..b.0] != "\n" { + words.push(text[prev..b.0].trim_end_matches("\n")); + if text[prev..b.0].ends_with("\n") { + words.push(" "); + } + } + prev = b.0; + } + if &text[prev..] != "\n" { + words.push(text[prev..].trim_end_matches("\n")); + } + } + let count = words.len(); + let mut minima = vec![std::usize::MAX - 1; count + 1]; + minima[0] = 0; + let mut offsets = Vec::with_capacity(words.len()); + offsets.push(0); + for w in words.iter() { + if *w == "\n\n" { + offsets.push(offsets.iter().last().unwrap() + width - 1); + } else { + offsets.push(offsets.iter().last().unwrap() + w.grapheme_len().saturating_sub(1)); + } + } + + let mut breaks = vec![0; count + 1]; + + let mut n = count + 1; + let mut i = 1; + let mut offset = 0; + loop { + let r = std::cmp::min(n, 2 * i); + let edge = i + offset; + smawk( + &mut (offset..edge).collect(), + &mut (edge..(r + offset)).collect(), + &mut minima, + &mut breaks, + width, + &offsets, + ); + let x = minima[r - 1 + offset]; + let mut for_was_broken = false; + for j in i..(r - 1) { + let y = cost(j + offset, r - 1 + offset, width, &minima, &offsets); + if y <= x { + n -= j; + i = 1; + offset += j; + for_was_broken = true; + break; + } + } + + if !for_was_broken { + if r == n { + break; + } + i *= 2; + } + } + let paragraphs = text.split("\n\n").count(); + let mut lines = Vec::new(); + let mut j = count; + let mut p_i = 0; + while j > 0 { + let mut line = String::new(); + for i in breaks[j]..j { + line.push_str(words[i]); + } + lines.push(line); + if p_i + 1 < paragraphs { + lines.push(String::new()); + p_i += 1; + } + j = breaks[j]; + } + lines.reverse(); + lines } } diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs index 5cfd5dbcc..df5f06a56 100644 --- a/ui/src/components/mail/view.rs +++ b/ui/src/components/mail/view.rs @@ -622,12 +622,7 @@ impl Component for MailView { .map(|v| String::from_utf8_lossy(v).into_owned()) .unwrap_or_else(|e| e.to_string()) }; - self.pager = Some(Pager::from_string( - text, - Some(context), - None, - Some(width!(area)), - )); + self.pager = Some(Pager::from_string(text, Some(context), None, None)); self.subview = None; } _ => { @@ -643,12 +638,7 @@ impl Component for MailView { } else { self.pager.as_mut().map(|p| p.cursor_pos()) }; - self.pager = Some(Pager::from_string( - text, - Some(context), - cursor_pos, - Some(width!(area)), - )); + self.pager = Some(Pager::from_string(text, Some(context), cursor_pos, None)); self.subview = None; } }; diff --git a/ui/src/components/mail/view/envelope.rs b/ui/src/components/mail/view/envelope.rs index 1cef8958a..b6ea3dfcc 100644 --- a/ui/src/components/mail/view/envelope.rs +++ b/ui/src/components/mail/view/envelope.rs @@ -339,12 +339,7 @@ impl Component for EnvelopeView { } else { self.pager.as_ref().map(Pager::cursor_pos) }; - self.pager = Some(Pager::from_string( - text, - Some(context), - cursor_pos, - Some(width!(area)), - )); + self.pager = Some(Pager::from_string(text, Some(context), cursor_pos, None)); } }; self.dirty = false; diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index 48108d504..46234c321 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -297,10 +297,10 @@ impl fmt::Display for Pager { impl Pager { const DESCRIPTION: &'static str = "pager"; pub fn update_from_str(&mut self, text: &str, width: Option) { - let lines: Vec<&str> = if let Some(width) = width { - word_break_string(text, width) + let lines: Vec = if let Some(width) = width { + text.split_lines(width) } else { - text.trim().split('\n').collect() + text.trim().split('\n').map(str::to_string).collect() }; let height = lines.len() + 1; @@ -355,10 +355,10 @@ impl Pager { } let content = { - let lines: Vec<&str> = if let Some(width) = width { - word_break_string(text.as_str(), width) + let lines: Vec = if let Some(width) = width { + text.split_lines(width) } else { - text.trim().split('\n').collect() + text.trim().split('\n').map(str::to_string).collect() }; let height = lines.len() + 1; @@ -384,10 +384,10 @@ impl Pager { } } pub fn from_str(text: &str, cursor_pos: Option, width: Option) -> Self { - let lines: Vec<&str> = if let Some(width) = width { - word_break_string(text, width) + let lines: Vec = if let Some(width) = width { + text.split_lines(width) } else { - text.trim().split('\n').collect() + text.trim().split('\n').map(str::to_string).collect() }; let height = lines.len() + 1; @@ -419,7 +419,7 @@ impl Pager { ..Default::default() } } - pub fn print_string(content: &mut CellBuffer, lines: Vec<&str>) { + pub fn print_string(content: &mut CellBuffer, lines: Vec) { let width = content.size().0; for (i, l) in lines.iter().enumerate() { write_string_to_grid( diff --git a/ui/src/terminal/text_editing.rs b/ui/src/terminal/text_editing.rs index 5433b6036..f46c11e13 100644 --- a/ui/src/terminal/text_editing.rs +++ b/ui/src/terminal/text_editing.rs @@ -1,4 +1,4 @@ -use text_processing::Graphemes; +use text_processing::TextProcessing; #[derive(Debug, Clone, Default, PartialEq)] pub struct UText {