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}