Skip to main content

qubit_codec_text/codec/
utf32_u32_codec.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8use crate::{
9    Charset,
10    CharsetCodec,
11    CharsetDecodeError,
12    CharsetDecodeErrorKind,
13    CharsetDecodeResult,
14    CharsetEncodeError,
15    CharsetEncodeErrorKind,
16    CharsetEncodeProbe,
17    CharsetEncodeResult,
18    Unicode,
19    Utf32,
20};
21use qubit_codec::Codec;
22
23/// Combined UTF-32 `u32` code-unit codec.
24///
25/// `Utf32U32Codec` works with raw UTF-32 scalar-value units rather than
26/// serialized bytes. Use [`crate::Utf32ByteCodec`] for byte streams with an
27/// explicit byte order.
28///
29/// # Examples
30///
31/// ```rust
32/// use qubit_codec_text::{
33///     CharsetCodec,
34///     CharsetEncodeProbe,
35///     Codec,
36///     Charset,
37///     Utf32,
38///     Utf32U32Codec,
39/// };
40///
41/// let codec = Utf32U32Codec;
42/// assert_eq!(Charset::UTF_32, codec.charset());
43/// assert_eq!(Utf32::MAX_UNITS_PER_CHAR, codec.max_units_per_value().get());
44///
45/// let mut output = [0_u32; Utf32::MAX_UNITS_PER_CHAR];
46/// let written = codec.encode_len('中', 0).expect("mappable");
47/// unsafe {
48///     codec.encode_unchecked(&'中', &mut output, 0).expect("buffer fits");
49/// }
50/// let (value, consumed) = unsafe {
51///     codec.decode_unchecked(&output[..written], 0).expect("valid UTF-32")
52/// };
53/// assert_eq!(('中', written), (value, consumed.get()));
54/// ```
55#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
56pub struct Utf32U32Codec;
57
58impl Utf32U32Codec {
59    /// Returns the UTF-32 encoding descriptor.
60    ///
61    /// # Returns
62    ///
63    /// Returns [`Charset::UTF_32`].
64    #[must_use]
65    #[inline(always)]
66    pub const fn charset(self) -> Charset {
67        Charset::UTF_32
68    }
69}
70
71impl CharsetCodec for Utf32U32Codec {
72    /// Returns UTF-32 charset descriptor.
73    ///
74    /// # Returns
75    ///
76    /// Returns [`Charset::UTF_32`].
77    #[inline(always)]
78    fn charset(&self) -> Charset {
79        Charset::UTF_32
80    }
81}
82
83impl CharsetEncodeProbe for Utf32U32Codec {
84    /// Encodes one Unicode scalar value into a `u32` unit at `index`.
85    ///
86    /// # Arguments
87    ///
88    /// * `ch` - The Unicode scalar value to encode.
89    /// * `index` - Input character index used for error context.
90    ///
91    /// # Returns
92    ///
93    /// Always returns `Ok(1)`.
94    #[inline(always)]
95    fn encode_len(
96        &self,
97        _ch: char,
98        _index: usize,
99    ) -> CharsetEncodeResult<usize> {
100        Ok(Utf32::MAX_UNITS_PER_CHAR)
101    }
102}
103
104unsafe impl Codec for Utf32U32Codec {
105    type Value = char;
106    type Unit = u32;
107    type DecodeError = CharsetDecodeError;
108    type EncodeError = CharsetEncodeError;
109
110    #[inline(always)]
111    fn min_units_per_value(&self) -> core::num::NonZeroUsize {
112        core::num::NonZeroUsize::MIN
113    }
114
115    #[inline(always)]
116    fn max_units_per_value(&self) -> core::num::NonZeroUsize {
117        core::num::NonZeroUsize::MIN
118    }
119
120    #[inline(always)]
121    unsafe fn decode_unchecked(
122        &self,
123        input: &[u32],
124        index: usize,
125    ) -> CharsetDecodeResult<(char, core::num::NonZeroUsize)> {
126        let (ch, consumed) = decode_units_prefix(input, index)?;
127        debug_assert!(consumed.get() <= input.len() - index);
128        Ok((ch, consumed))
129    }
130
131    #[inline(always)]
132    unsafe fn encode_unchecked(
133        &self,
134        ch: &char,
135        output: &mut [u32],
136        index: usize,
137    ) -> CharsetEncodeResult<usize> {
138        let written = encode_units_char(*ch, output, index)?;
139        debug_assert_eq!(written, Utf32::MAX_UNITS_PER_CHAR);
140        debug_assert!(written <= output.len() - index);
141        Ok(written)
142    }
143}
144
145/// Decodes the first UTF-32 character from a closed `u32` buffer.
146///
147/// Each UTF-32 unit is interpreted as a Unicode scalar value directly.
148///
149/// # Arguments
150///
151/// * `input` - UTF-32 unit slice to decode from.
152/// * `index` - Start offset in `input`; must be `<= input.len()`.
153///
154/// # Returns
155///
156/// Returns the decoded character and `1` consumed unit.
157///
158/// # Errors
159///
160/// * `CharsetDecodeErrorKind::InvalidInputIndex` when `index` is greater than
161///   `input.len()`.
162/// * `CharsetDecodeErrorKind::IncompleteSequence` when EOF appears before one
163///   UTF-32 unit is available.
164/// * `CharsetDecodeErrorKind::InvalidCodePoint` when `input[index]` is not a
165///   valid scalar.
166#[inline]
167fn decode_units_prefix(
168    input: &[u32],
169    index: usize,
170) -> CharsetDecodeResult<(char, core::num::NonZeroUsize)> {
171    if index > input.len() {
172        let kind = CharsetDecodeErrorKind::InvalidInputIndex {
173            input_len: input.len(),
174        };
175        return Err(CharsetDecodeError::new(Charset::UTF_32, kind, index));
176    }
177    if index == input.len() {
178        let kind = CharsetDecodeErrorKind::IncompleteSequence {
179            required: 1,
180            available: 0,
181        };
182        return Err(CharsetDecodeError::new(Charset::UTF_32, kind, index));
183    }
184    match Unicode::to_char(input[index]) {
185        Some(ch) => Ok((ch, core::num::NonZeroUsize::MIN)),
186        None => {
187            let kind = CharsetDecodeErrorKind::InvalidCodePoint {
188                value: input[index],
189            };
190            Err(CharsetDecodeError::new(Charset::UTF_32, kind, index))
191        }
192    }
193}
194
195/// Encodes one character into a UTF-32 `u32` unit at `index` in `output`.
196///
197/// # Arguments
198///
199/// * `ch` - The character to encode.
200/// * `output` - Destination unit buffer.
201/// * `index` - Start offset in `output`; must satisfy `index < output.len()`.
202///
203/// # Returns
204///
205/// Always `Ok(1)` to indicate one unit was written.
206///
207/// # Errors
208///
209/// * `CharsetEncodeErrorKind::BufferTooSmall` when no unit can be written at
210///   `index`.
211#[inline]
212fn encode_units_char(
213    ch: char,
214    output: &mut [u32],
215    index: usize,
216) -> CharsetEncodeResult<usize> {
217    if index >= output.len() {
218        let kind = CharsetEncodeErrorKind::BufferTooSmall {
219            required: required_index(index, 1),
220            available: 0,
221        };
222        return Err(CharsetEncodeError::new(Charset::UTF_32, kind, index));
223    }
224    output[index] = ch as u32;
225    Ok(1)
226}
227
228#[inline(always)]
229const fn required_index(index: usize, required_units: usize) -> usize {
230    match index.checked_add(required_units) {
231        Some(required) => required,
232        None => usize::MAX,
233    }
234}