#![allow(dead_code)]
use std::num::NonZeroUsize;
use std::sync::Mutex;
use cached::proc_macro::cached;
use lazy_static::lazy_static;
use lru::LruCache;
use regex::Regex;
use unicode_segmentation::UnicodeSegmentation;
use crate::cell_widths::CELL_WIDTHS;
lazy_static! {
static ref CACHE: Mutex<LruCache<String, u32>> =
Mutex::new(LruCache::new(NonZeroUsize::new(4096).unwrap()));
}
pub fn cell_len(text: &str) -> u32 {
let mut cache = CACHE.lock().expect("Unable to lock LRUCache!");
let cached_result = cache.get(text);
if let Some(cached_result) = cached_result {
return *cached_result;
}
let total_size = text.chars().map(get_character_cell_size).sum();
if UnicodeSegmentation::graphemes(text, true).count() <= 512 {
cache.put(text.into(), total_size);
}
total_size
}
#[cached(size = 4096)]
pub fn get_character_cell_size(character: char) -> u32 { get_codepoint_cell_size(character as u32) }
#[cached(size = 4096)]
fn get_codepoint_cell_size(codepoint: u32) -> u32 {
let table = CELL_WIDTHS;
let mut lower_bound = 0;
let mut upper_bound = table.len() - 1;
let mut index = (lower_bound + upper_bound) / 2;
loop {
let (start, end, width) = table[index];
if codepoint < start {
upper_bound = index - 1;
} else if codepoint > end {
lower_bound = index + 1;
} else {
return width.unwrap_or(0);
}
if upper_bound < lower_bound {
break 1;
}
index = (lower_bound + upper_bound) / 2;
}
}
#[allow(clippy::missing_panics_doc)]
pub fn set_cell_size(text: String, total: u32) -> String {
if Regex::new(r#"^[\u0020-\u006f\u00a0\u02ff\u0370-\u0482]*$"#)
.unwrap()
.is_match(&text)
{
let size: u32 = UnicodeSegmentation::graphemes(text.as_str(), true)
.count()
.try_into()
.unwrap();
if size < total {
return text + &" ".repeat((total - size) as usize);
}
return text[..total as usize].into();
}
if total == 0 {
return "".into();
}
let cell_size = cell_len(&text);
if cell_size == total {
return text;
}
if cell_size < total {
return text + &" ".repeat((total - cell_size) as usize);
}
let mut start = 0;
let mut end = UnicodeSegmentation::graphemes(text.as_str(), true).count();
loop {
let pos = (start + end) / 2;
let before = &UnicodeSegmentation::graphemes(text.as_str(), true)
.take(pos + 1)
.collect::<String>();
let before_len = cell_len(before);
let graphemes = UnicodeSegmentation::graphemes(before.as_str(), true);
if before_len == total + 1 && cell_len(graphemes.clone().last().unwrap()) == 2 {
return format!(
"{} ",
&graphemes
.clone()
.take(&graphemes.count() - 1)
.collect::<String>()
);
}
if before_len == total {
return before.into();
}
if before_len > total {
end = pos;
} else {
start = pos;
}
}
}
#[allow(clippy::module_name_repetitions)]
#[allow(clippy::missing_panics_doc)]
pub fn chop_cells(text: &str, max_size: u32, position: Option<u32>) -> Vec<String> {
let characters = text
.chars()
.map(|character| (character, get_character_cell_size(character)));
let mut lines: Vec<Vec<char>> = vec![vec![]];
let mut total_size = position.unwrap_or(0);
for (character, size) in characters.rev() {
if total_size + size > max_size {
lines.push(vec![character]);
total_size = size;
} else {
total_size += size;
lines.last_mut().unwrap().push(character);
}
}
lines
.into_iter()
.map(|line| line.into_iter().collect())
.collect()
}
#[cfg(test)]
mod tests {
#![allow(clippy::needless_pass_by_value)]
use rstest::rstest;
use super::*;
fn test_cell_len_long_string() { assert_eq!(cell_len(&"abc".repeat(200)), 3 * 200) }
#[rstest]
#[case("foo", 0, "")]
#[case("f", 0, "")]
#[case("", 0, "")]
#[case("😽😽", 0, "")]
#[case("foo", 2, "fo")]
#[case("foo", 3, "foo")]
#[case("foo", 4, "foo ")]
#[case("😽😽", 4, "😽😽")]
#[case("😽😽", 3, "😽 ")]
#[case("😽😽", 2, "😽")]
#[case("😽😽", 1, " ")]
#[case("😽😽", 5, "😽😽 ")]
fn test_set_cell_size(#[case] test_case: &str, #[case] size: u32, #[case] result: &str) {
assert_eq!(
set_cell_size(test_case.into(), size),
result,
"set_cell_size({test_case}, {size})"
);
}
#[test]
fn test_set_cell_size_infinite() {
for size in 0..38 {
assert_eq!(
cell_len(&set_cell_size(
"เป็นเกมที่ต้องมีความอดทนมากที่สุดตั้งเเต่เคยเล่นมา".into(),
size
)),
size
);
}
}
#[test]
fn test_chop_cells() {
assert_eq!(
chop_cells(
"这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。",
8,
None
),
vec![
"。惑诱的",
"测猜绝拒",
",法想的",
"可两棱模",
"对面。试",
"测的持支",
"言语洲亚",
"对是这"
]
);
}
}