arcdps_imgui/
string.rs

1use std::borrow::{Borrow, Cow};
2use std::ffi::CStr;
3use std::ops::{Deref, Index, RangeFull};
4use std::os::raw::c_char;
5use std::str;
6use std::{fmt, ptr};
7
8/// this is the unsafe cell upon which we build our abstraction.
9#[derive(Debug)]
10pub(crate) struct UiBuffer {
11    buffer: Vec<u8>,
12    max_len: usize,
13}
14
15impl UiBuffer {
16    /// Creates a new max buffer with the given length.
17    pub fn new(max_len: usize) -> Self {
18        Self {
19            buffer: Vec::with_capacity(max_len),
20            max_len,
21        }
22    }
23
24    /// Internal method to push a single text to our scratch buffer.
25    pub fn scratch_txt(&mut self, txt: impl AsRef<str>) -> *const sys::cty::c_char {
26        self.refresh_buffer();
27        self.push(txt)
28    }
29
30    /// Internal method to push an option text to our scratch buffer.
31    pub fn scratch_txt_opt(&mut self, txt: Option<impl AsRef<str>>) -> *const sys::cty::c_char {
32        match txt {
33            Some(v) => self.scratch_txt(v),
34            None => ptr::null(),
35        }
36    }
37
38    pub fn scratch_txt_two(
39        &mut self,
40        txt_0: impl AsRef<str>,
41        txt_1: impl AsRef<str>,
42    ) -> (*const sys::cty::c_char, *const sys::cty::c_char) {
43        self.refresh_buffer();
44        (self.push(txt_0), self.push(txt_1))
45    }
46
47    pub fn scratch_txt_with_opt(
48        &mut self,
49        txt_0: impl AsRef<str>,
50        txt_1: Option<impl AsRef<str>>,
51    ) -> (*const sys::cty::c_char, *const sys::cty::c_char) {
52        match txt_1 {
53            Some(value) => self.scratch_txt_two(txt_0, value),
54            None => (self.scratch_txt(txt_0), ptr::null()),
55        }
56    }
57
58    /// Attempts to clear the buffer if it's over the maximum length allowed.
59    pub fn refresh_buffer(&mut self) {
60        if self.buffer.len() > self.max_len {
61            self.buffer.clear();
62        }
63    }
64
65    /// Pushes a new scratch sheet text, which means it's not handling any clearing at all.
66    pub fn push(&mut self, txt: impl AsRef<str>) -> *const sys::cty::c_char {
67        unsafe {
68            let len = self.buffer.len();
69            self.buffer.extend(txt.as_ref().as_bytes());
70            self.buffer.push(b'\0');
71
72            self.buffer.as_ptr().add(len) as *const _
73        }
74    }
75}
76
77#[macro_export]
78#[deprecated = "all functions take AsRef<str> now -- use inline strings or `format` instead"]
79macro_rules! im_str {
80    ($e:literal $(,)?) => {{
81        const __INPUT: &str = concat!($e, "\0");
82        {
83            // Trigger a compile error if there's an interior NUL character.
84            const _CHECK_NUL: [(); 0] = [(); {
85                let bytes = __INPUT.as_bytes();
86                let mut i = 0;
87                let mut found_nul = 0;
88                while i < bytes.len() - 1 && found_nul == 0 {
89                    if bytes[i] == 0 {
90                        found_nul = 1;
91                    }
92                    i += 1;
93                }
94                found_nul
95            }];
96            const RESULT: &'static $crate::ImStr = unsafe {
97                $crate::__core::mem::transmute::<&'static [u8], &'static $crate::ImStr>(__INPUT.as_bytes())
98            };
99            RESULT
100        }
101    }};
102    ($e:literal, $($arg:tt)+) => ({
103        $crate::ImString::new(format!($e, $($arg)*))
104    });
105}
106
107/// A UTF-8 encoded, growable, implicitly nul-terminated string.
108#[derive(Clone, Hash, Ord, Eq, PartialOrd, PartialEq)]
109pub struct ImString(pub(crate) Vec<u8>);
110
111impl ImString {
112    /// Creates a new `ImString` from an existing string.
113    pub fn new<T: Into<String>>(value: T) -> ImString {
114        unsafe {
115            let mut s = ImString::from_utf8_unchecked(value.into().into_bytes());
116            s.refresh_len();
117            s
118        }
119    }
120
121    /// Creates a new empty `ImString` with a particular capacity
122    #[inline]
123    pub fn with_capacity(capacity: usize) -> ImString {
124        let mut v = Vec::with_capacity(capacity + 1);
125        v.push(b'\0');
126        ImString(v)
127    }
128
129    /// Converts a vector of bytes to a `ImString` without checking that the string contains valid
130    /// UTF-8
131    ///
132    /// # Safety
133    ///
134    /// It is up to the caller to guarantee the vector contains valid UTF-8 and no null terminator.
135    #[inline]
136    pub unsafe fn from_utf8_unchecked(mut v: Vec<u8>) -> ImString {
137        v.push(b'\0');
138        ImString(v)
139    }
140
141    /// Converts a vector of bytes to a `ImString` without checking that the string contains valid
142    /// UTF-8
143    ///
144    /// # Safety
145    ///
146    /// It is up to the caller to guarantee the vector contains valid UTF-8 and a null terminator.
147    #[inline]
148    pub unsafe fn from_utf8_with_nul_unchecked(v: Vec<u8>) -> ImString {
149        ImString(v)
150    }
151
152    /// Truncates this `ImString`, removing all contents
153    #[inline]
154    pub fn clear(&mut self) {
155        self.0.clear();
156        self.0.push(b'\0');
157    }
158
159    /// Appends the given character to the end of this `ImString`
160    #[inline]
161    pub fn push(&mut self, ch: char) {
162        let mut buf = [0; 4];
163        self.push_str(ch.encode_utf8(&mut buf));
164    }
165
166    /// Appends a given string slice to the end of this `ImString`
167    #[inline]
168    pub fn push_str(&mut self, string: &str) {
169        self.0.pop();
170        self.0.extend(string.bytes());
171        self.0.push(b'\0');
172        unsafe {
173            self.refresh_len();
174        }
175    }
176
177    /// Returns the capacity of this `ImString` in bytes
178    #[inline]
179    pub fn capacity(&self) -> usize {
180        self.0.capacity() - 1
181    }
182
183    /// Returns the capacity of this `ImString` in bytes, including the implicit null byte
184    #[inline]
185    pub fn capacity_with_nul(&self) -> usize {
186        self.0.capacity()
187    }
188
189    /// Ensures that the capacity of this `ImString` is at least `additional` bytes larger than the
190    /// current length.
191    ///
192    /// The capacity may be increased by more than `additional` bytes.
193    pub fn reserve(&mut self, additional: usize) {
194        self.0.reserve(additional);
195    }
196
197    /// Ensures that the capacity of this `ImString` is at least `additional` bytes larger than the
198    /// current length
199    pub fn reserve_exact(&mut self, additional: usize) {
200        self.0.reserve_exact(additional);
201    }
202
203    /// Returns a raw pointer to the underlying buffer
204    #[inline]
205    pub fn as_ptr(&self) -> *const c_char {
206        self.0.as_ptr() as *const c_char
207    }
208
209    /// Returns a raw mutable pointer to the underlying buffer.
210    ///
211    /// If the underlying data is modified, `refresh_len` *must* be called afterwards.
212    #[inline]
213    pub fn as_mut_ptr(&mut self) -> *mut c_char {
214        self.0.as_mut_ptr() as *mut c_char
215    }
216
217    /// Updates the underlying buffer length based on the current contents.
218    ///
219    /// This function *must* be called if the underlying data is modified via a pointer
220    /// obtained by `as_mut_ptr`.
221    ///
222    /// # Safety
223    ///
224    /// It is up to the caller to guarantee the this ImString contains valid UTF-8 and a null
225    /// terminator.
226    #[inline]
227    pub unsafe fn refresh_len(&mut self) {
228        let len = CStr::from_ptr(self.0.as_ptr() as *const c_char)
229            .to_bytes_with_nul()
230            .len();
231        self.0.set_len(len);
232    }
233}
234
235impl<'a> Default for ImString {
236    #[inline]
237    fn default() -> ImString {
238        ImString(vec![b'\0'])
239    }
240}
241
242impl From<String> for ImString {
243    #[inline]
244    fn from(s: String) -> ImString {
245        ImString::new(s)
246    }
247}
248
249impl<'a> From<ImString> for Cow<'a, ImStr> {
250    #[inline]
251    fn from(s: ImString) -> Cow<'a, ImStr> {
252        Cow::Owned(s)
253    }
254}
255
256impl<'a> From<&'a ImString> for Cow<'a, ImStr> {
257    #[inline]
258    fn from(s: &'a ImString) -> Cow<'a, ImStr> {
259        Cow::Borrowed(s)
260    }
261}
262
263impl<'a, T: ?Sized + AsRef<ImStr>> From<&'a T> for ImString {
264    #[inline]
265    fn from(s: &'a T) -> ImString {
266        s.as_ref().to_owned()
267    }
268}
269
270impl AsRef<ImStr> for ImString {
271    #[inline]
272    fn as_ref(&self) -> &ImStr {
273        self
274    }
275}
276
277impl Borrow<ImStr> for ImString {
278    #[inline]
279    fn borrow(&self) -> &ImStr {
280        self
281    }
282}
283
284impl AsRef<str> for ImString {
285    #[inline]
286    fn as_ref(&self) -> &str {
287        self.to_str()
288    }
289}
290
291impl Borrow<str> for ImString {
292    #[inline]
293    fn borrow(&self) -> &str {
294        self.to_str()
295    }
296}
297
298impl Index<RangeFull> for ImString {
299    type Output = ImStr;
300    #[inline]
301    fn index(&self, _index: RangeFull) -> &ImStr {
302        self
303    }
304}
305
306impl fmt::Debug for ImString {
307    #[inline]
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        fmt::Debug::fmt(self.to_str(), f)
310    }
311}
312
313impl fmt::Display for ImString {
314    #[inline]
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        fmt::Display::fmt(self.to_str(), f)
317    }
318}
319
320impl Deref for ImString {
321    type Target = ImStr;
322    #[inline]
323    fn deref(&self) -> &ImStr {
324        // as_ptr() is used, because we need to look at the bytes to figure out the length
325        // self.0.len() is incorrect, because there might be more than one nul byte in the end, or
326        // some interior nuls in the data
327        unsafe {
328            &*(CStr::from_ptr(self.0.as_ptr() as *const c_char) as *const CStr as *const ImStr)
329        }
330    }
331}
332
333impl fmt::Write for ImString {
334    #[inline]
335    fn write_str(&mut self, s: &str) -> fmt::Result {
336        self.push_str(s);
337        Ok(())
338    }
339
340    #[inline]
341    fn write_char(&mut self, c: char) -> fmt::Result {
342        self.push(c);
343        Ok(())
344    }
345}
346
347/// A UTF-8 encoded, implicitly nul-terminated string slice.
348#[derive(Hash, PartialEq, Eq, PartialOrd, Ord)]
349#[repr(transparent)]
350pub struct ImStr([u8]);
351
352impl<'a> Default for &'a ImStr {
353    #[inline]
354    fn default() -> &'a ImStr {
355        static SLICE: &[u8] = &[0];
356        unsafe { ImStr::from_utf8_with_nul_unchecked(SLICE) }
357    }
358}
359
360impl fmt::Debug for ImStr {
361    #[inline]
362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363        fmt::Debug::fmt(&self.0, f)
364    }
365}
366
367impl fmt::Display for ImStr {
368    #[inline]
369    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370        fmt::Display::fmt(self.to_str(), f)
371    }
372}
373
374impl ImStr {
375    /// Wraps a raw UTF-8 encoded C string
376    ///
377    /// # Safety
378    ///
379    /// It is up to the caller to guarantee the pointer is not null and it points to a
380    /// null-terminated UTF-8 string valid for the duration of the arbitrary lifetime 'a.
381    #[inline]
382    pub unsafe fn from_ptr_unchecked<'a>(ptr: *const c_char) -> &'a ImStr {
383        ImStr::from_cstr_unchecked(CStr::from_ptr(ptr))
384    }
385
386    /// Converts a slice of bytes to an imgui-rs string slice without checking for valid UTF-8 or
387    /// null termination.
388    ///
389    /// # Safety
390    ///
391    /// It is up to the caller to guarantee the slice contains valid UTF-8 and a null terminator.
392    #[inline]
393    pub unsafe fn from_utf8_with_nul_unchecked(bytes: &[u8]) -> &ImStr {
394        &*(bytes as *const [u8] as *const ImStr)
395    }
396
397    /// Converts a CStr reference to an imgui-rs string slice without checking for valid UTF-8.
398    ///
399    /// # Safety
400    ///
401    /// It is up to the caller to guarantee the CStr reference contains valid UTF-8.
402    #[inline]
403    pub unsafe fn from_cstr_unchecked(value: &CStr) -> &ImStr {
404        &*(value.to_bytes_with_nul() as *const [u8] as *const ImStr)
405    }
406
407    /// Converts an imgui-rs string slice to a raw pointer
408    #[inline]
409    pub fn as_ptr(&self) -> *const c_char {
410        self.0.as_ptr() as *const c_char
411    }
412
413    /// Converts an imgui-rs string slice to a normal string slice
414    #[inline]
415    pub fn to_str(&self) -> &str {
416        self.sanity_check();
417        unsafe { str::from_utf8_unchecked(&self.0[..(self.0.len() - 1)]) }
418    }
419
420    /// Returns true if the imgui-rs string slice is empty
421    #[inline]
422    pub fn is_empty(&self) -> bool {
423        debug_assert!(self.0.len() != 0);
424        self.0.len() == 1
425    }
426
427    // TODO: if this is too slow, avoid the UTF8 validation except if we'd
428    // already be doing O(n) stuff.
429    #[inline]
430    fn sanity_check(&self) {
431        debug_assert!(
432            str::from_utf8(&self.0).is_ok()
433                && !self.0.is_empty()
434                && !self.0[..(self.0.len() - 1)].contains(&0u8)
435                && self.0[self.0.len() - 1] == 0,
436            "bad ImStr: {:?}",
437            &self.0
438        );
439    }
440}
441
442impl AsRef<CStr> for ImStr {
443    #[inline]
444    fn as_ref(&self) -> &CStr {
445        // Safety: our safety requirements are a superset of CStr's, so this is fine
446        unsafe { CStr::from_bytes_with_nul_unchecked(&self.0) }
447    }
448}
449
450impl AsRef<ImStr> for ImStr {
451    #[inline]
452    fn as_ref(&self) -> &ImStr {
453        self
454    }
455}
456
457impl AsRef<str> for ImStr {
458    #[inline]
459    fn as_ref(&self) -> &str {
460        self.to_str()
461    }
462}
463
464impl<'a> From<&'a ImStr> for Cow<'a, ImStr> {
465    #[inline]
466    fn from(s: &'a ImStr) -> Cow<'a, ImStr> {
467        Cow::Borrowed(s)
468    }
469}
470
471impl ToOwned for ImStr {
472    type Owned = ImString;
473    #[inline]
474    fn to_owned(&self) -> ImString {
475        self.sanity_check();
476        ImString(self.0.to_owned())
477    }
478}
479
480#[test]
481fn test_imstring_constructors() {
482    let s = ImString::new("test");
483    assert_eq!(s.0, b"test\0");
484
485    let s = ImString::with_capacity(100);
486    assert_eq!(s.0, b"\0");
487
488    let s = unsafe { ImString::from_utf8_unchecked(vec![b't', b'e', b's', b't']) };
489    assert_eq!(s.0, b"test\0");
490
491    let s = unsafe { ImString::from_utf8_with_nul_unchecked(vec![b't', b'e', b's', b't', b'\0']) };
492    assert_eq!(s.0, b"test\0");
493}
494
495#[test]
496fn test_imstring_operations() {
497    let mut s = ImString::new("test");
498    s.clear();
499    assert_eq!(s.0, b"\0");
500    s.push('z');
501    assert_eq!(s.0, b"z\0");
502    s.push('ä');
503    assert_eq!(s.0, b"z\xc3\xa4\0");
504    s.clear();
505    s.push_str("imgui-rs");
506    assert_eq!(s.0, b"imgui-rs\0");
507    s.push_str("öä");
508    assert_eq!(s.0, b"imgui-rs\xc3\xb6\xc3\xa4\0");
509}
510
511#[test]
512fn test_imstring_fmt_write() {
513    use std::fmt::Write;
514    let mut s = ImString::default();
515    let _ = write!(s, "format {:02x}", 0x42);
516    assert_eq!(s.0, b"format 42\0");
517}
518
519#[test]
520fn test_imstring_refresh_len() {
521    let mut s = ImString::new("testing");
522    unsafe {
523        let mut ptr = s.as_mut_ptr() as *mut u8;
524        ptr = ptr.wrapping_add(2);
525        *ptr = b'z';
526        ptr = ptr.wrapping_add(1);
527        *ptr = b'\0';
528    }
529    assert_eq!(s.0, b"tez\0ing\0");
530    unsafe { s.refresh_len() };
531    assert_eq!(s.0, b"tez\0");
532}
533
534#[test]
535fn test_imstring_interior_nul() {
536    let s = ImString::new("test\0ohno");
537    assert_eq!(s.0, b"test\0");
538    assert_eq!(s.to_str(), "test");
539    assert!(!s.is_empty());
540
541    let s = ImString::new("\0ohno");
542    assert_eq!(s.to_str(), "");
543    assert!(s.is_empty());
544}