qubit_text_codec/encoding/charset.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 core::{
11 fmt,
12 hash::{
13 Hash,
14 Hasher,
15 },
16};
17
18use crate::ByteOrder;
19
20/// Identifies the charset associated with a codec or error.
21///
22/// A charset is represented by a stable normalized identifier, a display
23/// name, and accepted aliases. Equality and hashing use only the identifier, so
24/// display names and alias lists can evolve without changing identity.
25///
26/// # Examples
27///
28/// ```rust
29/// use qubit_text_codec::Charset;
30///
31/// const GBK: Charset = Charset::new("gbk", "GBK", &["cp936"]);
32///
33/// assert!(GBK.matches_label("CP936"));
34/// assert_eq!(GBK, Charset::new("gbk", "Chinese GBK", &[]));
35/// assert_eq!("GBK", GBK.to_string());
36/// ```
37#[derive(Clone, Copy, Debug)]
38pub struct Charset {
39 /// Stable normalized identifier used for identity comparison.
40 id: &'static str,
41 /// Human-friendly display name for logs and errors.
42 name: &'static str,
43 /// Static alias list accepted in label matching.
44 aliases: &'static [&'static str],
45}
46
47impl Charset {
48 /// US-ASCII text.
49 pub const ASCII: Self = Self::new("ascii", "ASCII", &["us-ascii"]);
50
51 /// ISO-8859-1 / Latin-1 text.
52 pub const ISO_8859_1: Self = Self::new(
53 "iso-8859-1",
54 "ISO-8859-1",
55 &["latin1", "latin-1", "iso8859-1", "csisolatin1", "iso_8859-1"],
56 );
57
58 /// UTF-8 text.
59 pub const UTF_8: Self = Self::new("utf-8", "UTF-8", &["utf8"]);
60
61 /// UTF-16 text.
62 pub const UTF_16: Self = Self::new("utf-16", "UTF-16", &["utf16"]);
63
64 /// UTF-16 text serialized in little-endian byte order.
65 pub const UTF_16LE: Self = Self::new("utf-16le", "UTF-16LE", &["utf16le", "utf16_le", "utf_16_le"]);
66
67 /// UTF-16 text serialized in big-endian byte order.
68 pub const UTF_16BE: Self = Self::new("utf-16be", "UTF-16BE", &["utf16be", "utf16_be", "utf_16_be"]);
69
70 /// UTF-32 text.
71 pub const UTF_32: Self = Self::new("utf-32", "UTF-32", &["utf32"]);
72
73 /// UTF-32 text serialized in little-endian byte order.
74 pub const UTF_32LE: Self = Self::new("utf-32le", "UTF-32LE", &["utf32le", "utf32_le", "utf_32_le"]);
75
76 /// UTF-32 text serialized in big-endian byte order.
77 pub const UTF_32BE: Self = Self::new("utf-32be", "UTF-32BE", &["utf32be", "utf32_be", "utf_32_be"]);
78
79 /// Creates a charset descriptor.
80 ///
81 /// # Parameters
82 ///
83 /// - `id`: Stable normalized identifier used for equality and hashing.
84 /// - `name`: Human-readable display name.
85 /// - `aliases`: Additional labels accepted for this charset.
86 ///
87 /// # Returns
88 ///
89 /// Returns a charset descriptor carrying the supplied metadata.
90 #[inline]
91 pub const fn new(id: &'static str, name: &'static str, aliases: &'static [&'static str]) -> Self {
92 Self { id, name, aliases }
93 }
94
95 /// Returns the stable normalized charset identifier.
96 ///
97 /// # Returns
98 ///
99 /// Returns the identifier used for equality and hashing.
100 #[inline]
101 pub const fn id(self) -> &'static str {
102 self.id
103 }
104
105 /// Returns a human-readable charset label.
106 ///
107 /// # Returns
108 ///
109 /// Returns the display name stored in this descriptor.
110 #[inline]
111 pub const fn name(self) -> &'static str {
112 self.name
113 }
114
115 /// Returns accepted aliases for this charset.
116 ///
117 /// # Returns
118 ///
119 /// Returns the static alias list stored in this descriptor.
120 #[inline]
121 pub const fn aliases(self) -> &'static [&'static str] {
122 self.aliases
123 }
124
125 /// Returns the UTF-16 charset with a fixed byte order.
126 ///
127 /// # Parameters
128 ///
129 /// - `byte_order`: The byte order used by the byte stream.
130 ///
131 /// # Returns
132 ///
133 /// Returns [`Self::UTF_16LE`] for little-endian byte order and
134 /// [`Self::UTF_16BE`] for big-endian byte order.
135 #[inline]
136 pub const fn from_utf16_byte_order(byte_order: ByteOrder) -> Self {
137 match byte_order {
138 ByteOrder::LittleEndian => Self::UTF_16LE,
139 ByteOrder::BigEndian => Self::UTF_16BE,
140 }
141 }
142
143 /// Returns the UTF-32 charset with a fixed byte order.
144 ///
145 /// # Parameters
146 ///
147 /// - `byte_order`: The byte order used by the byte stream.
148 ///
149 /// # Returns
150 ///
151 /// Returns [`Self::UTF_32LE`] for little-endian byte order and
152 /// [`Self::UTF_32BE`] for big-endian byte order.
153 #[inline]
154 pub const fn from_utf32_byte_order(byte_order: ByteOrder) -> Self {
155 match byte_order {
156 ByteOrder::LittleEndian => Self::UTF_32LE,
157 ByteOrder::BigEndian => Self::UTF_32BE,
158 }
159 }
160
161 /// Returns the fixed byte order represented by this charset.
162 ///
163 /// # Returns
164 ///
165 /// Returns `Some(ByteOrder)` for fixed-endian UTF-16 and UTF-32 charsets.
166 /// Returns `None` for UTF-8 and generic UTF-16/UTF-32 charsets.
167 #[inline]
168 pub fn byte_order(self) -> Option<ByteOrder> {
169 if self == Self::UTF_16LE || self == Self::UTF_32LE {
170 Some(ByteOrder::LittleEndian)
171 } else if self == Self::UTF_16BE || self == Self::UTF_32BE {
172 Some(ByteOrder::BigEndian)
173 } else {
174 None
175 }
176 }
177
178 /// Tests whether a label names this charset.
179 ///
180 /// # Parameters
181 ///
182 /// - `label`: The label to compare with this descriptor's identifier, display
183 /// name, and aliases.
184 ///
185 /// # Returns
186 ///
187 /// Returns `true` when `label` matches the identifier, display name, or one of
188 /// the aliases using ASCII case-insensitive comparison.
189 #[inline]
190 pub fn matches_label(self, label: &str) -> bool {
191 if label.eq_ignore_ascii_case(self.id) || label.eq_ignore_ascii_case(self.name) {
192 return true;
193 }
194 self.aliases.iter().any(|alias| label.eq_ignore_ascii_case(alias))
195 }
196}
197
198impl PartialEq for Charset {
199 /// Compares charsets by stable identifier.
200 ///
201 /// # Parameters
202 ///
203 /// - `other`: The descriptor to compare against.
204 ///
205 /// # Returns
206 ///
207 /// Returns `true` when both descriptors have the same identifier.
208 fn eq(&self, other: &Self) -> bool {
209 self.id == other.id
210 }
211}
212
213impl Eq for Charset {}
214
215impl Hash for Charset {
216 /// Hashes the stable encoding identifier.
217 ///
218 /// # Parameters
219 ///
220 /// - `state`: The hasher receiving this encoding's identity.
221 fn hash<H>(&self, state: &mut H)
222 where
223 H: Hasher,
224 {
225 self.id.hash(state);
226 }
227}
228
229impl fmt::Display for Charset {
230 /// Formats this charset label.
231 ///
232 /// # Parameters
233 ///
234 /// - `formatter`: The formatter receiving the label.
235 ///
236 /// # Errors
237 ///
238 /// Returns any formatting error reported by `formatter`.
239 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
240 formatter.write_str(self.name())
241 }
242}