xutf 0.1.0

Permissive UTF-8/16/32 transcoding, comparison and BOM detection with SIMD ASCII fast paths
Documentation
use unicode_width::UnicodeWidthStr;
use xutf::{Utf8, Utf16, Utf32, width, width_str};

const CORPUS: &[&str] = &[
	"plain printable ASCII 0123456789 !@#$%^&*()",
	"Cafe\u{301} nai\u{308}ve A\u{30a}",
	"Ελληνικά Кириллица",
	"漢字界面 日本語 カタカナ ひらがな",
	"abc アイウ カタカナ",
	"한글 각 값 가 각 ㄱㄴㅏㅣ \u{3164}",
	"नमस्ते हिन्दी தமிழ் ภาษาไทย",
	"┌─┬─┐│╳│└─┴─┘",
	"😀 🐈 🚀 ✅ 🌍",
	"⚠️ ℹ️ ❤️",
	"0️⃣ #️⃣",
	"👨‍👩‍👧 👩‍❤️‍👩 👨‍👨‍👦‍👦",
	"👋🏽 👍🏻 🧑🏿",
	"🇺🇸 🇯🇵 🇩🇪",
	"🏴\u{e0067}\u{e0062}\u{e0065}\u{e006e}\u{e0067}\u{e007f}",
];

fn reference(s: &str) -> usize {
	UnicodeWidthStr::width(s)
}

#[test]
fn safe_corpus_matches_unicode_width() {
	for &s in CORPUS {
		assert_eq!(width_str(s), reference(s), "{s:?}");
	}

	let mut state = 0x8e5d_a2c7_13b9_4f61u64;
	for _ in 0..500 {
		state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
		let count = 2 + (state as usize % 7);
		let mut s = String::new();
		for _ in 0..count {
			state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
			s.push_str(CORPUS[state as usize % CORPUS.len()]);
			s.push(' ');
		}
		assert_eq!(width_str(&s), reference(&s), "{s:?}");
	}
}

#[test]
fn deterministic_safe_scalar_fuzz_matches_unicode_width() {
	const SAFE: &[char] = &[
		' ', '!', '0', 'A', 'z', 'é', 'Ω', 'Ж', '\u{301}', '\u{308}', '', '', '\u{93f}', '\u{94d}',
		'', '\u{bbf}', '', '\u{e34}', '', '', '', '', '', '', '', '', '', '',
		'\u{3164}', '', '', '', '😀', '', '🚀', '👋', '🏽', '🇺', '🇸',
	];
	let mut state = 0xd1b5_4a32_d192_ed03u64;
	for _ in 0..2_000 {
		state = state
			.wrapping_mul(2862933555777941757)
			.wrapping_add(3037000493);
		let len = state as usize % 40;
		let mut s = String::new();
		for _ in 0..len {
			state = state
				.wrapping_mul(2862933555777941757)
				.wrapping_add(3037000493);
			s.push(SAFE[state as usize % SAFE.len()]);
		}
		assert_eq!(width_str(&s), reference(&s), "{s:?}");
	}
}

#[test]
fn all_encodings_measure_the_same_width() {
	for &s in CORPUS {
		let utf16: Vec<u16> = s.encode_utf16().collect();
		let utf32: Vec<u32> = s.chars().map(u32::from).collect();
		let utf16_foreign: Vec<u16> = utf16.iter().map(|u| u.swap_bytes()).collect();
		let utf32_foreign: Vec<u32> = utf32.iter().map(|u| u.swap_bytes()).collect();
		let expected = width::<Utf8>(s.as_bytes());
		assert_eq!(width::<Utf16<false>>(&utf16), expected, "{s:?}");
		assert_eq!(width::<Utf32<false>>(&utf32), expected, "{s:?}");
		assert_eq!(width::<Utf16<true>>(&utf16_foreign), expected, "{s:?}");
		assert_eq!(width::<Utf32<true>>(&utf32_foreign), expected, "{s:?}");
	}
}

#[test]
fn simd_boundaries_and_cluster_backoff() {
	const LENGTHS: &[usize] = &[1, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129];
	for &k in LENGTHS {
		assert_eq!(width_str(&"x".repeat(k)), k);

		let keycap = "0".repeat(k) + "0\u{fe0f}\u{20e3}";
		assert_eq!(width_str(&keycap), k + 2, "keycap after {k} units");

		let promoted = "0".repeat(k) + "\u{fe0f}";
		assert_eq!(width_str(&promoted), k + 1, "VS16 after {k} units");

		let text_base = "x".repeat(k) + "\u{fe0f}\u{20e3}";
		assert_eq!(width_str(&text_base), k, "non-keycap base after {k} units");

		let tab = "x".repeat(k) + "\t";
		let bell = "x".repeat(k) + "\x07";
		assert_eq!(width_str(&tab), k);
		assert_eq!(width_str(&bell), k);
	}
}

#[test]
fn tui_reference_widths() {
	for (s, expected) in [
		("⚠️", 2),
		("", 1),
		("", 2),
		("0️⃣", 2),
		("a\tb", 2),
		("", 2),
		("\u{3164}", 0),
		("e\u{301}", 1),
		("🇺🇸", 2),
		("👨‍👩‍👧", 2),
		("", 0),
	] {
		assert_eq!(width_str(s), expected, "{s:?}");
	}
}