Skip to main content

ferroui_base/media/fonts/tables/
encoding_id_extensions.rs

1// Derived from SixLabors.Fonts (Apache License 2.0), see NOTICE.md.
2
3use super::encoding_ids::EncodingIDs;
4
5/// The text encodings the table readers decode (the counterpart of the
6/// `System.Text.Encoding` instances the reference implementation selects).
7///
8/// Malformed input never fails: invalid sequences decode to U+FFFD.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub enum Encoding {
11    /// UTF-16, big-endian.
12    BigEndianUnicode,
13    /// UTF-8.
14    UTF8,
15}
16
17impl Encoding {
18    /// Decodes `bytes`.
19    pub fn get_string(&self, bytes: &[u8]) -> String {
20        match self {
21            Encoding::BigEndianUnicode => {
22                let pairs = bytes.chunks_exact(2);
23                let has_trailing_byte = !pairs.remainder().is_empty();
24                let units = pairs.map(|pair| u16::from_be_bytes([pair[0], pair[1]]));
25
26                let mut text: String = char::decode_utf16(units)
27                    .map(|unit| unit.unwrap_or(char::REPLACEMENT_CHARACTER))
28                    .collect();
29
30                // A dangling odd byte is an incomplete code unit.
31                if has_trailing_byte {
32                    text.push(char::REPLACEMENT_CHARACTER);
33                }
34
35                text
36            }
37            Encoding::UTF8 => String::from_utf8_lossy(bytes).into_owned(),
38        }
39    }
40}
41
42/// Converts encoding ID to text encoding.
43pub struct EncodingIDExtensions;
44
45impl EncodingIDExtensions {
46    /// Converts encoding ID to text encoding.
47    pub fn as_encoding(id: EncodingIDs) -> Encoding {
48        if id == EncodingIDs::Unicode11 || id == EncodingIDs::Unicode2 {
49            Encoding::BigEndianUnicode
50        } else {
51            Encoding::UTF8
52        }
53    }
54}
55
56impl EncodingIDs {
57    /// Converts encoding ID to text encoding.
58    #[inline]
59    pub fn as_encoding(self) -> Encoding {
60        EncodingIDExtensions::as_encoding(self)
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn selects_utf16_for_windows_unicode_encodings() {
70        assert_eq!(EncodingIDs::Unicode11.as_encoding(), Encoding::BigEndianUnicode);
71        assert_eq!(EncodingIDs::Unicode2.as_encoding(), Encoding::BigEndianUnicode);
72        assert_eq!(EncodingIDs::Unicode1.as_encoding(), Encoding::UTF8);
73        assert_eq!(EncodingIDs(77).as_encoding(), Encoding::UTF8);
74    }
75
76    #[test]
77    fn decodes_utf16_big_endian() {
78        // "A", U+1F600 (surrogate pair), then a lone high surrogate and a dangling byte.
79        let bytes = [0x00, 0x41, 0xD8, 0x3D, 0xDE, 0x00, 0xD8, 0x00, 0x42];
80
81        assert_eq!(Encoding::BigEndianUnicode.get_string(&bytes), "A\u{1F600}\u{FFFD}\u{FFFD}");
82        assert_eq!(Encoding::BigEndianUnicode.get_string(&[]), "");
83    }
84
85    #[test]
86    fn decodes_utf8_with_replacement() {
87        assert_eq!(Encoding::UTF8.get_string(b"Inter"), "Inter");
88        assert_eq!(Encoding::UTF8.get_string(&[b'a', 0xFF, b'b']), "a\u{FFFD}b");
89    }
90}