168 lines
5.0 KiB
Rust
168 lines
5.0 KiB
Rust
/// A literal UTF-8 string with a trailing null terminator.
|
||
#[macro_export]
|
||
macro_rules! s {
|
||
($s:literal) => {
|
||
$crate::PCSTR::from_raw(::core::concat!($s, '\0').as_ptr())
|
||
};
|
||
}
|
||
|
||
/// A literal UTF-16 wide string with a trailing null terminator.
|
||
#[macro_export]
|
||
macro_rules! w {
|
||
($s:literal) => {{
|
||
const INPUT: &[u8] = $s.as_bytes();
|
||
const OUTPUT_LEN: usize = $crate::utf16_len(INPUT) + 1;
|
||
const OUTPUT: &[u16; OUTPUT_LEN] = {
|
||
let mut buffer = [0; OUTPUT_LEN];
|
||
let mut input_pos = 0;
|
||
let mut output_pos = 0;
|
||
while let Some((mut code_point, new_pos)) = $crate::decode_utf8_char(INPUT, input_pos) {
|
||
input_pos = new_pos;
|
||
if code_point <= 0xffff {
|
||
buffer[output_pos] = code_point as u16;
|
||
output_pos += 1;
|
||
} else {
|
||
code_point -= 0x10000;
|
||
buffer[output_pos] = 0xd800 + (code_point >> 10) as u16;
|
||
output_pos += 1;
|
||
buffer[output_pos] = 0xdc00 + (code_point & 0x3ff) as u16;
|
||
output_pos += 1;
|
||
}
|
||
}
|
||
&{ buffer }
|
||
};
|
||
$crate::PCWSTR::from_raw(OUTPUT.as_ptr())
|
||
}};
|
||
}
|
||
|
||
/// A literal HSTRING, length-prefixed wide string with a trailing null terminator.
|
||
#[macro_export]
|
||
macro_rules! h {
|
||
($s:literal) => {{
|
||
const INPUT: &[u8] = $s.as_bytes();
|
||
const OUTPUT_LEN: usize = $crate::utf16_len(INPUT) + 1;
|
||
#[allow(clippy::declare_interior_mutable_const)]
|
||
const RESULT: $crate::HSTRING = {
|
||
if OUTPUT_LEN == 1 {
|
||
unsafe { ::std::mem::transmute(::std::ptr::null::<u16>()) }
|
||
} else {
|
||
const OUTPUT: $crate::PCWSTR = $crate::w!($s);
|
||
const HEADER: $crate::HSTRING_HEADER = $crate::HSTRING_HEADER {
|
||
flags: 0x11,
|
||
len: (OUTPUT_LEN - 1) as u32,
|
||
padding1: 0,
|
||
padding2: 0,
|
||
ptr: OUTPUT.as_ptr(),
|
||
};
|
||
// SAFETY: an `HSTRING` is exactly equivalent to a pointer to an `HSTRING_HEADER`
|
||
unsafe {
|
||
::std::mem::transmute::<&$crate::HSTRING_HEADER, $crate::HSTRING>(&HEADER)
|
||
}
|
||
}
|
||
};
|
||
#[allow(clippy::borrow_interior_mutable_const)]
|
||
&RESULT
|
||
}};
|
||
}
|
||
|
||
#[doc(hidden)]
|
||
pub const fn decode_utf8_char(bytes: &[u8], mut pos: usize) -> Option<(u32, usize)> {
|
||
if bytes.len() == pos {
|
||
return None;
|
||
}
|
||
let ch = bytes[pos] as u32;
|
||
pos += 1;
|
||
if ch <= 0x7f {
|
||
return Some((ch, pos));
|
||
}
|
||
if (ch & 0xe0) == 0xc0 {
|
||
if bytes.len() - pos < 1 {
|
||
return None;
|
||
}
|
||
let ch2 = bytes[pos] as u32;
|
||
pos += 1;
|
||
if (ch2 & 0xc0) != 0x80 {
|
||
return None;
|
||
}
|
||
let result: u32 = ((ch & 0x1f) << 6) | (ch2 & 0x3f);
|
||
if result <= 0x7f {
|
||
return None;
|
||
}
|
||
return Some((result, pos));
|
||
}
|
||
if (ch & 0xf0) == 0xe0 {
|
||
if bytes.len() - pos < 2 {
|
||
return None;
|
||
}
|
||
let ch2 = bytes[pos] as u32;
|
||
pos += 1;
|
||
let ch3 = bytes[pos] as u32;
|
||
pos += 1;
|
||
if (ch2 & 0xc0) != 0x80 || (ch3 & 0xc0) != 0x80 {
|
||
return None;
|
||
}
|
||
let result = ((ch & 0x0f) << 12) | ((ch2 & 0x3f) << 6) | (ch3 & 0x3f);
|
||
if result <= 0x7ff || (0xd800 <= result && result <= 0xdfff) {
|
||
return None;
|
||
}
|
||
return Some((result, pos));
|
||
}
|
||
if (ch & 0xf8) == 0xf0 {
|
||
if bytes.len() - pos < 3 {
|
||
return None;
|
||
}
|
||
let ch2 = bytes[pos] as u32;
|
||
pos += 1;
|
||
let ch3 = bytes[pos] as u32;
|
||
pos += 1;
|
||
let ch4 = bytes[pos] as u32;
|
||
pos += 1;
|
||
if (ch2 & 0xc0) != 0x80 || (ch3 & 0xc0) != 0x80 || (ch4 & 0xc0) != 0x80 {
|
||
return None;
|
||
}
|
||
let result =
|
||
((ch & 0x07) << 18) | ((ch2 & 0x3f) << 12) | ((ch3 & 0x3f) << 6) | (ch4 & 0x3f);
|
||
if result <= 0xffff || 0x10ffff < result {
|
||
return None;
|
||
}
|
||
return Some((result, pos));
|
||
}
|
||
None
|
||
}
|
||
|
||
#[doc(hidden)]
|
||
#[repr(C)]
|
||
pub struct HSTRING_HEADER {
|
||
pub flags: u32,
|
||
pub len: u32,
|
||
pub padding1: u32,
|
||
pub padding2: u32,
|
||
pub ptr: *const u16,
|
||
}
|
||
|
||
#[doc(hidden)]
|
||
pub const fn utf16_len(bytes: &[u8]) -> usize {
|
||
let mut pos = 0;
|
||
let mut len = 0;
|
||
while let Some((code_point, new_pos)) = decode_utf8_char(bytes, pos) {
|
||
pos = new_pos;
|
||
len += if code_point <= 0xffff { 1 } else { 2 };
|
||
}
|
||
len
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test() {
|
||
assert_eq!(decode_utf8_char(b"123", 0), Some((0x31, 1)));
|
||
assert_eq!(decode_utf8_char(b"123", 1), Some((0x32, 2)));
|
||
assert_eq!(decode_utf8_char(b"123", 2), Some((0x33, 3)));
|
||
assert_eq!(decode_utf8_char(b"123", 3), None);
|
||
assert_eq!(utf16_len(b"123"), 3);
|
||
assert_eq!(utf16_len("α & ω".as_bytes()), 5);
|
||
}
|
||
}
|