qubit_codec_text/codec/utf16_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 Utf16,
21};
22use core::num::NonZeroUsize;
23use qubit_codec::Codec;
24
25/// Combined byte-serialized UTF-16 codec.
26///
27/// The codec uses one configured byte order for both decoding and encoding. It
28/// does not detect, consume, or emit a BOM automatically; callers should use
29/// [`crate::UnicodeBom`] when a byte stream may carry an explicit BOM.
30///
31/// # Examples
32///
33/// ```rust
34/// use qubit_codec_text::{
35/// ByteOrder,
36/// CharsetCodec,
37/// CharsetEncodeProbe,
38/// Codec,
39/// Charset,
40/// Utf16,
41/// Utf16ByteCodec,
42/// };
43///
44/// let codec = Utf16ByteCodec::new(ByteOrder::LittleEndian);
45/// assert_eq!(Charset::UTF_16LE, codec.charset());
46/// assert_eq!(Utf16::MAX_BYTES_PER_CHAR, codec.max_units_per_value().get());
47///
48/// let mut output = [0_u8; Utf16::MAX_BYTES_PER_CHAR];
49/// let written = codec.encode_len('😀', 0).expect("mappable");
50/// unsafe {
51/// codec.encode_unchecked(&'😀', &mut output, 0).expect("buffer fits");
52/// }
53/// let (value, consumed) = unsafe {
54/// codec.decode_unchecked(&output[..written], 0).expect("valid UTF-16LE")
55/// };
56/// assert_eq!(('😀', written), (value, consumed.get()));
57/// ```
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub struct Utf16ByteCodec {
60 /// Byte order used by both encoder and decoder paths.
61 byte_order: ByteOrder,
62}
63
64impl Utf16ByteCodec {
65 /// Creates a byte-serialized UTF-16 codec.
66 ///
67 /// # Parameters
68 ///
69 /// - `byte_order`: The byte order used by the byte buffer.
70 ///
71 /// # Returns
72 ///
73 /// Returns a UTF-16 byte codec.
74 #[inline(always)]
75 #[must_use]
76 pub const fn new(byte_order: ByteOrder) -> Self {
77 Self { byte_order }
78 }
79
80 /// Returns the configured byte order.
81 ///
82 /// # Returns
83 ///
84 /// Returns the byte order used by this codec.
85 #[inline(always)]
86 #[must_use]
87 pub const fn byte_order(self) -> ByteOrder {
88 self.byte_order
89 }
90
91 /// Returns the fixed-endian UTF-16 charset descriptor.
92 ///
93 /// # Returns
94 ///
95 /// Returns [`Charset::UTF_16LE`] or [`Charset::UTF_16BE`] according to this
96 /// codec's configured byte order.
97 #[inline(always)]
98 #[must_use]
99 pub const fn charset(self) -> Charset {
100 Charset::from_utf16_byte_order(self.byte_order)
101 }
102}
103
104impl CharsetCodec for Utf16ByteCodec {
105 /// Returns the fixed-endian UTF-16 charset for the configured byte order.
106 ///
107 /// # Returns
108 ///
109 /// Returns [`Charset::UTF_16BE`] when configured with
110 /// `ByteOrder::BigEndian`, otherwise [`Charset::UTF_16LE`].
111 #[inline(always)]
112 fn charset(&self) -> Charset {
113 Charset::from_utf16_byte_order(self.byte_order)
114 }
115}
116
117impl CharsetEncodeProbe for Utf16ByteCodec {
118 /// Encodes one Unicode scalar value into UTF-16 bytes at `index`.
119 ///
120 /// # Arguments
121 ///
122 /// * `ch` - The Unicode scalar value to encode.
123 /// * `index` - Input character index used for error context.
124 ///
125 /// # Returns
126 ///
127 /// `Ok(usize)` with the required bytes (`2` for BMP and `4` for
128 /// supplementary).
129 #[inline(always)]
130 fn encode_len(
131 &self,
132 ch: char,
133 _index: usize,
134 ) -> CharsetEncodeResult<usize> {
135 Ok(Utf16::unit_len(ch) * 2)
136 }
137}
138
139unsafe impl Codec for Utf16ByteCodec {
140 type Value = char;
141 type Unit = u8;
142 type DecodeError = CharsetDecodeError;
143 type EncodeError = CharsetEncodeError;
144
145 #[inline(always)]
146 fn min_units_per_value(&self) -> NonZeroUsize {
147 // SAFETY: 2 is non-zero.
148 unsafe { NonZeroUsize::new_unchecked(2) }
149 }
150
151 #[inline(always)]
152 fn max_units_per_value(&self) -> NonZeroUsize {
153 // SAFETY: UTF-16 byte encoding uses at least one two-byte unit.
154 unsafe { NonZeroUsize::new_unchecked(Utf16::MAX_BYTES_PER_CHAR) }
155 }
156
157 #[inline(always)]
158 unsafe fn decode_unchecked(
159 &self,
160 input: &[u8],
161 index: usize,
162 ) -> CharsetDecodeResult<(char, 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, ch.len_utf16() * 2);
178 debug_assert!(written <= output.len() - index);
179 Ok(written)
180 }
181}
182
183/// Decodes the first UTF-16 character from a closed byte buffer.
184///
185/// The input bytes are interpreted with `byte_order`, then decoded using UTF-16
186/// surrogate rules.
187///
188/// # Arguments
189///
190/// * `input` - UTF-16 encoded byte slice. Callers using closed-prefix decoding
191/// provide at least [`Utf16::MAX_BYTES_PER_CHAR`] readable bytes unless EOF
192/// has been reached.
193/// * `index` - Start offset in `input` bytes; must be `<= input.len()`.
194/// * `byte_order` - Byte order used to read UTF-16 units.
195///
196/// # Returns
197///
198/// Returns the decoded character and the non-zero number of consumed bytes.
199///
200/// # Errors
201///
202/// * `CharsetDecodeErrorKind::InvalidInputIndex` when `index` is greater than
203/// `input.len()`.
204/// * `CharsetDecodeErrorKind::MalformedSequence` for invalid UTF-16 byte
205/// sequences or malformed surrogate usage.
206/// * `CharsetDecodeErrorKind::IncompleteSequence` when EOF appears before a
207/// complete UTF-16 unit or surrogate pair is available.
208fn decode_bytes_prefix(
209 input: &[u8],
210 index: usize,
211 byte_order: ByteOrder,
212) -> CharsetDecodeResult<(char, NonZeroUsize)> {
213 let charset = Charset::from_utf16_byte_order(byte_order);
214 if index > input.len() {
215 let kind = CharsetDecodeErrorKind::InvalidInputIndex {
216 input_len: input.len(),
217 };
218 return Err(CharsetDecodeError::new(charset, kind, index));
219 }
220 let available = input.len() - index;
221 if available < 2 {
222 let kind = CharsetDecodeErrorKind::IncompleteSequence {
223 required: 2,
224 available,
225 };
226 return Err(CharsetDecodeError::new(charset, kind, index));
227 }
228 let first = read_ordered_u16(input, index, byte_order);
229 if Utf16::is_high_surrogate(first) {
230 if available < 4 {
231 let kind = CharsetDecodeErrorKind::IncompleteSequence {
232 required: 4,
233 available,
234 };
235 return Err(CharsetDecodeError::new(charset, kind, index));
236 }
237 let second = read_ordered_u16(input, index + 2, byte_order);
238 match Utf16::compose_pair(first, second).and_then(Unicode::to_char) {
239 Some(ch) => {
240 // SAFETY: 4 is non-zero.
241 Ok((ch, unsafe { NonZeroUsize::new_unchecked(4) }))
242 }
243 None => {
244 let kind = CharsetDecodeErrorKind::MalformedSequence {
245 value: Some(second as u32),
246 };
247 Err(CharsetDecodeError::new(
248 charset,
249 kind,
250 required_index(index, 2),
251 )
252 .with_consumed(4))
253 }
254 }
255 } else if Utf16::is_low_surrogate(first) {
256 let kind = CharsetDecodeErrorKind::MalformedSequence {
257 value: Some(first as u32),
258 };
259 Err(CharsetDecodeError::new(charset, kind, index).with_consumed(2))
260 } else {
261 let ch = char::from_u32(first as u32)
262 .expect("non-surrogate UTF-16 unit is a scalar value");
263 // SAFETY: 2 is non-zero.
264 Ok((ch, unsafe { NonZeroUsize::new_unchecked(2) }))
265 }
266}
267
268/// Encodes one character into byte-serialized UTF-16 at `index` in `output`.
269///
270/// # Arguments
271///
272/// * `ch` - The character to encode.
273/// * `output` - Byte destination.
274/// * `byte_order` - Byte order for writing UTF-16 units.
275/// * `index` - Start offset in `output` bytes; must be `<= output.len()`.
276///
277/// # Returns
278///
279/// `Ok(usize)` with the number of bytes written (`2` for BMP, `4` for
280/// supplementary).
281///
282/// # Errors
283///
284/// * `CharsetEncodeErrorKind::BufferTooSmall` when output bytes from `index`
285/// are insufficient.
286fn encode_bytes_char(
287 ch: char,
288 output: &mut [u8],
289 byte_order: ByteOrder,
290 index: usize,
291) -> CharsetEncodeResult<usize> {
292 let charset = Charset::from_utf16_byte_order(byte_order);
293 if index > output.len() {
294 let kind = CharsetEncodeErrorKind::BufferTooSmall {
295 required: required_index(index, 2),
296 available: 0,
297 };
298 return Err(CharsetEncodeError::new(charset, kind, index));
299 }
300 let required = Utf16::unit_len(ch) * 2;
301 let available = output.len() - index;
302 if available < required {
303 let kind = CharsetEncodeErrorKind::BufferTooSmall {
304 required: required_index(index, required),
305 available,
306 };
307 return Err(CharsetEncodeError::new(charset, kind, index));
308 }
309 let code_point = ch as u32;
310 if required == 2 {
311 write_ordered_u16(output, index, code_point as u16, byte_order);
312 } else {
313 let high = Utf16::high_surrogate(code_point)
314 .expect("supplementary scalar has high surrogate");
315 let low = Utf16::low_surrogate(code_point)
316 .expect("supplementary scalar has low surrogate");
317 write_ordered_u16(output, index, high, byte_order);
318 write_ordered_u16(output, index + 2, low, byte_order);
319 }
320 Ok(required)
321}
322
323#[inline(always)]
324const fn required_index(index: usize, required_units: usize) -> usize {
325 match index.checked_add(required_units) {
326 Some(required) => required,
327 None => usize::MAX,
328 }
329}
330
331/// Reads one endian-aware `u16` value from an already checked byte slice.
332///
333/// # Parameters
334///
335/// - `input`: Source byte slice.
336/// - `index`: Start byte offset. The caller must guarantee two bytes are
337/// available from this offset.
338/// - `byte_order`: Byte order used to interpret the two bytes.
339///
340/// # Returns
341///
342/// Returns the decoded UTF-16 unit.
343#[inline(always)]
344fn read_ordered_u16(input: &[u8], index: usize, byte_order: ByteOrder) -> u16 {
345 let bytes = [input[index], input[index + 1]];
346 match byte_order {
347 ByteOrder::BigEndian => u16::from_be_bytes(bytes),
348 ByteOrder::LittleEndian => u16::from_le_bytes(bytes),
349 }
350}
351
352/// Writes one endian-aware `u16` value into an already checked byte slice.
353///
354/// # Parameters
355///
356/// - `output`: Destination byte slice.
357/// - `index`: Start byte offset. The caller must guarantee two bytes are
358/// writable from this offset.
359/// - `unit`: UTF-16 unit to write.
360/// - `byte_order`: Byte order used to serialize the unit.
361#[inline(always)]
362fn write_ordered_u16(
363 output: &mut [u8],
364 index: usize,
365 unit: u16,
366 byte_order: ByteOrder,
367) {
368 let bytes = match byte_order {
369 ByteOrder::BigEndian => unit.to_be_bytes(),
370 ByteOrder::LittleEndian => unit.to_le_bytes(),
371 };
372 output[index..index + 2].copy_from_slice(&bytes);
373}