Skip to main content

ttf_view/platform/
encodings.rs

1use crate::platform::{PlatformId, define_u16_ids};
2use std::borrow::Cow;
3
4#[cfg(feature = "non-standard-encodings")]
5use encoding_rs::{BIG5, EUC_KR, Encoding as EncodingRs, GB18030, SHIFT_JIS};
6
7#[derive(Debug, Copy, Hash)]
8#[derive_const(Clone, PartialEq, Eq)]
9#[repr(u16)]
10pub enum EncodingId {
11    Unicode(UnicodeEncodingId) = 0,
12    Macintosh(MacintoshEncodingId) = 1,
13    Iso(IsoEncodingId) = 2,
14    Windows(WindowsEncodingId) = 3,
15    Custom(u8) = 4,
16}
17
18define_u16_ids! {
19    pub enum UnicodeEncodingId {
20        Unicode1_0 = 0, Unicode1_1 = 1, IsoIec10646 = 2, Unicode2_0BmpOnly = 3, Unicode2_0Full = 4,
21        UnicodeVariations = 5, UnicodeFull = 6,
22    }
23    pub enum MacintoshEncodingId {
24        Roman = 0, Japanese = 1, ChineseTraditional = 2, Korean = 3, Arabic = 4, Hebrew = 5,
25        Greek = 6, Russian = 7, RSymbol = 8, Devanagari = 9, Gurmukhi = 10, Gujarati = 11,
26        Odia = 12, Bangla = 13, Tamil = 14, Telugu = 15, Kannada = 16, Malayalam = 17,
27        Sinhalese = 18, Burmese = 19, Khmer = 20, Thai = 21, Laotian = 22, Georgian = 23,
28        Armenian = 24, ChineseSimplified = 25, Tibetan = 26, Mongolian = 27, Geez = 28,
29        Slavic = 29, Vietnamese = 30, Sindhi = 31, Uninterpreted = 32,
30    }
31    pub enum IsoEncodingId {
32        SevenBitAscii = 0, Iso10646 = 1, Iso8859_1 = 2,
33    }
34    pub enum WindowsEncodingId {
35        Symbol = 0, UnicodeBmp = 1, ShiftJis = 2, Prc = 3, Big5 = 4, Wansung = 5, Johab = 6,
36        Reserved7 = 7, Reserved8 = 8, Reserved9 = 9, UnicodeFull = 10,
37    }
38}
39
40#[derive(Debug, thiserror::Error)]
41#[derive_const(Clone, PartialEq, Eq)]
42pub enum EncodingError {
43    #[error("unknown platform id")]
44    UnknownPlatform,
45    #[error("unknown encoding id")]
46    UnknownEncoding,
47    #[error("string data could not be decoded")]
48    MalformedString,
49    #[error("this encoding is not implemented")]
50    Unimplemented,
51}
52
53impl EncodingId {
54    pub const fn new(platform_id: u16, encoding_id: u16) -> Result<Self, EncodingError> {
55        let platform = PlatformId::new(platform_id).ok_or(EncodingError::UnknownPlatform)?;
56        platform.encoding(encoding_id).ok_or(EncodingError::UnknownEncoding)
57    }
58
59    pub const fn platform_id(&self) -> u16 {
60        match self {
61            Self::Unicode(_) => 0,
62            Self::Macintosh(_) => 1,
63            Self::Iso(_) => 2,
64            Self::Windows(_) => 3,
65            Self::Custom(_) => 4,
66        }
67    }
68    pub const fn encoding_id(&self) -> u16 {
69        match self {
70            Self::Unicode(x) => x.get(),
71            Self::Macintosh(x) => x.get(),
72            Self::Iso(x) => x.get(),
73            Self::Windows(x) => x.get(),
74            Self::Custom(x) => *x as u16,
75        }
76    }
77    pub fn name(&self) -> Cow<'static, str> {
78        Cow::Borrowed(match self {
79            Self::Unicode(x) => x.name(),
80            Self::Macintosh(x) => x.name(),
81            Self::Iso(x) => x.name(),
82            Self::Windows(x) => x.name(),
83            Self::Custom(charset) => {
84                return Cow::Owned(format!("Charset {}", charset));
85            },
86        })
87    }
88
89    pub fn decode_utf16be(&self, bytes: &[u8]) -> Result<String, EncodingError> {
90        use EncodingError::*;
91
92        fn decode_utf16be(bytes: &[u8]) -> Result<String, EncodingError> {
93            String::from_utf16be(bytes).map_err(|_| MalformedString)
94        }
95
96        #[cfg(feature = "non-standard-encodings")]
97        fn decode(bytes: &[u8], encoding: &'static EncodingRs) -> Result<String, EncodingError> {
98            let decoded = encoding.decode_without_bom_handling_and_without_replacement(bytes);
99            decoded.ok_or(MalformedString).map(|x| x.into_owned())
100        }
101
102        match *self {
103            // All Unicode encodings use UTF-16BE
104            Self::Unicode(_) => decode_utf16be(bytes),
105
106            Self::Macintosh(encoding) => {
107                use MacintoshEncodingId::*;
108
109                match encoding {
110                    Roman => Ok(decode_macos_roman(bytes)), // Mac OS Roman
111                    _ => Err(Unimplemented),
112                }
113            },
114
115            Self::Iso(_) => Err(Unimplemented),
116
117            Self::Windows(encoding) => {
118                use WindowsEncodingId::*;
119
120                match encoding {
121                    Symbol | UnicodeBmp | UnicodeFull => decode_utf16be(bytes),
122
123                    #[cfg(feature = "non-standard-encodings")]
124                    ShiftJis => decode(bytes, SHIFT_JIS), // aka. CP 932
125                    #[cfg(feature = "non-standard-encodings")]
126                    Prc => decode(bytes, GB18030), // aka. GB 18030 / CP 54936
127                    #[cfg(feature = "non-standard-encodings")]
128                    Big5 => decode(bytes, BIG5), // aka. CP 950
129                    #[cfg(feature = "non-standard-encodings")]
130                    Wansung => decode(bytes, EUC_KR), // aka. EUC-KR / CP 949
131
132                    // #[cfg(feature = "non-standard-encodings")]
133                    // Johab => , // aka. KS C 5601-1992 / CP 1361
134                    _ => Err(Unimplemented),
135                }
136            },
137
138            Self::Custom(_) => Err(Unimplemented),
139        }
140    }
141}
142
143const impl From<UnicodeEncodingId> for EncodingId {
144    fn from(value: UnicodeEncodingId) -> Self {
145        Self::Unicode(value)
146    }
147}
148const impl From<MacintoshEncodingId> for EncodingId {
149    fn from(value: MacintoshEncodingId) -> Self {
150        Self::Macintosh(value)
151    }
152}
153const impl From<IsoEncodingId> for EncodingId {
154    fn from(value: IsoEncodingId) -> Self {
155        Self::Iso(value)
156    }
157}
158const impl From<WindowsEncodingId> for EncodingId {
159    fn from(value: WindowsEncodingId) -> Self {
160        Self::Windows(value)
161    }
162}
163
164// We'll include the MacOS Roman table by default, since it's used often.
165fn decode_macos_roman(bytes: &[u8]) -> String {
166    bytes.iter().copied().map(map_macos_roman).collect()
167}
168fn map_macos_roman(byte: u8) -> char {
169    if byte < 0x80 {
170        byte as char
171    } else {
172        unsafe {
173            char::from_u32_unchecked(*MACOS_ROMAN.get_unchecked((byte - 0x80) as usize) as u32)
174        }
175    }
176}
177
178const MACOS_ROMAN: [u16; 128] = [
179    196, 197, 199, 201, 209, 214, 220, 225, 224, 226, 228, 227, 229, 231, 233, 232, 234, 235, 237,
180    236, 238, 239, 241, 243, 242, 244, 246, 245, 250, 249, 251, 252, 8224, 176, 162, 163, 167,
181    8226, 182, 223, 174, 169, 8482, 180, 168, 8800, 198, 216, 8734, 177, 8804, 8805, 165, 181,
182    8706, 8721, 8719, 960, 8747, 170, 186, 937, 230, 248, 191, 161, 172, 8730, 402, 8776, 8710,
183    171, 187, 8230, 160, 192, 195, 213, 338, 339, 8211, 8212, 8220, 8221, 8216, 8217, 247, 9674,
184    255, 376, 8260, 8364, 8249, 8250, 64257, 64258, 8225, 183, 8218, 8222, 8240, 194, 202, 193,
185    203, 200, 205, 206, 207, 204, 211, 212, 63743, 210, 218, 219, 217, 305, 710, 732, 175, 728,
186    729, 730, 184, 733, 731, 711,
187];