use unicode_width::UnicodeWidthStr;
const HALFWIDTH_KATAKANA_VOICED_SOUND_MARK: char = '\u{FF9E}';
const HALFWIDTH_KATAKANA_SEMI_VOICED_SOUND_MARK: char = '\u{FF9F}';
pub trait CellWidth {
fn cell_width(&self) -> u16;
}
impl CellWidth for str {
fn cell_width(&self) -> u16 {
if self.len() == 1 {
debug_assert!(
!self.as_bytes()[0].is_ascii_control(),
"control character passed to cell_width without filtering"
);
1
} else {
let width = self.width() as u16;
width.saturating_add(count_halfwidth_sound_marks(self))
}
}
}
fn count_halfwidth_sound_marks(s: &str) -> u16 {
s.chars()
.filter(|c| {
matches!(
*c,
HALFWIDTH_KATAKANA_VOICED_SOUND_MARK | HALFWIDTH_KATAKANA_SEMI_VOICED_SOUND_MARK
)
})
.count() as u16
}
#[cfg(test)]
mod tests {
use super::*;
fn width(s: &str) -> u16 {
s.cell_width()
}
fn width_char(c: char) -> u16 {
let mut buf = [0; 4];
width(c.encode_utf8(&mut buf))
}
#[test]
fn wide_char() {
assert_eq!("あ".cell_width(), 2);
}
#[test]
fn empty() {
assert_eq!("".cell_width(), 0);
}
#[test]
fn halfwidth_dakuten_alone() {
assert_eq!(width_char(HALFWIDTH_KATAKANA_VOICED_SOUND_MARK), 1); }
#[test]
fn halfwidth_handakuten_alone() {
assert_eq!(width_char(HALFWIDTH_KATAKANA_SEMI_VOICED_SOUND_MARK), 1); }
#[test]
fn halfwidth_katakana_with_dakuten() {
assert_eq!(width("ガ"), 2); assert_eq!(width("ザ"), 2); }
#[test]
fn halfwidth_katakana_with_handakuten() {
assert_eq!(width("パ"), 2); assert_eq!(width("ピ"), 2); }
#[test]
fn non_katakana_with_halfwidth_dakuten() {
assert_eq!(width("a゙"), 2); assert_eq!(width("1゚"), 2); assert_eq!(width("あ゙"), 3); assert_eq!(width("紅゙"), 3); }
#[test]
#[allow(clippy::unicode_not_nfc)]
fn combining_dakuten_no_special_handling() {
assert_eq!(width("ガ"), 1); assert_eq!(width("ガ"), 2); }
#[test]
#[allow(clippy::unicode_not_nfc)]
fn combining_handakuten_no_special_handling() {
assert_eq!(width("パ"), 1); assert_eq!(width("パ"), 2); }
#[test]
fn mixed_text_unchanged() {
assert_eq!(width("a"), 1);
assert_eq!(width("あ"), 2);
assert_eq!(width("カ"), 1);
assert_eq!(width("カ"), 2);
assert_eq!(width("aガb"), 4); assert_eq!(width("あガ"), 4); }
}