Skip to main content

qubit_text_codec/codec/
utf32_byte_codec.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use super::inner::utf32;
11use core::hash::{
12    Hash,
13    Hasher,
14};
15
16use crate::{
17    ByteOrder,
18    Charset,
19    CharsetCodec,
20    CharsetDecodeResult,
21    CharsetEncodeResult,
22    DecodeStatus,
23    Utf32,
24};
25
26/// Combined byte-serialized UTF-32 codec.
27///
28/// The codec uses one configured byte order for both decoding and encoding. It
29/// does not detect, consume, or emit a BOM automatically; callers should use
30/// [`crate::UnicodeBom`] when a byte stream may carry an explicit BOM.
31///
32/// # Examples
33///
34/// ```rust
35/// use qubit_text_codec::{
36///     ByteOrder,
37///     CharsetCodec,
38///     DecodeStatus,
39///     Charset,
40///     Utf32,
41///     Utf32ByteCodec,
42/// };
43///
44/// let codec = Utf32ByteCodec::new(ByteOrder::BigEndian);
45/// assert_eq!(Charset::UTF_32BE, codec.charset());
46/// assert_eq!(Utf32::MAX_BYTES_PER_CHAR, codec.max_units_per_char());
47///
48/// let mut output = [0_u8; Utf32::MAX_BYTES_PER_CHAR];
49/// let written = codec.encode_one('中', &mut output, 0).expect("buffer fits");
50/// assert_eq!(
51///     DecodeStatus::Complete {
52///         value: '中',
53///         consumed: written,
54///     },
55///     codec.decode_one(&output[..written], 0).expect("valid UTF-32BE"),
56/// );
57/// ```
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub struct Utf32ByteCodec {
60    /// Byte order used by both encoder and decoder paths.
61    byte_order: ByteOrder,
62}
63
64impl Hash for Utf32ByteCodec {
65    /// Hashes the fixed-endian UTF-32 charset identity.
66    ///
67    /// # Parameters
68    ///
69    /// - `state`: The hasher receiving this codec's identity.
70    fn hash<H>(&self, state: &mut H)
71    where
72        H: Hasher,
73    {
74        self.charset().hash(state);
75    }
76}
77
78impl Utf32ByteCodec {
79    /// Creates a byte-serialized UTF-32 codec.
80    ///
81    /// # Parameters
82    ///
83    /// - `byte_order`: The byte order used by the byte buffer.
84    ///
85    /// # Returns
86    ///
87    /// Returns a UTF-32 byte codec.
88    #[must_use]
89    #[inline]
90    pub const fn new(byte_order: ByteOrder) -> Self {
91        Self { byte_order }
92    }
93
94    /// Returns the configured byte order.
95    ///
96    /// # Returns
97    ///
98    /// Returns the byte order used by this codec.
99    #[must_use]
100    #[inline]
101    pub const fn byte_order(self) -> ByteOrder {
102        self.byte_order
103    }
104
105    /// Returns the fixed-endian UTF-32 charset descriptor.
106    ///
107    /// # Returns
108    ///
109    /// Returns [`Charset::UTF_32LE`] or [`Charset::UTF_32BE`] according to this
110    /// codec's configured byte order.
111    #[must_use]
112    #[inline]
113    pub const fn charset(self) -> Charset {
114        Charset::from_utf32_byte_order(self.byte_order)
115    }
116
117    /// Returns the maximum number of serialized UTF-32 bytes for one character.
118    ///
119    /// # Returns
120    ///
121    /// Returns [`Utf32::MAX_BYTES_PER_CHAR`].
122    #[must_use]
123    #[inline]
124    pub const fn max_units_per_char(self) -> usize {
125        Utf32::MAX_BYTES_PER_CHAR
126    }
127}
128
129impl CharsetCodec for Utf32ByteCodec {
130    type Unit = u8;
131    /// Returns the fixed-endian UTF-32 charset for the configured byte order.
132    ///
133    /// # Returns
134    ///
135    /// Returns [`Charset::UTF_32BE`] when configured with
136    /// `ByteOrder::BigEndian`, otherwise [`Charset::UTF_32LE`].
137    #[inline]
138    fn charset(&self) -> Charset {
139        Charset::from_utf32_byte_order(self.byte_order)
140    }
141
142    /// Returns the fixed size (4 bytes) for one serialized UTF-32 scalar value.
143    ///
144    /// # Returns
145    ///
146    /// Returns [`Utf32::MAX_BYTES_PER_CHAR`].
147    #[inline]
148    fn max_units_per_char(&self) -> usize {
149        Utf32::MAX_BYTES_PER_CHAR
150    }
151
152    /// Decodes one UTF-32 scalar value from a byte-prefixed UTF-32 stream.
153    ///
154    /// # Arguments
155    ///
156    /// * `input` - Byte-prefixed UTF-32 buffer.
157    /// * `index` - Start offset for parsing; must satisfy `index <= input.len()`.
158    ///
159    /// # Returns
160    ///
161    /// * `Ok(DecodeStatus::NeedMore { required, available })` when fewer than
162    ///   four bytes remain.
163    /// * `Ok(DecodeStatus::Complete { value, consumed })` when one character is decoded.
164    ///
165    /// # Errors
166    ///
167    /// * [`crate::CharsetDecodeErrorKind::MalformedSequence`] when byte index is invalid.
168    /// * [`crate::CharsetDecodeErrorKind::InvalidCodePoint`] when bytes decode
169    ///   to an invalid scalar.
170    fn decode_one(&self, input: &[u8], index: usize) -> CharsetDecodeResult<DecodeStatus> {
171        utf32::decode_bytes_prefix(input, index, self.byte_order)
172    }
173
174    /// Encodes one Unicode scalar value into UTF-32 bytes at `index`.
175    ///
176    /// # Arguments
177    ///
178    /// * `ch` - The Unicode scalar value to encode.
179    /// * `output` - Destination byte buffer.
180    /// * `index` - Start offset where 4 bytes are written; must satisfy
181    ///   `index <= output.len()`.
182    ///
183    /// # Returns
184    ///
185    /// Always returns `Ok(4)` on success.
186    ///
187    /// # Errors
188    ///
189    /// * [`crate::CharsetEncodeErrorKind::BufferTooSmall`] if fewer than 4 bytes
190    ///   remain in `output`.
191    fn encode_one(&self, ch: char, output: &mut [u8], index: usize) -> CharsetEncodeResult<usize> {
192        utf32::encode_bytes_char(ch, output, self.byte_order, index)
193    }
194}