windows_helpers/core/
string.rs

1use crate::windows;
2use windows::core::HSTRING;
3
4pub trait HStringExt {
5    /// Similar to `HSTRING::as_wide()`, but truncates the slice to the specified length, avoiding to cut a UTF-16 surrogate pair in half by reducing the length by one additional wide char, if needed.
6    ///
7    /// With a target length larger than the string length, the behavior is identical to that of `as_wide()`.
8    fn as_safely_truncated_wide(&self, target_len: usize) -> &[u16];
9
10    /// Writes the `HSTRING` into the buffer, followed by a terminating null character.
11    fn write_truncated(&self, buf: &mut [u16]);
12}
13
14impl HStringExt for HSTRING {
15    fn as_safely_truncated_wide(&self, target_len: usize) -> &[u16] {
16        let slice = self.as_wide();
17        let len = slice.len();
18
19        if target_len >= len {
20            // Nothing to do.
21            return slice;
22        }
23
24        if len
25            .checked_sub(1)
26            .map(|last_index| is_leading_surrogate(slice[last_index]))
27            .unwrap_or(false /*empty string doesn't end in leading surrogate*/)
28        {
29            // Don't cut surrogate pair in half. Remove whole pair instead.
30            &slice[..len - 1]
31        } else {
32            // Ordinary truncation.
33            &slice[..len]
34        }
35    }
36
37    fn write_truncated(&self, buf: &mut [u16]) {
38        let truncated_slice =
39            self.as_safely_truncated_wide(self.len().min(buf.len() - 1 /*null-termination*/));
40
41        buf[..truncated_slice.len()].copy_from_slice(truncated_slice);
42        buf[truncated_slice.len()] = 0;
43    }
44}
45
46const fn is_leading_surrogate(wide_char: u16) -> bool {
47    wide_char >= 0xd800 && wide_char <= 0xdbff
48}