Skip to main content

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}