1 //! UEFI character handling 2 //! 3 //! UEFI uses both Latin-1 and UCS-2 character encoding, this module implements 4 //! support for the associated character types. 5 6 use core::fmt::{self, Display, Formatter}; 7 8 /// Character conversion error 9 #[derive(Clone, Copy, Debug)] 10 pub struct CharConversionError; 11 12 impl Display for CharConversionError { fmt(&self, f: &mut Formatter<'_>) -> fmt::Result13 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 14 write!(f, "{self:?}") 15 } 16 } 17 18 #[cfg(feature = "unstable")] 19 impl core::error::Error for CharConversionError {} 20 21 /// A Latin-1 character 22 #[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Hash)] 23 #[repr(transparent)] 24 pub struct Char8(u8); 25 26 impl TryFrom<char> for Char8 { 27 type Error = CharConversionError; 28 try_from(value: char) -> Result<Self, Self::Error>29 fn try_from(value: char) -> Result<Self, Self::Error> { 30 let code_point = u32::from(value); 31 u8::try_from(code_point) 32 .map(Char8) 33 .map_err(|_| CharConversionError) 34 } 35 } 36 37 impl From<Char8> for char { from(char: Char8) -> Self38 fn from(char: Char8) -> Self { 39 Self::from(char.0) 40 } 41 } 42 43 impl From<u8> for Char8 { from(value: u8) -> Self44 fn from(value: u8) -> Self { 45 Self(value) 46 } 47 } 48 49 impl From<Char8> for u8 { from(char: Char8) -> Self50 fn from(char: Char8) -> Self { 51 char.0 52 } 53 } 54 55 impl fmt::Debug for Char8 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result56 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 57 <char as fmt::Debug>::fmt(&From::from(self.0), f) 58 } 59 } 60 61 impl fmt::Display for Char8 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 <char as fmt::Display>::fmt(&From::from(self.0), f) 64 } 65 } 66 67 impl PartialEq<char> for Char8 { eq(&self, other: &char) -> bool68 fn eq(&self, other: &char) -> bool { 69 u32::from(self.0) == u32::from(*other) 70 } 71 } 72 73 /// Latin-1 version of the NUL character 74 pub const NUL_8: Char8 = Char8(0); 75 76 /// An UCS-2 code point 77 #[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Hash)] 78 #[repr(transparent)] 79 pub struct Char16(u16); 80 81 impl Char16 { 82 /// Creates a UCS-2 character from a Rust character without checks. 83 /// 84 /// # Safety 85 /// The caller must be sure that the character is valid. 86 #[must_use] from_u16_unchecked(val: u16) -> Self87 pub const unsafe fn from_u16_unchecked(val: u16) -> Self { 88 Self(val) 89 } 90 91 /// Checks if the value is within the ASCII range. 92 #[must_use] is_ascii(&self) -> bool93 pub const fn is_ascii(&self) -> bool { 94 self.0 <= 127 95 } 96 } 97 98 impl TryFrom<char> for Char16 { 99 type Error = CharConversionError; 100 try_from(value: char) -> Result<Self, Self::Error>101 fn try_from(value: char) -> Result<Self, Self::Error> { 102 let code_point = u32::from(value); 103 u16::try_from(code_point) 104 .map(Char16) 105 .map_err(|_| CharConversionError) 106 } 107 } 108 109 impl From<Char16> for char { from(char: Char16) -> Self110 fn from(char: Char16) -> Self { 111 u32::from(char.0).try_into().unwrap() 112 } 113 } 114 115 impl TryFrom<u16> for Char16 { 116 type Error = CharConversionError; 117 try_from(value: u16) -> Result<Self, Self::Error>118 fn try_from(value: u16) -> Result<Self, Self::Error> { 119 // We leverage char's TryFrom<u32> impl for Unicode validity checking 120 let res: Result<char, _> = u32::from(value).try_into(); 121 if let Ok(ch) = res { 122 ch.try_into() 123 } else { 124 Err(CharConversionError) 125 } 126 } 127 } 128 129 impl From<Char16> for u16 { from(char: Char16) -> Self130 fn from(char: Char16) -> Self { 131 char.0 132 } 133 } 134 135 impl fmt::Debug for Char16 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result136 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 137 if let Ok(c) = u32::from(self.0).try_into() { 138 <char as fmt::Debug>::fmt(&c, f) 139 } else { 140 write!(f, "Char16({:?})", self.0) 141 } 142 } 143 } 144 145 impl fmt::Display for Char16 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result146 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 147 if let Ok(c) = u32::from(self.0).try_into() { 148 <char as fmt::Display>::fmt(&c, f) 149 } else { 150 write!(f, "{}", core::char::REPLACEMENT_CHARACTER) 151 } 152 } 153 } 154 155 impl PartialEq<char> for Char16 { eq(&self, other: &char) -> bool156 fn eq(&self, other: &char) -> bool { 157 u32::from(self.0) == u32::from(*other) 158 } 159 } 160 161 /// UCS-2 version of the NUL character 162 pub const NUL_16: Char16 = unsafe { Char16::from_u16_unchecked(0) }; 163 164 #[cfg(test)] 165 mod tests { 166 use super::*; 167 168 #[test] test_char8_from_char()169 fn test_char8_from_char() { 170 assert_eq!(Char8::try_from('A').unwrap(), Char8(0x41)); 171 } 172 173 #[test] test_char16_from_char()174 fn test_char16_from_char() { 175 assert_eq!(Char16::try_from('A').unwrap(), Char16(0x41)); 176 assert_eq!(Char16::try_from('ꋃ').unwrap(), Char16(0xa2c3)); 177 } 178 179 /// Test that `Char8` and `Char16` can be directly compared with `char`. 180 #[test] test_char_eq()181 fn test_char_eq() { 182 let primitive_char: char = 'A'; 183 assert_eq!(Char8(0x41), primitive_char); 184 assert_eq!(Char16(0x41), primitive_char); 185 } 186 } 187