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}