Skip to main content

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}