use crate::{
Charset,
CharsetCodec,
CharsetDecodeError,
CharsetDecodeErrorKind,
CharsetDecodeResult,
CharsetEncodeError,
CharsetEncodeErrorKind,
CharsetEncodeProbe,
CharsetEncodeResult,
Unicode,
Utf8,
};
use qubit_codec::Codec;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct Utf8Codec;
impl Utf8Codec {
#[must_use]
#[inline(always)]
pub const fn charset(self) -> Charset {
Charset::UTF_8
}
}
impl CharsetCodec for Utf8Codec {
#[inline(always)]
fn charset(&self) -> Charset {
Charset::UTF_8
}
}
impl CharsetEncodeProbe for Utf8Codec {
#[inline(always)]
fn encode_len(
&self,
ch: char,
_index: usize,
) -> CharsetEncodeResult<usize> {
Ok(Utf8::byte_len(ch))
}
}
unsafe impl Codec for Utf8Codec {
type Value = char;
type Unit = u8;
type DecodeError = CharsetDecodeError;
type EncodeError = CharsetEncodeError;
#[inline(always)]
fn min_units_per_value(&self) -> core::num::NonZeroUsize {
core::num::NonZeroUsize::MIN
}
#[inline(always)]
fn max_units_per_value(&self) -> core::num::NonZeroUsize {
unsafe {
core::num::NonZeroUsize::new_unchecked(Utf8::MAX_UNITS_PER_CHAR)
}
}
#[inline(always)]
unsafe fn decode_unchecked(
&self,
input: &[u8],
index: usize,
) -> CharsetDecodeResult<(char, core::num::NonZeroUsize)> {
let (ch, consumed) = decode_prefix(input, index)?;
debug_assert!(consumed.get() <= input.len() - index);
Ok((ch, consumed))
}
#[inline(always)]
unsafe fn encode_unchecked(
&self,
ch: &char,
output: &mut [u8],
index: usize,
) -> CharsetEncodeResult<usize> {
let written = encode_char(*ch, output, index)?;
debug_assert_eq!(written, Utf8::byte_len(*ch));
debug_assert!(written <= output.len() - index);
Ok(written)
}
}
#[inline]
fn decode_prefix(
input: &[u8],
index: usize,
) -> CharsetDecodeResult<(char, core::num::NonZeroUsize)> {
if index > input.len() {
let kind = CharsetDecodeErrorKind::InvalidInputIndex {
input_len: input.len(),
};
return Err(CharsetDecodeError::new(Charset::UTF_8, kind, index));
}
if index == input.len() {
let kind = CharsetDecodeErrorKind::IncompleteSequence {
required: 1,
available: 0,
};
return Err(CharsetDecodeError::new(Charset::UTF_8, kind, index));
}
let first = input[index];
let length = match Utf8::byte_len_from_leading_byte(first) {
Some(length) => length,
None => {
let kind = CharsetDecodeErrorKind::MalformedSequence {
value: Some(first as u32),
};
return Err(CharsetDecodeError::new(Charset::UTF_8, kind, index));
}
};
if !has_units(input.len(), index, length) {
validate_partial(input, index)?;
let kind = CharsetDecodeErrorKind::IncompleteSequence {
required: length,
available: input.len() - index,
};
return Err(CharsetDecodeError::new(Charset::UTF_8, kind, index));
}
let code_point = match length {
1 => first as u32,
2 => decode_two(input, index)?,
3 => decode_three(input, index)?,
4 => decode_four(input, index)?,
_ => unreachable!("UTF-8 sequence length is limited to four bytes"),
};
let ch = Unicode::to_char(code_point)
.expect("well-formed UTF-8 decodes to a Unicode scalar");
Ok((
ch,
core::num::NonZeroUsize::new(length)
.expect("well-formed UTF-8 sequence has non-zero length"),
))
}
#[inline]
fn encode_char(
ch: char,
output: &mut [u8],
index: usize,
) -> CharsetEncodeResult<usize> {
if index > output.len() {
let kind = CharsetEncodeErrorKind::BufferTooSmall {
required: required_index(index, 1),
available: 0,
};
return Err(CharsetEncodeError::new(Charset::UTF_8, kind, index));
}
let length = Utf8::byte_len(ch);
let available = output.len() - index;
if available < length {
let kind = CharsetEncodeErrorKind::BufferTooSmall {
required: required_index(index, length),
available,
};
return Err(CharsetEncodeError::new(Charset::UTF_8, kind, index));
}
let mut scratch = [0_u8; Utf8::MAX_BYTES_PER_CHAR];
let encoded = ch.encode_utf8(&mut scratch);
output[index..index + length].copy_from_slice(encoded.as_bytes());
Ok(length)
}
#[inline(always)]
const fn has_units(len: usize, index: usize, required_units: usize) -> bool {
match index.checked_add(required_units) {
Some(end) => len >= end,
None => false,
}
}
#[inline(always)]
const fn required_index(index: usize, required_units: usize) -> usize {
match index.checked_add(required_units) {
Some(required) => required,
None => usize::MAX,
}
}
#[inline]
fn decode_two(input: &[u8], index: usize) -> CharsetDecodeResult<u32> {
let second = input[index + 1];
if !Utf8::is_continuation_byte(second) {
let kind = CharsetDecodeErrorKind::MalformedSequence {
value: Some(second as u32),
};
return Err(CharsetDecodeError::new(
Charset::UTF_8,
kind,
required_index(index, 1),
)
.with_consumed(2));
}
Ok((((input[index] & 0x1f) as u32) << 6) | ((second & 0x3f) as u32))
}
#[inline]
fn validate_partial(input: &[u8], index: usize) -> CharsetDecodeResult<()> {
if has_units(input.len(), index, 2)
&& !is_valid_second_byte(input[index], input[index + 1])
{
let kind = CharsetDecodeErrorKind::MalformedSequence {
value: Some(input[index + 1] as u32),
};
return Err(CharsetDecodeError::new(
Charset::UTF_8,
kind,
required_index(index, 1),
)
.with_consumed(2));
}
if has_units(input.len(), index, 3)
&& !Utf8::is_continuation_byte(input[index + 2])
{
let kind = CharsetDecodeErrorKind::MalformedSequence {
value: Some(input[index + 2] as u32),
};
return Err(CharsetDecodeError::new(
Charset::UTF_8,
kind,
required_index(index, 2),
)
.with_consumed(3));
}
Ok(())
}
#[inline(always)]
fn is_valid_second_byte(first: u8, second: u8) -> bool {
match first {
0xc2..=0xdf => Utf8::is_continuation_byte(second),
0xe0 => (0xa0..=0xbf).contains(&second),
0xed => (0x80..=0x9f).contains(&second),
0xe1..=0xec | 0xee..=0xef => Utf8::is_continuation_byte(second),
0xf0 => (0x90..=0xbf).contains(&second),
0xf1..=0xf3 => Utf8::is_continuation_byte(second),
0xf4 => (0x80..=0x8f).contains(&second),
_ => false,
}
}
#[inline]
fn decode_three(input: &[u8], index: usize) -> CharsetDecodeResult<u32> {
let first = input[index];
let second = input[index + 1];
let third = input[index + 2];
if !is_valid_second_byte(first, second) {
let kind = CharsetDecodeErrorKind::MalformedSequence {
value: Some(second as u32),
};
return Err(CharsetDecodeError::new(
Charset::UTF_8,
kind,
required_index(index, 1),
)
.with_consumed(2));
}
if !Utf8::is_continuation_byte(third) {
let kind = CharsetDecodeErrorKind::MalformedSequence {
value: Some(third as u32),
};
return Err(CharsetDecodeError::new(
Charset::UTF_8,
kind,
required_index(index, 2),
)
.with_consumed(3));
}
Ok((((first & 0x0f) as u32) << 12)
| (((second & 0x3f) as u32) << 6)
| ((third & 0x3f) as u32))
}
#[inline]
fn decode_four(input: &[u8], index: usize) -> CharsetDecodeResult<u32> {
let first = input[index];
let second = input[index + 1];
let third = input[index + 2];
let fourth = input[index + 3];
if !is_valid_second_byte(first, second) {
let kind = CharsetDecodeErrorKind::MalformedSequence {
value: Some(second as u32),
};
return Err(CharsetDecodeError::new(
Charset::UTF_8,
kind,
required_index(index, 1),
)
.with_consumed(2));
}
if !Utf8::is_continuation_byte(third) {
let kind = CharsetDecodeErrorKind::MalformedSequence {
value: Some(third as u32),
};
return Err(CharsetDecodeError::new(
Charset::UTF_8,
kind,
required_index(index, 2),
)
.with_consumed(3));
}
if !Utf8::is_continuation_byte(fourth) {
let kind = CharsetDecodeErrorKind::MalformedSequence {
value: Some(fourth as u32),
};
return Err(CharsetDecodeError::new(
Charset::UTF_8,
kind,
required_index(index, 3),
)
.with_consumed(4));
}
Ok((((first & 0x07) as u32) << 18)
| (((second & 0x3f) as u32) << 12)
| (((third & 0x3f) as u32) << 6)
| ((fourth & 0x3f) as u32))
}