#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
use crate::util;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
static UTF16_TO_UTF8_PACK_1_2: [[u8; 17]; 256] = const {
let mut table = [[0x80u8; 17]; 256];
let mut mask = 0;
while mask < 256 {
let row = &mut table[mask];
let bits = [0, 2, 4, 6, 1, 3, 5, 7];
let mut i = 1;
let mut widx = 0u8;
while widx < 8 {
let bit = bits[widx as usize];
if mask & (1 << bit) != 0 {
row[i] = widx * 2;
i += 1;
} else {
row[i] = widx * 2 + 1;
row[i + 1] = widx * 2;
i += 2;
}
widx += 1;
}
row[0] = i as u8 - 1;
mask += 1;
}
table
};
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
static UTF16_TO_UTF8_PACK_1_2_3: [[u8; 17]; 256] = const {
let mut table = [[0x80u8; 17]; 256];
let mut mask = 0;
while mask < 256 {
let row = &mut table[mask];
let mut i = 1;
let mut widx = 0u8;
while widx < 4 {
let subword = (mask >> (2 * widx)) & 0b11;
match subword {
0 => {
row[i] = widx * 4 + 2;
row[i + 1] = widx * 4 + 3;
row[i + 2] = widx * 4 + 1;
i += 3;
}
3 => {
row[i] = widx * 4;
i += 1;
}
2 => {
row[i] = widx * 4 + 3;
row[i + 1] = widx * 4 + 1;
i += 2;
}
_ => {}
}
widx += 1;
}
row[0] = i as u8 - 1;
mask += 1;
}
table
};
fn utf16_to_utf8_scalar(buf: &[u16], forward: usize, out: &mut [u8]) -> Result<(usize, usize), (usize, usize)> {
let mut consumed = 0;
let mut written = 0;
while consumed < forward {
let word = buf[consumed];
if word & 0xFF80 == 0 {
out[written] = word as u8;
written += 1;
} else if word & 0xF800 == 0 {
out[written] = ((word >> 6) | 0b11000000) as u8;
out[written + 1] = ((word & 0b111111) | 0b10000000) as u8;
written += 2;
} else if word & 0xF800 != 0xD800 {
out[written] = ((word >> 12) | 0b11100000) as u8;
out[written + 1] = (((word >> 6) & 0b111111) | 0b10000000) as u8;
out[written + 2] = ((word & 0b111111) | 0b10000000) as u8;
written += 3;
} else {
let diff = word.wrapping_sub(0xD800);
let next_word = buf.get(consumed + 1).ok_or_else(|| (consumed, written))?;
consumed += 1;
let diff2 = next_word.wrapping_sub(0xDC00);
if (diff | diff2) > 0x3FF {
return Err((consumed - 1, written));
}
let value = ((diff as u32) << 10) + diff2 as u32 + 0x10000;
out[written] = ((value >> 18) | 0b11110000) as u8;
out[written + 1] = (((value >> 12) & 0b111111) | 0b10000000) as u8;
out[written + 2] = (((value >> 6) & 0b111111) | 0b10000000) as u8;
out[written + 3] = ((value & 0b111111) | 0b10000000) as u8;
written += 4;
}
consumed += 1;
}
Ok((consumed, written))
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
fn utf16_to_utf8_avx2(buf: &[u16], out: &mut [u8]) -> Result<(usize, usize), (usize, usize)> {
use core::arch::x86_64::*;
let v_0000 = _mm256_setzero_si256();
let v_f800 = _mm256_set1_epi16(0xF800_u16 as _);
let v_d800 = _mm256_set1_epi16(0xD800_u16 as _);
let v_c080 = _mm256_set1_epi16(0xC080_u16 as _);
const SAFETY_MARGIN: usize = 12;
assert!(buf.len() > 16 + SAFETY_MARGIN);
let mut consumed = 0;
let mut written = 0;
while buf.len() - consumed >= 16 + SAFETY_MARGIN {
let inp = unsafe { _mm256_loadu_si256(buf.as_ptr().add(consumed).cast()) };
let v_ff80 = _mm256_set1_epi16(0xFF80_u16 as _);
if _mm256_testz_si256(inp, v_ff80) == 1 {
let utf8_packed = _mm_packus_epi16(_mm256_castsi256_si128(inp), _mm256_extractf128_si256::<1>(inp));
unsafe { _mm_storeu_si128(out.as_mut_ptr().add(written).cast(), utf8_packed) };
consumed += 16;
written += 16;
continue;
}
let one_byte_bytemask = _mm256_cmpeq_epi16(_mm256_and_si256(inp, v_ff80), v_0000);
let one_byte_bitmask = _mm256_movemask_epi8(one_byte_bytemask) as u32;
let one_or_two_bytes_bytemask = _mm256_cmpeq_epi16(_mm256_and_si256(inp, v_f800), v_0000);
let one_or_two_bytes_bitmask = _mm256_movemask_epi8(one_or_two_bytes_bytemask) as u32;
if one_or_two_bytes_bitmask == 0xFFFF_FFFF {
let v_1f00 = _mm256_set1_epi16(0x1F00);
let v_003f = _mm256_set1_epi16(0x003F);
let t0 = _mm256_slli_epi16::<2>(inp);
let t1 = _mm256_and_si256(t0, v_1f00);
let t2 = _mm256_and_si256(inp, v_003f);
let t3 = _mm256_or_si256(t1, t2);
let t4 = _mm256_or_si256(t3, v_c080);
let utf8_unpacked = _mm256_blendv_epi8(t4, inp, one_byte_bytemask);
let m0 = one_byte_bitmask & 0x5555_5555;
let m1 = m0 >> 7;
let m2 = (m1 | m0) & 0x00FF_00FF;
let row = &UTF16_TO_UTF8_PACK_1_2[(m2 & 0xFF) as usize];
let row2 = &UTF16_TO_UTF8_PACK_1_2[((m2 >> 16) & 0xFF) as usize];
let shuffle = unsafe { _mm_loadu_si128(row.as_ptr().add(1).cast()) };
let shuffle2 = unsafe { _mm_loadu_si128(row2.as_ptr().add(1).cast()) };
let utf8_packed = _mm256_shuffle_epi8(utf8_unpacked, _mm256_setr_m128i(shuffle, shuffle2));
unsafe { _mm_storeu_si128(out.as_mut_ptr().add(written).cast(), _mm256_castsi256_si128(utf8_packed)) };
written += row[0] as usize;
unsafe { _mm_storeu_si128(out.as_mut_ptr().add(written).cast(), _mm256_extractf128_si256::<1>(utf8_packed)) };
written += row2[0] as usize;
consumed += 16;
continue;
}
let surrogates_bytemask = _mm256_cmpeq_epi16(_mm256_and_si256(inp, v_f800), v_d800);
let surrogates_bitmask = _mm256_movemask_epi8(surrogates_bytemask) as u32;
if surrogates_bitmask == 0x0000_0000 {
let dup_even = _mm256_setr_epi16(
0x0000, 0x0202, 0x0404, 0x0606, 0x0808, 0x0A0A, 0x0C0C, 0x0E0E, 0x0000, 0x0202, 0x0404, 0x0606, 0x0808, 0x0A0A, 0x0C0C, 0x0E0E
);
let t0 = _mm256_shuffle_epi8(inp, dup_even);
let t1 = _mm256_and_si256(t0, _mm256_set1_epi16(0b0011111101111111_u16 as _));
let t2 = _mm256_or_si256(t1, _mm256_set1_epi16(0b1000000000000000_u16 as _));
let s0 = _mm256_srli_epi16::<4>(inp);
let s1 = _mm256_and_si256(s0, _mm256_set1_epi16(0b0000111111111100_u16 as _));
let s2 = _mm256_maddubs_epi16(s1, _mm256_set1_epi16(0x0140));
let s3 = _mm256_or_si256(s2, _mm256_set1_epi16(0b1100000011100000_u16 as _));
let m0 = _mm256_andnot_si256(one_or_two_bytes_bytemask, _mm256_set1_epi16(0b0100000000000000_u16 as _));
let s4 = _mm256_xor_si256(s3, m0);
let out0 = _mm256_unpacklo_epi16(t2, s4);
let out1 = _mm256_unpackhi_epi16(t2, s4);
let mask = (one_byte_bitmask & 0x5555_5555) | (one_or_two_bytes_bitmask & 0xAAAA_AAAA);
let row0 = &UTF16_TO_UTF8_PACK_1_2_3[(mask & 0xFF) as usize];
let shuffle0 = unsafe { _mm_loadu_si128(row0.as_ptr().add(1).cast()) };
let utf8_0 = _mm_shuffle_epi8(_mm256_castsi256_si128(out0), shuffle0);
let row1 = &UTF16_TO_UTF8_PACK_1_2_3[((mask >> 8) & 0xFF) as usize];
let shuffle1 = unsafe { _mm_loadu_si128(row1.as_ptr().add(1).cast()) };
let utf8_1 = _mm_shuffle_epi8(_mm256_castsi256_si128(out1), shuffle1);
let row2 = &UTF16_TO_UTF8_PACK_1_2_3[((mask >> 16) & 0xFF) as usize];
let shuffle2 = unsafe { _mm_loadu_si128(row2.as_ptr().add(1).cast()) };
let utf8_2 = _mm_shuffle_epi8(_mm256_extractf128_si256::<1>(out0), shuffle2);
let row3 = &UTF16_TO_UTF8_PACK_1_2_3[((mask >> 24) & 0xFF) as usize];
let shuffle3 = unsafe { _mm_loadu_si128(row3.as_ptr().add(1).cast()) };
let utf8_3 = _mm_shuffle_epi8(_mm256_extractf128_si256::<1>(out1), shuffle3);
unsafe { _mm_storeu_si128(out.as_mut_ptr().add(written).cast(), utf8_0) };
written += row0[0] as usize;
unsafe { _mm_storeu_si128(out.as_mut_ptr().add(written).cast(), utf8_1) };
written += row1[0] as usize;
unsafe { _mm_storeu_si128(out.as_mut_ptr().add(written).cast(), utf8_2) };
written += row2[0] as usize;
unsafe { _mm_storeu_si128(out.as_mut_ptr().add(written).cast(), utf8_3) };
written += row3[0] as usize;
consumed += 16;
} else {
let mut forward = 15;
if buf.len() - consumed < forward + 1 {
forward = buf.len() - consumed - 1;
}
match utf16_to_utf8_scalar(&buf[consumed..], forward, &mut out[written..]) {
Ok((k, l)) => {
consumed += k;
written += l;
}
Err((k, l)) => return Err((consumed + k, written + l))
}
}
}
Ok((consumed, written))
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
fn utf16_to_utf8_neon(buf: &[u16], out: &mut [u8]) -> Result<(usize, usize), (usize, usize)> {
use core::{arch::aarch64::*, mem::transmute};
let v_f800 = vmovq_n_u16(0xF800_u16 as _);
let v_d800 = vmovq_n_u16(0xD800_u16 as _);
let v_c080 = vmovq_n_u16(0xC080_u16 as _);
const SAFETY_MARGIN: usize = 12;
assert!(buf.len() > 16 + SAFETY_MARGIN);
let mut consumed = 0;
let mut written = 0;
while buf.len() - consumed >= 16 + SAFETY_MARGIN {
let mut inp = unsafe { vld1q_u16(buf.as_ptr().add(consumed).cast()) };
if vmaxvq_u16(inp) <= 0x7F {
let nextinp = unsafe { vld1q_u16(buf.as_ptr().add(consumed + 8).cast()) };
if vmaxvq_u16(nextinp) > 0x7F {
let utf8_packed = vmovn_u16(inp);
unsafe { vst1_u8(out.as_mut_ptr().add(written).cast(), utf8_packed) };
consumed += 8;
written += 8;
inp = nextinp;
} else {
let utf8_packed = vmovn_high_u16(vmovn_u16(inp), nextinp);
unsafe { vst1q_u8(out.as_mut_ptr().add(written).cast(), utf8_packed) };
consumed += 16;
written += 16;
continue;
}
}
if vmaxvq_u16(inp) <= 0x7FF {
let v_1f00 = vmovq_n_u16(0x1f00);
let v_003f = vmovq_n_u16(0x003f);
let t0 = vshlq_n_u16::<2>(inp);
let t1 = vandq_u16(t0, v_1f00);
let t2 = vandq_u16(inp, v_003f);
let t3 = vorrq_u16(t1, t2);
let t4 = vorrq_u16(t3, v_c080);
let v_007f = vmovq_n_u16(0x007F);
let one_byte_bytemask = vcleq_u16(inp, v_007f);
let utf8_unpacked = vreinterpretq_u8_u16(vbslq_u16(one_byte_bytemask, inp, t4));
let m2 = vaddvq_u16(vandq_u16(one_byte_bytemask, unsafe { transmute([0x0001_u16, 0x0004, 0x0010, 0x0040, 0x0002, 0x0008, 0x0020, 0x0080]) }));
let row = &UTF16_TO_UTF8_PACK_1_2[(m2 & 0xFF) as usize];
let shuffle = unsafe { vld1q_u8(row.as_ptr().add(1)) };
let utf8_packed = vqtbl1q_u8(utf8_unpacked, shuffle);
unsafe { vst1q_u8(out.as_mut_ptr().add(written).cast(), utf8_packed) };
consumed += 8;
written += row[0] as usize;
continue;
}
let surrogates_bytemask = vceqq_u16(vandq_u16(inp, v_f800), v_d800);
if vget_lane_f64::<0>(vreinterpret_f64_u8(vshrn_n_u16::<4>(surrogates_bytemask))) == 0.0 {
let dup_even: uint16x8_t = unsafe { transmute([0x0000_u16, 0x0202, 0x0404, 0x0606, 0x0808, 0x0A0A, 0x0C0C, 0x0E0E]) };
let t0 = vreinterpretq_u16_u8(vqtbl1q_u8(vreinterpretq_u8_u16(inp), vreinterpretq_u8_u16(dup_even)));
let t1 = vandq_u16(t0, vmovq_n_u16(0b0011111101111111));
let t2 = vorrq_u16(t1, vmovq_n_u16(0b1000000000000000));
let s0 = vshrq_n_u16::<12>(inp);
let s1 = vandq_u16(inp, vmovq_n_u16(0b0000111111000000));
let s1s = vshlq_n_u16::<2>(s1);
let s2 = vorrq_u16(s0, s1s);
let s3 = vorrq_u16(s2, vmovq_n_u16(0b1100000011100000));
let v_07ff = vmovq_n_u16(0x07FF);
let one_or_two_bytes_bytemask = vcleq_u16(inp, v_07ff);
let m0 = vbicq_u16(vmovq_n_u16(0b0100000000000000), one_or_two_bytes_bytemask);
let s4 = veorq_u16(s3, m0);
let out0 = vreinterpretq_u8_u16(vzip1q_u16(t2, s4));
let out1 = vreinterpretq_u8_u16(vzip2q_u16(t2, s4));
let v_007f = vmovq_n_u16(0x007F);
let one_byte_bytemask = vcleq_u16(inp, v_007f);
let onemask: uint16x8_t = unsafe { transmute([0x0001_u16, 0x0004, 0x0010, 0x0040, 0x0100, 0x0400, 0x1000, 0x4000]) };
let twomask: uint16x8_t = unsafe { transmute([0x0002_u16, 0x0008, 0x0020, 0x0080, 0x0200, 0x0800, 0x2000, 0x8000]) };
let combined = vorrq_u16(vandq_u16(one_byte_bytemask, onemask), vandq_u16(one_or_two_bytes_bytemask, twomask));
let mask = vaddvq_u16(combined);
let row0 = &UTF16_TO_UTF8_PACK_1_2_3[(mask & 0xFF) as usize];
let shuffle0 = unsafe { vld1q_u8(row0.as_ptr().add(1)) };
let utf8_0 = vqtbl1q_u8(out0, shuffle0);
let row1 = &UTF16_TO_UTF8_PACK_1_2_3[((mask >> 8) & 0xFF) as usize];
let shuffle1 = unsafe { vld1q_u8(row1.as_ptr().add(1)) };
let utf8_1 = vqtbl1q_u8(out1, shuffle1);
unsafe { vst1q_u8(out.as_mut_ptr().add(written).cast(), utf8_0) };
written += row0[0] as usize;
unsafe { vst1q_u8(out.as_mut_ptr().add(written).cast(), utf8_1) };
written += row1[0] as usize;
consumed += 8;
} else {
let mut forward = 15;
if buf.len() - consumed < forward + 1 {
forward = buf.len() - consumed - 1;
}
match utf16_to_utf8_scalar(&buf[consumed..], forward, &mut out[written..]) {
Ok((k, l)) => {
consumed += k;
written += l;
}
Err((k, l)) => return Err((consumed + k, written + l))
}
}
}
Ok((consumed, written))
}
pub fn utf16_to_utf8<'o>(buf: &[u16], out: &'o mut [u8]) -> Result<&'o mut str, usize> {
#[allow(unused_mut)]
let (mut consumed, mut written) = (0, 0);
if buf.len() > 64 {
#[cfg(target_arch = "x86_64")]
if util::is_x86_feature_detected!("avx2") {
let (k, l) = unsafe { utf16_to_utf8_avx2(buf, out).map_err(|(consumed, _written)| consumed)? };
consumed += k;
written += l;
}
#[cfg(target_arch = "aarch64")]
if util::is_aarch64_feature_detected!("neon") {
let (k, l) = unsafe { utf16_to_utf8_neon(buf, out).map_err(|(consumed, _written)| consumed)? };
consumed += k;
written += l;
}
}
if consumed != buf.len() {
let (k, l) = utf16_to_utf8_scalar(&buf[consumed..], buf.len() - consumed, &mut out[written..]).map_err(|(k, _l)| consumed + k)?;
assert_eq!(consumed + k, buf.len());
Ok(unsafe { str::from_utf8_unchecked_mut(&mut out[..written + l]) })
} else {
Ok(unsafe { str::from_utf8_unchecked_mut(&mut out[..written]) })
}
}
fn latin1_to_utf8_scalar(buf: &[u8], out: &mut [u8]) -> usize {
let mut written = 0;
for &byte in buf {
if byte & 0x80 == 0 {
out[written] = byte;
written += 1;
} else {
out[written] = (byte >> 6) | 0b11000000;
out[written + 1] = (byte & 0b111111) | 0b10000000;
written += 2;
}
}
written
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
fn latin1_to_utf8_avx2(buf: &[u8], out: &mut [u8]) -> (usize, usize) {
use core::arch::x86_64::*;
let v_0000 = _mm256_setzero_si256();
let v_c080 = _mm256_set1_epi16(0xC080_u16 as _);
let v_ff80 = _mm256_set1_epi16(0xFF80_u16 as _);
const SAFETY_MARGIN: usize = 12;
assert!(buf.len() > 16 + SAFETY_MARGIN);
let mut consumed = 0;
let mut written = 0;
while buf.len() - consumed >= 16 + SAFETY_MARGIN {
let inp = unsafe { _mm_loadu_si128(buf.as_ptr().add(consumed).cast()) };
let v_80 = _mm_set1_epi8(0x80_u8 as _);
if _mm_testz_si128(inp, v_80) == 1 {
unsafe { _mm_storeu_si128(out.as_mut_ptr().add(written).cast(), inp) };
consumed += 16;
written += 16;
continue;
}
let inp = _mm256_cvtepu8_epi16(inp);
let v_1f00 = _mm256_set1_epi16(0x1F00_u16 as _);
let v_003f = _mm256_set1_epi16(0x003F_u16 as _);
let t0 = _mm256_slli_epi16::<2>(inp);
let t1 = _mm256_and_si256(t0, v_1f00);
let t2 = _mm256_and_si256(inp, v_003f);
let t3 = _mm256_or_si256(t1, t2);
let t4 = _mm256_or_si256(t3, v_c080);
let one_byte_bytemask = _mm256_cmpeq_epi16(_mm256_and_si256(inp, v_ff80), v_0000);
let one_byte_bitmask = _mm256_movemask_epi8(one_byte_bytemask) as u32;
let utf8_unpacked = _mm256_blendv_epi8(t4, inp, one_byte_bytemask);
let m0 = one_byte_bitmask & 0x5555_5555;
let m1 = m0 >> 7;
let m2 = (m1 | m0) & 0x00FF_00FF;
let row = &UTF16_TO_UTF8_PACK_1_2[(m2 & 0xFF) as usize];
let row2 = &UTF16_TO_UTF8_PACK_1_2[((m2 >> 16) & 0xFF) as usize];
let shuffle = unsafe { _mm_loadu_si128(row.as_ptr().add(1).cast()) };
let shuffle2 = unsafe { _mm_loadu_si128(row2.as_ptr().add(1).cast()) };
let utf8_packed = _mm256_shuffle_epi8(utf8_unpacked, _mm256_setr_m128i(shuffle, shuffle2));
unsafe { _mm_storeu_si128(out.as_mut_ptr().add(written).cast(), _mm256_castsi256_si128(utf8_packed)) };
written += row[0] as usize;
unsafe { _mm_storeu_si128(out.as_mut_ptr().add(written).cast(), _mm256_extractf128_si256::<1>(utf8_packed)) };
written += row2[0] as usize;
consumed += 16;
continue;
}
(consumed, written)
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
fn latin1_to_utf8_neon(buf: &[u8], out: &mut [u8]) -> (usize, usize) {
use core::{arch::aarch64::*, mem::transmute};
const SAFETY_MARGIN: usize = 8;
assert!(buf.len() > 16 + SAFETY_MARGIN);
let mut consumed = 0;
let mut written = 0;
while buf.len() - consumed >= 16 + SAFETY_MARGIN {
let inp = unsafe { vld1q_u8(buf.as_ptr().add(consumed).cast()) };
if vmaxvq_u8(inp) <= 0x7F {
unsafe { vst1q_u8(out.as_mut_ptr().add(written).cast(), inp) };
consumed += 16;
written += 16;
continue;
}
let inp = vmovl_u8(vget_low_u8(inp));
let v_1f00 = vmovq_n_u16(0x1F00);
let v_003f = vmovq_n_u16(0x003F);
let v_c080 = vmovq_n_u16(0xC080);
let t0 = vshlq_n_u16::<2>(inp);
let t1 = vandq_u16(t0, v_1f00);
let t2 = vandq_u16(inp, v_003f);
let t3 = vorrq_u16(t1, t2);
let t4 = vorrq_u16(t3, v_c080);
let v_007f = vmovq_n_u16(0x007F);
let one_byte_bytemask = vcleq_u16(inp, v_007f);
let utf8_unpacked = vreinterpretq_u8_u16(vbslq_u16(one_byte_bytemask, inp, t4));
let m2 = vaddvq_u16(vandq_u16(one_byte_bytemask, unsafe { transmute([0x0001_u16, 0x0004, 0x0010, 0x0040, 0x0002, 0x0008, 0x0020, 0x0080]) }));
let row = &UTF16_TO_UTF8_PACK_1_2[m2 as usize];
let shuffle = unsafe { vld1q_u8(row.as_ptr().add(1).cast()) };
let utf8_packed = vqtbl1q_u8(utf8_unpacked, shuffle);
unsafe { vst1q_u8(out.as_mut_ptr().add(written).cast(), utf8_packed) };
written += row[0] as usize;
consumed += 8;
continue;
}
(consumed, written)
}
pub fn latin1_to_utf8<'o>(buf: &[u8], out: &'o mut [u8]) -> &'o mut str {
#[allow(unused_mut)]
let (mut consumed, mut written) = (0, 0);
if buf.len() > 64 {
#[cfg(target_arch = "x86_64")]
if util::is_x86_feature_detected!("avx2") {
let (k, l) = unsafe { latin1_to_utf8_avx2(buf, out) };
consumed += k;
written += l;
}
#[cfg(target_arch = "aarch64")]
if util::is_aarch64_feature_detected!("neon") {
let (k, l) = unsafe { latin1_to_utf8_neon(buf, out) };
consumed += k;
written += l;
}
}
if consumed != buf.len() {
let l = latin1_to_utf8_scalar(&buf[consumed..], &mut out[written..]);
unsafe { str::from_utf8_unchecked_mut(&mut out[..written + l]) }
} else {
unsafe { str::from_utf8_unchecked_mut(&mut out[..written]) }
}
}
#[cfg(test)]
mod tests {
use std::{fs, path::Path};
use super::{latin1_to_utf8, utf16_to_utf8};
fn do_utf16_test(path: impl AsRef<Path>) {
let bytes = fs::read(path).unwrap();
let mut encoded = Vec::<u16>::new();
encoded.extend(
bytes[2..]
.chunks_exact(2)
.map(|x| unsafe { <[_; 2]>::try_from(x).unwrap_unchecked() })
.map(|x| u16::from_le_bytes(x))
);
let text = String::from_utf16(&encoded).unwrap();
let mut out = vec![0u8; encoded.len() * 3];
let out = utf16_to_utf8(&encoded, &mut out).unwrap();
assert_eq!(text.as_str(), out);
}
#[test]
fn test_utf16_to_utf8() {
for lang in ["bn", "en", "fa", "ja", "pt"] {
println!("testing: {lang}");
do_utf16_test(format!("tests/data/uhdr/{lang}.txt"));
}
}
#[test]
fn test_utf16_to_utf8_invalid() {
let inp = &[0xD834, 0xDD1E, 0x006D, 0x0075, 0x0073, 0x0069, 0x0063];
let mut out = vec![0u8; inp.len() * 3];
assert!(utf16_to_utf8(inp, &mut out).is_ok());
let inp = &[0xD834, 0xDD1E, 0x006D, 0x0075, 0x0073, 0xDD1E, 0x0069, 0x0063, 0xD834];
let mut out = vec![0u8; inp.len() * 3];
assert!(utf16_to_utf8(inp, &mut out).is_err());
let inp = [0xD834, 0xDD1E, 0x006D, 0x0075, 0x0073, 0xDD1E, 0x0069, 0x0063, 0xD834].repeat(64);
let mut out = vec![0u8; inp.len() * 3];
assert!(utf16_to_utf8(&inp, &mut out).is_err());
}
fn do_latin1_test(path: impl AsRef<Path>) {
let bytes = fs::read(path).unwrap();
let text = bytes.iter().map(|c| *c as char).collect::<String>();
let mut out = vec![0u8; text.len() * 2];
let out = latin1_to_utf8(&bytes, &mut out);
assert_eq!(text.as_str(), out);
}
#[test]
fn test_latin1_to_utf8() {
for lang in ["pt_latin1", "en_ascii"] {
println!("testing: {lang}");
do_latin1_test(format!("tests/data/uhdr/{lang}.txt"));
}
}
}