#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TabPolicy {
Fixed(u16),
SingleCell,
}
pub fn cell_width_char(ch: char) -> u16 {
if ch.is_control() {
return 0;
}
let u = ch as u32;
if matches!(
u,
0x0300..=0x036F | 0x1AB0..=0x1AFF | 0x1DC0..=0x1DFF | 0x20D0..=0x20FF | 0xFE20..=0xFE2F ) {
return 0;
}
if matches!(
u,
0x1100..=0x115F | 0x2329..=0x232A
| 0x2E80..=0xA4CF | 0xAC00..=0xD7A3 | 0xF900..=0xFAFF | 0xFE10..=0xFE19 | 0xFE30..=0xFE6F | 0xFF00..=0xFF60 | 0xFFE0..=0xFFE6
) {
return 2;
}
1
}
pub fn cell_width(s: &str, tab_policy: TabPolicy) -> u16 {
let mut width: u16 = 0;
for ch in s.chars() {
match ch {
'\t' => match tab_policy {
TabPolicy::Fixed(n) => width = width.saturating_add(n),
TabPolicy::SingleCell => width = width.saturating_add(1),
},
'\n' | '\r' => {
}
_ => width = width.saturating_add(cell_width_char(ch)),
}
}
width
}
pub fn clip_to_cells(s: &str, max_cells: u16, tab_policy: TabPolicy) -> String {
if max_cells == 0 {
return String::new();
}
let mut out = String::new();
let mut used: u16 = 0;
for ch in s.chars() {
if ch == '\n' || ch == '\r' {
break;
}
if ch == '\t' {
let tab_w = match tab_policy {
TabPolicy::Fixed(n) => n,
TabPolicy::SingleCell => 1,
};
if used.saturating_add(tab_w) > max_cells {
break;
}
let spaces = tab_w as usize;
out.extend(std::iter::repeat(' ').take(spaces));
used = used.saturating_add(tab_w);
continue;
}
let w = cell_width_char(ch);
if w == 0 {
continue;
}
if used.saturating_add(w) > max_cells {
break;
}
out.push(ch);
used = used.saturating_add(w);
}
out
}
pub fn clip_to_cells_ellipsis(s: &str, max_cells: u16, tab_policy: TabPolicy) -> String {
if max_cells == 0 {
return String::new();
}
if cell_width(s, tab_policy) <= max_cells {
return s.to_string();
}
let ell = '…';
let ell_w = cell_width_char(ell);
if ell_w > 0 && ell_w < max_cells {
let mut clipped = clip_to_cells(s, max_cells.saturating_sub(ell_w), tab_policy);
clipped.push(ell);
return clipped;
}
if max_cells >= 1 {
return ".".to_string();
}
String::new()
}
pub fn fit_to_cells(s: &str, target_cells: u16, tab_policy: TabPolicy, ellipsis: bool) -> String {
if target_cells == 0 {
return String::new();
}
let mut out = if ellipsis {
clip_to_cells_ellipsis(s, target_cells, tab_policy)
} else {
clip_to_cells(s, target_cells, tab_policy)
};
let used = cell_width(&out, TabPolicy::SingleCell);
if used < target_cells {
out.extend(std::iter::repeat(' ').take((target_cells - used) as usize));
}
out
}