xutf 1.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_char, width_str, width_within, width_within_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:?}");
	}
}

#[test]
fn bounded_width_matches_unbounded_measurement() {
	for (s, budgets) in [
		("", &[0, 1][..]),
		("ascii", &[0, 4, 5, 6]),
		("a somewhat longer printable ASCII line", &[0, 8, 20, 38, 39]),
		("漢字a", &[0, 1, 2, 4, 5]),
		("👨‍👩‍👧 and text", &[0, 1, 2, 5, 10]),
		("ab\u{301}", &[0, 1, 2]),
	] {
		let expected = width_str(s);
		for &max_width in budgets {
			let bounded = width_within_str(s, max_width);
			assert_eq!(bounded, (expected <= max_width).then_some(expected), "{s:?} at {max_width}");
			assert_eq!(width_within::<Utf8>(s.as_bytes(), max_width), bounded, "{s:?} at {max_width}");
		}
	}

	let long = "x".repeat(1024 * 1024);
	assert_eq!(width_within_str(&long, 80), None);
}

#[test]
fn bounded_width_includes_zero_width_tail_at_edge() {
	assert_eq!(width_within_str("", 0), Some(0));
	assert_eq!(width_within_str("a", 0), None);
	assert_eq!(width_within_str("ab\u{301}", 2), Some(2));
	assert_eq!(width_within_str("ab\u{301}", 1), None);

	let exact = "界e\u{301}😀";
	let exact_width = width_str(exact);
	assert_eq!(width_within_str(exact, exact_width), Some(exact_width));
}

#[test]
fn bounded_width_matches_utf16() {
	let sample = "ASCII 漢字 e\u{301} 👨‍👩‍👧";
	let utf16: Vec<u16> = sample.encode_utf16().collect();
	for max_width in 0..=width_str(sample) + 1 {
		assert_eq!(
			width_within::<Utf16<false>>(&utf16, max_width),
			width_within_str(sample, max_width),
			"budget {max_width}"
		);
	}
}

#[test]
fn standalone_character_widths() {
	for (c, expected) in [
		('a', 1),
		('\t', 0),
		('\n', 0),
		('\r', 0),
		('', 2),
		('\u{301}', 0),
		('\u{2764}', 1),
		('\u{1f600}', 2),
		('\u{200d}', 0),
	] {
		assert_eq!(width_char(c), expected, "{c:?}");
	}
}