qubit_text_codec/codec/utf16_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::utf16;
11use core::hash::{
12 Hash,
13 Hasher,
14};
15
16use crate::{
17 ByteOrder,
18 Charset,
19 CharsetCodec,
20 CharsetDecodeResult,
21 CharsetEncodeResult,
22 DecodeStatus,
23 Utf16,
24};
25
26/// Combined byte-serialized UTF-16 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/// 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_char());
47///
48/// let mut output = [0_u8; Utf16::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-16LE"),
56/// );
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 Hash for Utf16ByteCodec {
65 /// Hashes the fixed-endian UTF-16 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 Utf16ByteCodec {
79 /// Creates a byte-serialized UTF-16 codec.
80 ///
81 /// # Parameters
82 ///
83 /// - `byte_order`: The byte order used by the byte buffer.
84 ///
85 /// # Returns
86 ///
87 /// Returns a UTF-16 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-16 charset descriptor.
106 ///
107 /// # Returns
108 ///
109 /// Returns [`Charset::UTF_16LE`] or [`Charset::UTF_16BE`] according to this
110 /// codec's configured byte order.
111 #[must_use]
112 #[inline]
113 pub const fn charset(self) -> Charset {
114 Charset::from_utf16_byte_order(self.byte_order)
115 }
116
117 /// Returns the maximum number of serialized UTF-16 bytes for one character.
118 ///
119 /// # Returns
120 ///
121 /// Returns [`Utf16::MAX_BYTES_PER_CHAR`].
122 #[must_use]
123 #[inline]
124 pub const fn max_units_per_char(self) -> usize {
125 Utf16::MAX_BYTES_PER_CHAR
126 }
127}
128
129impl CharsetCodec for Utf16ByteCodec {
130 type Unit = u8;
131 /// Returns the fixed-endian UTF-16 charset for the configured byte order.
132 ///
133 /// # Returns
134 ///
135 /// Returns [`Charset::UTF_16BE`] when configured with
136 /// `ByteOrder::BigEndian`, otherwise [`Charset::UTF_16LE`].
137 #[inline]
138 fn charset(&self) -> Charset {
139 Charset::from_utf16_byte_order(self.byte_order)
140 }
141
142 /// Returns the maximum number of UTF-16 bytes for a single encoded character.
143 ///
144 /// # Returns
145 ///
146 /// Returns [`Utf16::MAX_BYTES_PER_CHAR`].
147 #[inline]
148 fn max_units_per_char(&self) -> usize {
149 Utf16::MAX_BYTES_PER_CHAR
150 }
151
152 /// Decodes one UTF-16 scalar value from a byte-prefixed UTF-16 stream.
153 ///
154 /// # Arguments
155 ///
156 /// * `input` - Byte-prefixed UTF-16 buffer.
157 /// * `index` - Start offset for parsing; must satisfy `index <= input.len()`.
158 ///
159 /// # Returns
160 ///
161 /// * `Ok(DecodeStatus::NeedMore { required, available })` when the current slice
162 /// has only a partial unit/pair.
163 /// * `Ok(DecodeStatus::Complete { value, consumed })` when one Unicode scalar value
164 /// is decoded.
165 ///
166 /// # Errors
167 ///
168 /// * `CharsetDecodeError` when UTF-16 structure is malformed.
169 fn decode_one(&self, input: &[u8], index: usize) -> CharsetDecodeResult<DecodeStatus> {
170 utf16::decode_bytes_prefix(input, index, self.byte_order)
171 }
172
173 /// Encodes one Unicode scalar value into UTF-16 bytes at `index`.
174 ///
175 /// # Arguments
176 ///
177 /// * `ch` - The Unicode scalar value to encode.
178 /// * `output` - Destination byte buffer.
179 /// * `index` - Start offset where bytes are written; must satisfy
180 /// `index <= output.len()`.
181 ///
182 /// # Returns
183 ///
184 /// `Ok(usize)` with the number of written bytes (`2` for BMP and `4` for supplementary).
185 ///
186 /// # Errors
187 ///
188 /// * [`crate::CharsetEncodeErrorKind::BufferTooSmall`] if output does not
189 /// have enough space.
190 fn encode_one(&self, ch: char, output: &mut [u8], index: usize) -> CharsetEncodeResult<usize> {
191 utf16::encode_bytes_char(ch, output, self.byte_order, index)
192 }
193}