Skip to main content

qubit_text_codec/charset/
utf16.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 crate::{
11    ByteOrder,
12    Unicode,
13    UnicodeBom,
14};
15
16/// Namespace for UTF-16 constants and code-unit classification helpers.
17pub enum Utf16 {}
18
19impl Utf16 {
20    /// Maximum number of UTF-16 code units needed for one Unicode scalar value.
21    pub const MAX_UNITS_PER_CHAR: usize = 2;
22
23    /// Maximum number of serialized UTF-16 bytes needed for one Unicode scalar value.
24    pub const MAX_BYTES_PER_CHAR: usize = 4;
25
26    /// Tests whether a UTF-16 unit is a high surrogate.
27    ///
28    /// # Parameters
29    ///
30    /// - `unit`: The UTF-16 unit to test.
31    ///
32    /// # Returns
33    ///
34    /// Returns `true` if `unit` is in `0xD800..=0xDBFF`.
35    #[inline]
36    pub const fn is_high_surrogate(unit: u16) -> bool {
37        (unit as u32) >= Unicode::HIGH_SURROGATE_MIN && (unit as u32) <= Unicode::HIGH_SURROGATE_MAX
38    }
39
40    /// Tests whether a UTF-16 unit is a low surrogate.
41    ///
42    /// # Parameters
43    ///
44    /// - `unit`: The UTF-16 unit to test.
45    ///
46    /// # Returns
47    ///
48    /// Returns `true` if `unit` is in `0xDC00..=0xDFFF`.
49    #[inline]
50    pub const fn is_low_surrogate(unit: u16) -> bool {
51        (unit as u32) >= Unicode::LOW_SURROGATE_MIN && (unit as u32) <= Unicode::LOW_SURROGATE_MAX
52    }
53
54    /// Tests whether a UTF-16 unit is any surrogate.
55    ///
56    /// # Parameters
57    ///
58    /// - `unit`: The UTF-16 unit to test.
59    ///
60    /// # Returns
61    ///
62    /// Returns `true` if `unit` is in `0xD800..=0xDFFF`.
63    #[inline]
64    pub const fn is_surrogate(unit: u16) -> bool {
65        (unit as u32) >= Unicode::SURROGATE_MIN && (unit as u32) <= Unicode::SURROGATE_MAX
66    }
67
68    /// Tests whether a UTF-16 unit encodes a scalar value by itself.
69    ///
70    /// # Parameters
71    ///
72    /// - `unit`: The UTF-16 unit to test.
73    ///
74    /// # Returns
75    ///
76    /// Returns `true` for non-surrogate units.
77    #[inline]
78    pub const fn is_single_unit(unit: u16) -> bool {
79        !Self::is_surrogate(unit)
80    }
81
82    /// Tests whether two UTF-16 units form a surrogate pair.
83    ///
84    /// # Parameters
85    ///
86    /// - `high`: The candidate high surrogate.
87    /// - `low`: The candidate low surrogate.
88    ///
89    /// # Returns
90    ///
91    /// Returns `true` if `high` is a high surrogate and `low` is a low surrogate.
92    #[inline]
93    pub const fn is_surrogate_pair(high: u16, low: u16) -> bool {
94        Self::is_high_surrogate(high) && Self::is_low_surrogate(low)
95    }
96
97    /// Returns the UTF-16 unit count needed for `ch`.
98    ///
99    /// # Parameters
100    ///
101    /// - `ch`: The character to size.
102    ///
103    /// # Returns
104    ///
105    /// Returns `1` for BMP scalar values and `2` for supplementary scalar values.
106    #[inline]
107    pub const fn unit_len(ch: char) -> usize {
108        if (ch as u32) >= Unicode::SUPPLEMENTARY_MIN {
109            2
110        } else {
111            1
112        }
113    }
114
115    /// Returns the UTF-16 unit count needed for a raw code point.
116    ///
117    /// # Parameters
118    ///
119    /// - `code_point`: The raw code point to size.
120    ///
121    /// # Returns
122    ///
123    /// Returns `Some(1)` or `Some(2)` for scalar values and `None` otherwise.
124    #[inline]
125    pub const fn unit_len_code_point(code_point: u32) -> Option<usize> {
126        if !Unicode::is_scalar_value(code_point) {
127            None
128        } else if code_point >= Unicode::SUPPLEMENTARY_MIN {
129            Some(2)
130        } else {
131            Some(1)
132        }
133    }
134
135    /// Composes a surrogate pair into a Unicode code point.
136    ///
137    /// # Parameters
138    ///
139    /// - `high`: The high surrogate.
140    /// - `low`: The low surrogate.
141    ///
142    /// # Returns
143    ///
144    /// Returns `Some(code_point)` when the pair is valid, or `None` otherwise.
145    #[inline]
146    pub const fn compose_pair(high: u16, low: u16) -> Option<u32> {
147        if Self::is_surrogate_pair(high, low) {
148            let high_payload = (high as u32) - Unicode::HIGH_SURROGATE_MIN;
149            let low_payload = (low as u32) - Unicode::LOW_SURROGATE_MIN;
150            Some((high_payload << 10) + low_payload + Unicode::SUPPLEMENTARY_MIN)
151        } else {
152            None
153        }
154    }
155
156    /// Returns the high surrogate for a supplementary code point.
157    ///
158    /// # Parameters
159    ///
160    /// - `code_point`: The supplementary code point.
161    ///
162    /// # Returns
163    ///
164    /// Returns `Some(high_surrogate)` for supplementary code points and `None` otherwise.
165    #[inline]
166    pub const fn high_surrogate(code_point: u32) -> Option<u16> {
167        if Unicode::is_supplementary(code_point) {
168            Some((((code_point - Unicode::SUPPLEMENTARY_MIN) >> 10) + Unicode::HIGH_SURROGATE_MIN) as u16)
169        } else {
170            None
171        }
172    }
173
174    /// Returns the low surrogate for a supplementary code point.
175    ///
176    /// # Parameters
177    ///
178    /// - `code_point`: The supplementary code point.
179    ///
180    /// # Returns
181    ///
182    /// Returns `Some(low_surrogate)` for supplementary code points and `None` otherwise.
183    #[inline]
184    pub const fn low_surrogate(code_point: u32) -> Option<u16> {
185        if Unicode::is_supplementary(code_point) {
186            Some((((code_point - Unicode::SUPPLEMENTARY_MIN) & 0x3ff) + Unicode::LOW_SURROGATE_MIN) as u16)
187        } else {
188            None
189        }
190    }
191
192    /// Detects a UTF-16 BOM and returns its byte order.
193    ///
194    /// # Parameters
195    ///
196    /// - `bytes`: The byte buffer to inspect.
197    ///
198    /// # Returns
199    ///
200    /// Returns `Some(ByteOrder)` for UTF-16 BOM prefixes, or `None` otherwise.
201    #[inline]
202    pub fn detect_bom(bytes: &[u8]) -> Option<ByteOrder> {
203        match UnicodeBom::detect(bytes) {
204            Some(UnicodeBom::Utf16BigEndian) => Some(ByteOrder::BigEndian),
205            Some(UnicodeBom::Utf16LittleEndian) => Some(ByteOrder::LittleEndian),
206            _ => None,
207        }
208    }
209}