Skip to main content

qubit_codec_text/codec/
utf32_byte_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    ByteOrder,
10    Charset,
11    CharsetCodec,
12    CharsetDecodeError,
13    CharsetDecodeErrorKind,
14    CharsetDecodeResult,
15    CharsetEncodeError,
16    CharsetEncodeErrorKind,
17    CharsetEncodeProbe,
18    CharsetEncodeResult,
19    Unicode,
20    Utf32,
21};
22use qubit_codec::Codec;
23
24/// Combined byte-serialized UTF-32 codec.
25///
26/// The codec uses one configured byte order for both decoding and encoding. It
27/// does not detect, consume, or emit a BOM automatically; callers should use
28/// [`crate::UnicodeBom`] when a byte stream may carry an explicit BOM.
29///
30/// # Examples
31///
32/// ```rust
33/// use qubit_codec_text::{
34///     ByteOrder,
35///     CharsetCodec,
36///     CharsetEncodeProbe,
37///     Codec,
38///     Charset,
39///     Utf32,
40///     Utf32ByteCodec,
41/// };
42///
43/// let codec = Utf32ByteCodec::new(ByteOrder::BigEndian);
44/// assert_eq!(Charset::UTF_32BE, codec.charset());
45/// assert_eq!(Utf32::MAX_BYTES_PER_CHAR, codec.max_units_per_value().get());
46///
47/// let mut output = [0_u8; Utf32::MAX_BYTES_PER_CHAR];
48/// let written = codec.encode_len('中', 0).expect("mappable");
49/// unsafe {
50///     codec.encode_unchecked(&'中', &mut output, 0).expect("buffer fits");
51/// }
52/// let (value, consumed) = unsafe {
53///     codec.decode_unchecked(&output[..written], 0).expect("valid UTF-32BE")
54/// };
55/// assert_eq!(('中', written), (value, consumed.get()));
56/// ```
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58pub struct Utf32ByteCodec {
59    /// Byte order used by both encoder and decoder paths.
60    byte_order: ByteOrder,
61}
62
63impl Utf32ByteCodec {
64    /// Creates a byte-serialized UTF-32 codec.
65    ///
66    /// # Parameters
67    ///
68    /// - `byte_order`: The byte order used by the byte buffer.
69    ///
70    /// # Returns
71    ///
72    /// Returns a UTF-32 byte codec.
73    #[must_use]
74    #[inline(always)]
75    pub const fn new(byte_order: ByteOrder) -> Self {
76        Self { byte_order }
77    }
78
79    /// Returns the configured byte order.
80    ///
81    /// # Returns
82    ///
83    /// Returns the byte order used by this codec.
84    #[must_use]
85    #[inline(always)]
86    pub const fn byte_order(self) -> ByteOrder {
87        self.byte_order
88    }
89
90    /// Returns the fixed-endian UTF-32 charset descriptor.
91    ///
92    /// # Returns
93    ///
94    /// Returns [`Charset::UTF_32LE`] or [`Charset::UTF_32BE`] according to this
95    /// codec's configured byte order.
96    #[must_use]
97    #[inline(always)]
98    pub const fn charset(self) -> Charset {
99        Charset::from_utf32_byte_order(self.byte_order)
100    }
101}
102
103impl CharsetCodec for Utf32ByteCodec {
104    /// Returns the fixed-endian UTF-32 charset for the configured byte order.
105    ///
106    /// # Returns
107    ///
108    /// Returns [`Charset::UTF_32BE`] when configured with
109    /// `ByteOrder::BigEndian`, otherwise [`Charset::UTF_32LE`].
110    #[inline(always)]
111    fn charset(&self) -> Charset {
112        Charset::from_utf32_byte_order(self.byte_order)
113    }
114}
115
116impl CharsetEncodeProbe for Utf32ByteCodec {
117    /// Encodes one Unicode scalar value into UTF-32 bytes at `index`.
118    ///
119    /// # Arguments
120    ///
121    /// * `ch` - The Unicode scalar value to encode.
122    /// * `index` - Input character index used for error context.
123    ///
124    /// # Returns
125    ///
126    /// Always returns `Ok(4)`.
127    #[inline(always)]
128    fn encode_len(
129        &self,
130        _ch: char,
131        _index: usize,
132    ) -> CharsetEncodeResult<usize> {
133        Ok(Utf32::MAX_BYTES_PER_CHAR)
134    }
135}
136
137unsafe impl Codec for Utf32ByteCodec {
138    type Value = char;
139    type Unit = u8;
140    type DecodeError = CharsetDecodeError;
141    type EncodeError = CharsetEncodeError;
142
143    #[inline(always)]
144    fn min_units_per_value(&self) -> core::num::NonZeroUsize {
145        // SAFETY: 4 is non-zero.
146        unsafe { core::num::NonZeroUsize::new_unchecked(4) }
147    }
148
149    #[inline(always)]
150    fn max_units_per_value(&self) -> core::num::NonZeroUsize {
151        // SAFETY: UTF-32 byte encoding always uses four bytes.
152        unsafe {
153            core::num::NonZeroUsize::new_unchecked(Utf32::MAX_BYTES_PER_CHAR)
154        }
155    }
156
157    #[inline(always)]
158    unsafe fn decode_unchecked(
159        &self,
160        input: &[u8],
161        index: usize,
162    ) -> CharsetDecodeResult<(char, core::num::NonZeroUsize)> {
163        let (ch, consumed) =
164            decode_bytes_prefix(input, index, self.byte_order)?;
165        debug_assert!(consumed.get() <= input.len() - index);
166        Ok((ch, consumed))
167    }
168
169    #[inline(always)]
170    unsafe fn encode_unchecked(
171        &self,
172        ch: &char,
173        output: &mut [u8],
174        index: usize,
175    ) -> CharsetEncodeResult<usize> {
176        let written = encode_bytes_char(*ch, output, self.byte_order, index)?;
177        debug_assert_eq!(written, Utf32::MAX_BYTES_PER_CHAR);
178        debug_assert!(written <= output.len() - index);
179        Ok(written)
180    }
181}
182
183/// Decodes the first UTF-32 character from a closed byte buffer.
184///
185/// The input bytes are interpreted according to `byte_order`.
186///
187/// # Arguments
188///
189/// * `input` - UTF-32 encoded byte slice.
190/// * `index` - Start byte offset; must be `<= input.len()`.
191/// * `byte_order` - Byte order used to read a `u32` unit.
192///
193/// # Returns
194///
195/// Returns the decoded character and a non-zero count of `4` consumed bytes.
196///
197/// # Errors
198///
199/// * `CharsetDecodeErrorKind::InvalidInputIndex` when `index` is greater than
200///   `input.len()`.
201/// * `CharsetDecodeErrorKind::IncompleteSequence` when EOF appears before four
202///   bytes are available.
203/// * `CharsetDecodeErrorKind::InvalidCodePoint` when the decoded unit is not a
204///   valid scalar.
205#[inline]
206fn decode_bytes_prefix(
207    input: &[u8],
208    index: usize,
209    byte_order: ByteOrder,
210) -> CharsetDecodeResult<(char, core::num::NonZeroUsize)> {
211    let charset = Charset::from_utf32_byte_order(byte_order);
212    if index > input.len() {
213        let kind = CharsetDecodeErrorKind::InvalidInputIndex {
214            input_len: input.len(),
215        };
216        return Err(CharsetDecodeError::new(charset, kind, index));
217    }
218    let available = input.len() - index;
219    if available < 4 {
220        let kind = CharsetDecodeErrorKind::IncompleteSequence {
221            required: 4,
222            available,
223        };
224        return Err(CharsetDecodeError::new(charset, kind, index));
225    }
226    let unit = read_ordered_u32(input, index, byte_order);
227    match Unicode::to_char(unit) {
228        Some(ch) => {
229            // SAFETY: 4 is non-zero.
230            Ok((ch, unsafe { core::num::NonZeroUsize::new_unchecked(4) }))
231        }
232        None => {
233            let kind = CharsetDecodeErrorKind::InvalidCodePoint { value: unit };
234            Err(CharsetDecodeError::new(charset, kind, index).with_consumed(4))
235        }
236    }
237}
238
239/// Encodes one character into byte-serialized UTF-32 at `index` in `output`.
240///
241/// # Arguments
242///
243/// * `ch` - The character to encode.
244/// * `output` - Destination byte buffer.
245/// * `byte_order` - Byte order used to write the 4-byte representation.
246/// * `index` - Start byte offset; must satisfy `index <= output.len()`.
247///
248/// # Returns
249///
250/// `Ok(4)` on success, because UTF-32 always occupies exactly four bytes.
251///
252/// # Errors
253///
254/// * `CharsetEncodeErrorKind::BufferTooSmall` when output has fewer than four
255///   bytes from `index`.
256#[inline]
257fn encode_bytes_char(
258    ch: char,
259    output: &mut [u8],
260    byte_order: ByteOrder,
261    index: usize,
262) -> CharsetEncodeResult<usize> {
263    let charset = Charset::from_utf32_byte_order(byte_order);
264    if index > output.len() {
265        let kind = CharsetEncodeErrorKind::BufferTooSmall {
266            required: required_index(index, 4),
267            available: 0,
268        };
269        return Err(CharsetEncodeError::new(charset, kind, index));
270    }
271    let required = 4;
272    let available = output.len() - index;
273    if available < required {
274        let kind = CharsetEncodeErrorKind::BufferTooSmall {
275            required: required_index(index, required),
276            available,
277        };
278        return Err(CharsetEncodeError::new(charset, kind, index));
279    }
280    write_ordered_u32(output, index, ch as u32, byte_order);
281    Ok(4)
282}
283
284#[inline(always)]
285const fn required_index(index: usize, required_units: usize) -> usize {
286    match index.checked_add(required_units) {
287        Some(required) => required,
288        None => usize::MAX,
289    }
290}
291
292/// Reads one endian-aware `u32` value from an already checked byte slice.
293///
294/// # Parameters
295///
296/// - `input`: Source byte slice.
297/// - `index`: Start byte offset. The caller must guarantee four bytes are
298///   available from this offset.
299/// - `byte_order`: Byte order used to interpret the four bytes.
300///
301/// # Returns
302///
303/// Returns the decoded UTF-32 unit.
304#[inline(always)]
305fn read_ordered_u32(input: &[u8], index: usize, byte_order: ByteOrder) -> u32 {
306    let bytes = [
307        input[index],
308        input[index + 1],
309        input[index + 2],
310        input[index + 3],
311    ];
312    match byte_order {
313        ByteOrder::BigEndian => u32::from_be_bytes(bytes),
314        ByteOrder::LittleEndian => u32::from_le_bytes(bytes),
315    }
316}
317
318/// Writes one endian-aware `u32` value into an already checked byte slice.
319///
320/// # Parameters
321///
322/// - `output`: Destination byte slice.
323/// - `index`: Start byte offset. The caller must guarantee four bytes are
324///   writable from this offset.
325/// - `unit`: UTF-32 unit to write.
326/// - `byte_order`: Byte order used to serialize the unit.
327#[inline(always)]
328fn write_ordered_u32(
329    output: &mut [u8],
330    index: usize,
331    unit: u32,
332    byte_order: ByteOrder,
333) {
334    let bytes = match byte_order {
335        ByteOrder::BigEndian => unit.to_be_bytes(),
336        ByteOrder::LittleEndian => unit.to_le_bytes(),
337    };
338    output[index..index + 4].copy_from_slice(&bytes);
339}