text_processing: add line_break method
In preparation for format=flowed support, add a line_break method in the text_processing Trait, now renamed from Graphemes to TextProcessing.sql
parent
098982015b
commit
e600b0252f
|
@ -1,7 +1,7 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[cfg(feature = "unicode_algorithms")]
|
#[cfg(feature = "unicode_algorithms")]
|
||||||
use text_processing::grapheme_clusters::Graphemes;
|
use text_processing::grapheme_clusters::TextProcessing;
|
||||||
|
|
||||||
pub fn encode_header(value: &str) -> String {
|
pub fn encode_header(value: &str) -> String {
|
||||||
let mut ret = String::with_capacity(value.len());
|
let mut ret = String::with_capacity(value.len());
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::wcwidth::{wcwidth, CodePointsIter};
|
||||||
extern crate unicode_segmentation;
|
extern crate unicode_segmentation;
|
||||||
use self::unicode_segmentation::UnicodeSegmentation;
|
use self::unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
pub trait Graphemes: UnicodeSegmentation + CodePointsIter {
|
pub trait TextProcessing: UnicodeSegmentation + CodePointsIter {
|
||||||
fn split_graphemes<'a>(&'a self) -> Vec<&'a str> {
|
fn split_graphemes<'a>(&'a self) -> Vec<&'a str> {
|
||||||
UnicodeSegmentation::graphemes(self, true).collect::<Vec<&str>>()
|
UnicodeSegmentation::graphemes(self, true).collect::<Vec<&str>>()
|
||||||
}
|
}
|
||||||
|
@ -41,9 +41,15 @@ pub trait Graphemes: UnicodeSegmentation + CodePointsIter {
|
||||||
fn grapheme_len(&self) -> usize {
|
fn grapheme_len(&self) -> usize {
|
||||||
self.split_graphemes().len()
|
self.split_graphemes().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn split_lines(&self, width: usize) -> Vec<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Graphemes for str {}
|
impl TextProcessing for str {
|
||||||
|
fn split_lines(&self, width: usize) -> Vec<String> {
|
||||||
|
crate::line_break::linear(self, width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WordBreakIter<'s> {
|
pub struct WordBreakIter<'s> {
|
||||||
input: &'s str,
|
input: &'s str,
|
||||||
|
|
|
@ -699,5 +699,179 @@ mod tests {
|
||||||
prev = b.0;
|
prev = b.0;
|
||||||
}
|
}
|
||||||
println!("{:?}", &s[prev..]);
|
println!("{:?}", &s[prev..]);
|
||||||
|
|
||||||
|
let s = r#"Τ' άστρα τα κοντά -στη γλυκιά σελήνη
|
||||||
|
την ειδή των κρύβουν - τη διαμαντένια,
|
||||||
|
άμα φως λαμπρό -στη γή πάσα χύνει,
|
||||||
|
όλη ασημένια."#;
|
||||||
|
let breaks = LineBreakCandidateIter::new(s).collect::<Vec<(usize, LineBreakCandidate)>>();
|
||||||
|
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<usize>, offsets: &Vec<usize>) -> 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<usize>,
|
||||||
|
columns: &mut Vec<usize>,
|
||||||
|
minima: &mut Vec<usize>,
|
||||||
|
breaks: &mut Vec<usize>,
|
||||||
|
width: usize,
|
||||||
|
offsets: &Vec<usize>,
|
||||||
|
) {
|
||||||
|
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<String> {
|
||||||
|
let mut words = Vec::new();
|
||||||
|
let breaks =
|
||||||
|
LineBreakCandidateIter::new(text).collect::<Vec<(usize, LineBreakCandidate)>>();
|
||||||
|
{
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -622,12 +622,7 @@ impl Component for MailView {
|
||||||
.map(|v| String::from_utf8_lossy(v).into_owned())
|
.map(|v| String::from_utf8_lossy(v).into_owned())
|
||||||
.unwrap_or_else(|e| e.to_string())
|
.unwrap_or_else(|e| e.to_string())
|
||||||
};
|
};
|
||||||
self.pager = Some(Pager::from_string(
|
self.pager = Some(Pager::from_string(text, Some(context), None, None));
|
||||||
text,
|
|
||||||
Some(context),
|
|
||||||
None,
|
|
||||||
Some(width!(area)),
|
|
||||||
));
|
|
||||||
self.subview = None;
|
self.subview = None;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -643,12 +638,7 @@ impl Component for MailView {
|
||||||
} else {
|
} else {
|
||||||
self.pager.as_mut().map(|p| p.cursor_pos())
|
self.pager.as_mut().map(|p| p.cursor_pos())
|
||||||
};
|
};
|
||||||
self.pager = Some(Pager::from_string(
|
self.pager = Some(Pager::from_string(text, Some(context), cursor_pos, None));
|
||||||
text,
|
|
||||||
Some(context),
|
|
||||||
cursor_pos,
|
|
||||||
Some(width!(area)),
|
|
||||||
));
|
|
||||||
self.subview = None;
|
self.subview = None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -339,12 +339,7 @@ impl Component for EnvelopeView {
|
||||||
} else {
|
} else {
|
||||||
self.pager.as_ref().map(Pager::cursor_pos)
|
self.pager.as_ref().map(Pager::cursor_pos)
|
||||||
};
|
};
|
||||||
self.pager = Some(Pager::from_string(
|
self.pager = Some(Pager::from_string(text, Some(context), cursor_pos, None));
|
||||||
text,
|
|
||||||
Some(context),
|
|
||||||
cursor_pos,
|
|
||||||
Some(width!(area)),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
|
|
|
@ -297,10 +297,10 @@ impl fmt::Display for Pager {
|
||||||
impl Pager {
|
impl Pager {
|
||||||
const DESCRIPTION: &'static str = "pager";
|
const DESCRIPTION: &'static str = "pager";
|
||||||
pub fn update_from_str(&mut self, text: &str, width: Option<usize>) {
|
pub fn update_from_str(&mut self, text: &str, width: Option<usize>) {
|
||||||
let lines: Vec<&str> = if let Some(width) = width {
|
let lines: Vec<String> = if let Some(width) = width {
|
||||||
word_break_string(text, width)
|
text.split_lines(width)
|
||||||
} else {
|
} else {
|
||||||
text.trim().split('\n').collect()
|
text.trim().split('\n').map(str::to_string).collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
let height = lines.len() + 1;
|
let height = lines.len() + 1;
|
||||||
|
@ -355,10 +355,10 @@ impl Pager {
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = {
|
let content = {
|
||||||
let lines: Vec<&str> = if let Some(width) = width {
|
let lines: Vec<String> = if let Some(width) = width {
|
||||||
word_break_string(text.as_str(), width)
|
text.split_lines(width)
|
||||||
} else {
|
} else {
|
||||||
text.trim().split('\n').collect()
|
text.trim().split('\n').map(str::to_string).collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
let height = lines.len() + 1;
|
let height = lines.len() + 1;
|
||||||
|
@ -384,10 +384,10 @@ impl Pager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn from_str(text: &str, cursor_pos: Option<usize>, width: Option<usize>) -> Self {
|
pub fn from_str(text: &str, cursor_pos: Option<usize>, width: Option<usize>) -> Self {
|
||||||
let lines: Vec<&str> = if let Some(width) = width {
|
let lines: Vec<String> = if let Some(width) = width {
|
||||||
word_break_string(text, width)
|
text.split_lines(width)
|
||||||
} else {
|
} else {
|
||||||
text.trim().split('\n').collect()
|
text.trim().split('\n').map(str::to_string).collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
let height = lines.len() + 1;
|
let height = lines.len() + 1;
|
||||||
|
@ -419,7 +419,7 @@ impl Pager {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn print_string(content: &mut CellBuffer, lines: Vec<&str>) {
|
pub fn print_string(content: &mut CellBuffer, lines: Vec<String>) {
|
||||||
let width = content.size().0;
|
let width = content.size().0;
|
||||||
for (i, l) in lines.iter().enumerate() {
|
for (i, l) in lines.iter().enumerate() {
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use text_processing::Graphemes;
|
use text_processing::TextProcessing;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
pub struct UText {
|
pub struct UText {
|
||||||
|
|
Loading…
Reference in New Issue