1#![allow(unsafe_code)]
9#![warn(missing_docs)]
10
11use crate::SharedVector;
12use alloc::string::String;
13use core::fmt::{Debug, Display, Write};
14use core::ops::Deref;
15#[cfg(not(feature = "std"))]
16#[allow(unused)]
17use num_traits::Float;
18
19#[macro_export]
27macro_rules! format {
28 ($($arg:tt)*) => {{
29 $crate::string::format(core::format_args!($($arg)*))
30 }}
31}
32
33#[derive(Clone, Default)]
44#[repr(C)]
45pub struct SharedString {
46 inner: SharedVector<u8>,
48}
49
50impl SharedString {
51 pub fn new() -> Self {
55 Self::default()
56 }
57
58 fn as_ptr(&self) -> *const u8 {
59 self.inner.as_ptr()
60 }
61
62 pub fn len(&self) -> usize {
64 self.inner.len().saturating_sub(1)
65 }
66
67 pub fn is_empty(&self) -> bool {
69 self.len() == 0
70 }
71
72 pub fn as_str(&self) -> &str {
74 unsafe {
76 core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.as_ptr(), self.len()))
77 }
78 }
79
80 pub(crate) fn replace_characters(&mut self, from: char, to: char, count: usize) {
82 let mut from_buffer = [0u8; 4];
83 let mut to_buffer = [0u8; 4];
84 self.inner.replace_range(
85 from.encode_utf8(&mut from_buffer).as_bytes(),
86 to.encode_utf8(&mut to_buffer).as_bytes(),
87 count,
88 );
89 }
90
91 pub fn push_str(&mut self, x: &str) {
102 let mut iter = x.as_bytes().iter().copied();
103 if self.inner.is_empty() {
104 self.inner.extend(iter.chain(core::iter::once(0)));
105 } else if let Some(first) = iter.next() {
106 let prev_len = self.len();
110 self.inner.extend(iter.chain(core::iter::once(0)));
111 self.inner.make_mut_slice()[prev_len] = first;
112 }
113 }
114}
115
116impl Deref for SharedString {
117 type Target = str;
118 fn deref(&self) -> &Self::Target {
119 self.as_str()
120 }
121}
122
123impl From<&str> for SharedString {
124 fn from(value: &str) -> Self {
125 SharedString {
126 inner: SharedVector::from_iter(
127 value.as_bytes().iter().cloned().chain(core::iter::once(0)),
128 ),
129 }
130 }
131}
132
133impl Debug for SharedString {
134 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
135 Debug::fmt(self.as_str(), f)
136 }
137}
138
139impl Display for SharedString {
140 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141 Display::fmt(self.as_str(), f)
142 }
143}
144
145impl AsRef<str> for SharedString {
146 #[inline]
147 fn as_ref(&self) -> &str {
148 self.as_str()
149 }
150}
151
152#[cfg(feature = "serde")]
153impl serde::Serialize for SharedString {
154 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
155 where
156 S: serde::Serializer,
157 {
158 let string = self.as_str();
159 serializer.serialize_str(string)
160 }
161}
162
163#[cfg(feature = "serde")]
164impl<'de> serde::Deserialize<'de> for SharedString {
165 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
166 where
167 D: serde::Deserializer<'de>,
168 {
169 let string: alloc::borrow::Cow<str> = serde::Deserialize::deserialize(deserializer)?;
170 Ok(SharedString::from(string.as_ref()))
171 }
172}
173
174#[cfg(feature = "std")]
175impl AsRef<std::ffi::CStr> for SharedString {
176 #[inline]
177 fn as_ref(&self) -> &std::ffi::CStr {
178 if self.inner.is_empty() {
179 return Default::default();
180 }
181 debug_assert_eq!(self.inner.as_slice()[self.inner.len() - 1], 0);
183 unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(self.inner.as_slice()) }
184 }
185}
186
187#[cfg(feature = "std")]
188impl AsRef<std::path::Path> for SharedString {
189 #[inline]
190 fn as_ref(&self) -> &std::path::Path {
191 self.as_str().as_ref()
192 }
193}
194
195#[cfg(feature = "std")]
196impl AsRef<std::ffi::OsStr> for SharedString {
197 #[inline]
198 fn as_ref(&self) -> &std::ffi::OsStr {
199 self.as_str().as_ref()
200 }
201}
202
203impl AsRef<[u8]> for SharedString {
204 #[inline]
205 fn as_ref(&self) -> &[u8] {
206 self.as_str().as_bytes()
207 }
208}
209
210impl<T> PartialEq<T> for SharedString
211where
212 T: ?Sized + AsRef<str>,
213{
214 fn eq(&self, other: &T) -> bool {
215 self.as_str() == other.as_ref()
216 }
217}
218impl Eq for SharedString {}
219
220impl<T> PartialOrd<T> for SharedString
221where
222 T: ?Sized + AsRef<str>,
223{
224 fn partial_cmp(&self, other: &T) -> Option<core::cmp::Ordering> {
225 PartialOrd::partial_cmp(self.as_str(), other.as_ref())
226 }
227}
228impl Ord for SharedString {
229 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
230 Ord::cmp(self.as_str(), other.as_str())
231 }
232}
233
234impl From<String> for SharedString {
235 fn from(s: String) -> Self {
236 s.as_str().into()
237 }
238}
239
240impl From<&String> for SharedString {
241 fn from(s: &String) -> Self {
242 s.as_str().into()
243 }
244}
245
246impl From<char> for SharedString {
247 fn from(c: char) -> Self {
248 SharedString::from(c.encode_utf8(&mut [0; 6]) as &str)
249 }
250}
251
252impl From<SharedString> for String {
253 fn from(s: SharedString) -> String {
254 s.as_str().into()
255 }
256}
257
258impl From<&SharedString> for String {
259 fn from(s: &SharedString) -> String {
260 s.as_str().into()
261 }
262}
263
264impl core::ops::AddAssign<&str> for SharedString {
265 fn add_assign(&mut self, other: &str) {
266 self.push_str(other);
267 }
268}
269
270impl core::ops::Add<&str> for SharedString {
271 type Output = SharedString;
272 fn add(mut self, other: &str) -> SharedString {
273 self.push_str(other);
274 self
275 }
276}
277
278impl core::hash::Hash for SharedString {
279 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
280 self.as_str().hash(state)
281 }
282}
283
284impl Write for SharedString {
285 fn write_str(&mut self, s: &str) -> core::fmt::Result {
286 self.push_str(s);
287 Ok(())
288 }
289}
290
291impl core::borrow::Borrow<str> for SharedString {
292 fn borrow(&self) -> &str {
293 self.as_str()
294 }
295}
296
297impl Extend<char> for SharedString {
298 fn extend<X: IntoIterator<Item = char>>(&mut self, iter: X) {
299 let iter = iter.into_iter();
300 self.inner.reserve(iter.size_hint().0);
301 let mut buf = [0; 4];
302 for ch in iter {
303 let utf8 = ch.encode_utf8(&mut buf);
304 self.push_str(utf8);
305 }
306 }
307}
308
309impl FromIterator<char> for SharedString {
310 fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
311 let mut str = Self::new();
312 str.extend(iter);
313 str
314 }
315}
316
317pub fn format(args: core::fmt::Arguments<'_>) -> SharedString {
319 let mut output = SharedString::default();
322 output.write_fmt(args).unwrap();
323 output
324}
325
326pub trait ToSharedString {
331 fn to_shared_string(&self) -> SharedString;
333}
334
335impl<T> ToSharedString for T
336where
337 T: Display + ?Sized,
338{
339 fn to_shared_string(&self) -> SharedString {
340 format!("{}", self)
341 }
342}
343
344#[inline]
346pub fn shared_string_from_number_unlocalized(n: f64) -> SharedString {
347 crate::format!("{}", i_slint_common::FormattedNumber(n))
348}
349
350pub fn shared_string_from_number(n: f64) -> SharedString {
352 crate::context::GLOBAL_CONTEXT.with(|ctx| {
353 let mut result = shared_string_from_number_unlocalized(n);
354
355 if let Some(ctx) = ctx.get() {
356 let pinned = ctx.0.as_ref().project_ref();
357 let decimal_separator = pinned.locale_decimal_separator.get();
358 if decimal_separator != i_slint_common::DEFAULT_DECIMAL_SEPARATOR {
359 result.replace_characters('.', decimal_separator, 1);
360 }
361 }
362 result
363 })
364}
365
366pub fn shared_string_from_number_fixed(n: f64, digits: usize) -> SharedString {
368 crate::context::GLOBAL_CONTEXT.with(|ctx| {
369 let mut result = crate::format!("{number:.digits$}", number = n, digits = digits);
370
371 if let Some(ctx) = ctx.get() {
372 let pinned = ctx.0.as_ref().project_ref();
373 let decimal_separator = pinned.locale_decimal_separator.get();
374 if decimal_separator != i_slint_common::DEFAULT_DECIMAL_SEPARATOR {
375 result.replace_characters('.', decimal_separator, 1);
376 }
377 }
378 result
379 })
380}
381
382pub fn shared_string_from_number_precision(n: f64, precision: usize) -> SharedString {
384 let exponent = f64::log10(n.abs()).floor() as isize;
385 if precision == 0 {
386 shared_string_from_number(n)
387 } else if exponent < -6 || (exponent >= 0 && exponent as usize >= precision) {
388 crate::format!(
389 "{number:.digits$e}",
390 number = n,
391 digits = precision.saturating_add_signed(-1)
392 )
393 } else {
394 shared_string_from_number_fixed(n, precision.saturating_add_signed(-(exponent + 1)))
395 }
396}
397
398pub fn string_to_float(string: &str) -> Option<f32> {
400 crate::context::GLOBAL_CONTEXT.with(|ctx| {
401 let sep = ctx.get().map(|ctx| ctx.locale_decimal_separator()).unwrap_or('.');
402
403 if sep == '.' {
404 string.parse::<f32>().ok()
405 } else {
406 if string.contains('.') {
407 return None;
408 }
409 string.replace(sep, ".").parse::<f32>().ok()
411 }
412 })
413}
414
415#[test]
416fn test_string_to_float() {
417 const TEST: &[(&str, Option<f32>)] = &[
418 ("-", None),
419 (".", None),
420 ("-.", None),
421 ("-.5", Some(-0.5)),
422 ("--", None),
423 ("..", None),
424 ("5.5.", None),
425 ("231.435", Some(231.435)),
426 ("-0.007", Some(-0.007)),
427 ("10e6", Some(10e6)),
428 ];
429
430 for (test_string, result) in TEST {
431 assert_eq!(string_to_float(test_string), *result);
432 }
433}
434
435#[test]
436fn simple_test() {
437 use std::string::ToString;
438 let x = SharedString::from("hello world!");
439 assert_eq!(x, "hello world!");
440 assert_ne!(x, "hello world?");
441 assert_eq!(x, x.clone());
442 assert_eq!("hello world!", x.as_str());
443 let string = String::from("hello world!");
444 assert_eq!(x, string);
445 assert_eq!(x.to_string(), string);
446 let def = SharedString::default();
447 assert_eq!(def, SharedString::default());
448 assert_eq!(def, SharedString::new());
449 assert_ne!(def, x);
450 assert_eq!(
451 (&x as &dyn AsRef<std::ffi::CStr>).as_ref(),
452 &*std::ffi::CString::new("hello world!").unwrap()
453 );
454 assert_eq!(SharedString::from('h'), "h");
455 assert_eq!(SharedString::from('😎'), "😎");
456}
457
458#[test]
459fn threading() {
460 let shared_cst = SharedString::from("Hello there!");
461 let shared_mtx = std::sync::Arc::new(std::sync::Mutex::new(SharedString::from("Shared:")));
462 let mut handles = std::vec::Vec::new();
463 for _ in 0..20 {
464 let cst = shared_cst.clone();
465 let mtx = shared_mtx.clone();
466 handles.push(std::thread::spawn(move || {
467 assert_eq!(cst, "Hello there!");
468 let mut cst2 = cst.clone();
469 cst2.push_str(" ... or not?");
470 assert_eq!(cst2, "Hello there! ... or not?");
471 assert_eq!(cst.clone(), "Hello there!");
472
473 let shared = {
474 let mut lock = mtx.lock().unwrap();
475 assert!(lock.starts_with("Shared:"));
476 lock.push_str("!");
477 lock.clone()
478 };
479 assert!(shared.clone().starts_with("Shared:"));
480 }));
481 }
482 for j in handles {
483 j.join().unwrap();
484 }
485 assert_eq!(shared_cst.clone(), "Hello there!");
486 assert_eq!(shared_mtx.lock().unwrap().as_str(), "Shared:!!!!!!!!!!!!!!!!!!!!");
487 }
489
490#[test]
491fn to_shared_string() {
492 let i = 5.1;
493 let five = SharedString::from("5.1");
494
495 assert_eq!(five, i.to_shared_string());
496}
497
498#[test]
499fn test_replace_characters() {
500 let mut value = SharedString::from("5.1");
501 value.replace_characters('.', ',', 1);
502
503 assert_eq!(value, "5,1".to_shared_string());
504}
505
506#[cfg(feature = "ffi")]
507pub(crate) mod ffi {
508 use super::*;
509
510 #[allow(non_camel_case_types)]
512 type c_char = u8;
513
514 #[unsafe(no_mangle)]
515 pub extern "C" fn slint_shared_string_bytes(ss: &SharedString) -> *const c_char {
519 if ss.is_empty() { c"".as_ptr().cast() } else { ss.as_ptr() }
520 }
521
522 #[unsafe(no_mangle)]
523 pub unsafe extern "C" fn slint_shared_string_drop(ss: *const SharedString) {
525 unsafe {
526 core::ptr::read(ss);
527 }
528 }
529
530 #[unsafe(no_mangle)]
531 pub unsafe extern "C" fn slint_shared_string_clone(out: *mut SharedString, ss: &SharedString) {
534 unsafe { core::ptr::write(out, ss.clone()) }
535 }
536
537 #[unsafe(no_mangle)]
538 pub unsafe extern "C" fn slint_shared_string_from_bytes(
541 out: *mut SharedString,
542 bytes: *const c_char,
543 len: usize,
544 ) {
545 unsafe {
546 let str = core::str::from_utf8(core::slice::from_raw_parts(bytes, len)).unwrap();
547 core::ptr::write(out, SharedString::from(str));
548 }
549 }
550
551 #[unsafe(no_mangle)]
554 pub unsafe extern "C" fn slint_shared_string_from_number_unlocalized(
555 out: *mut SharedString,
556 n: f64,
557 ) {
558 let str = shared_string_from_number_unlocalized(n);
559 unsafe { core::ptr::write(out, str) };
560 }
561
562 #[unsafe(no_mangle)]
565 pub unsafe extern "C" fn slint_shared_string_from_number(out: *mut SharedString, n: f64) {
566 let str = shared_string_from_number(n);
567 unsafe { core::ptr::write(out, str) };
568 }
569
570 #[test]
571 fn test_slint_shared_string_from_number() {
572 unsafe {
573 let mut s = core::mem::MaybeUninit::uninit();
574 slint_shared_string_from_number(s.as_mut_ptr(), 45.);
575 assert_eq!(s.assume_init(), "45");
576
577 let mut s = core::mem::MaybeUninit::uninit();
578 slint_shared_string_from_number(s.as_mut_ptr(), 45.12);
579 assert_eq!(s.assume_init(), "45.12");
580
581 let mut s = core::mem::MaybeUninit::uninit();
582 slint_shared_string_from_number(s.as_mut_ptr(), -1325466.);
583 assert_eq!(s.assume_init(), "-1325466");
584
585 let mut s = core::mem::MaybeUninit::uninit();
586 slint_shared_string_from_number(s.as_mut_ptr(), 0.);
587 assert_eq!(s.assume_init(), "0");
588
589 let mut s = core::mem::MaybeUninit::uninit();
590 slint_shared_string_from_number(
591 s.as_mut_ptr(),
592 ((1235.82756f32 * 1000f32).round() / 1000f32) as _,
593 );
594 assert_eq!(s.assume_init(), "1235.828");
595 }
596 }
597
598 #[unsafe(no_mangle)]
599 pub extern "C" fn slint_shared_string_from_number_fixed(
600 out: &mut SharedString,
601 n: f64,
602 digits: usize,
603 ) {
604 *out = shared_string_from_number_fixed(n, digits);
605 }
606
607 #[test]
608 fn test_slint_shared_string_from_number_fixed() {
609 let mut s = SharedString::default();
610
611 let num = 12345.6789;
612
613 slint_shared_string_from_number_fixed(&mut s, num, 0);
614 assert_eq!(s.as_str(), "12346");
615
616 slint_shared_string_from_number_fixed(&mut s, num, 1);
617 assert_eq!(s.as_str(), "12345.7");
618
619 slint_shared_string_from_number_fixed(&mut s, num, 6);
620 assert_eq!(s.as_str(), "12345.678900");
621
622 let num = -12345.6789;
623
624 slint_shared_string_from_number_fixed(&mut s, num, 0);
625 assert_eq!(s.as_str(), "-12346");
626
627 slint_shared_string_from_number_fixed(&mut s, num, 1);
628 assert_eq!(s.as_str(), "-12345.7");
629
630 slint_shared_string_from_number_fixed(&mut s, num, 6);
631 assert_eq!(s.as_str(), "-12345.678900");
632
633 slint_shared_string_from_number_fixed(&mut s, 1.23E+20_f64, 2);
634 assert_eq!(s.as_str(), "123000000000000000000.00");
635
636 slint_shared_string_from_number_fixed(&mut s, 1.23E-10_f64, 2);
637 assert_eq!(s.as_str(), "0.00");
638
639 slint_shared_string_from_number_fixed(&mut s, 2.34, 1);
640 assert_eq!(s.as_str(), "2.3");
641
642 slint_shared_string_from_number_fixed(&mut s, 2.35, 1);
643 assert_eq!(s.as_str(), "2.4");
644
645 slint_shared_string_from_number_fixed(&mut s, 2.55, 1);
646 assert_eq!(s.as_str(), "2.5");
647 }
648
649 #[unsafe(no_mangle)]
650 pub extern "C" fn slint_shared_string_from_number_precision(
651 out: &mut SharedString,
652 n: f64,
653 precision: usize,
654 ) {
655 *out = shared_string_from_number_precision(n, precision);
656 }
657
658 #[test]
659 fn test_slint_shared_string_from_number_precision() {
660 let mut s = SharedString::default();
661
662 let num = 5.123456;
663
664 slint_shared_string_from_number_precision(&mut s, num, 0);
665 assert_eq!(s.as_str(), "5.123456");
666
667 slint_shared_string_from_number_precision(&mut s, num, 5);
668 assert_eq!(s.as_str(), "5.1235");
669
670 slint_shared_string_from_number_precision(&mut s, num, 2);
671 assert_eq!(s.as_str(), "5.1");
672
673 slint_shared_string_from_number_precision(&mut s, num, 1);
674 assert_eq!(s.as_str(), "5");
675
676 let num = 0.000123;
677
678 slint_shared_string_from_number_precision(&mut s, num, 0);
679 assert_eq!(s.as_str(), "0.000123");
680
681 slint_shared_string_from_number_precision(&mut s, num, 5);
682 assert_eq!(s.as_str(), "0.00012300");
683
684 slint_shared_string_from_number_precision(&mut s, num, 2);
685 assert_eq!(s.as_str(), "0.00012");
686
687 slint_shared_string_from_number_precision(&mut s, num, 1);
688 assert_eq!(s.as_str(), "0.0001");
689
690 let num = 1234.5;
691
692 slint_shared_string_from_number_precision(&mut s, num, 1);
693 assert_eq!(s.as_str(), "1e3");
694
695 slint_shared_string_from_number_precision(&mut s, num, 2);
696 assert_eq!(s.as_str(), "1.2e3");
697
698 slint_shared_string_from_number_precision(&mut s, num, 6);
699 assert_eq!(s.as_str(), "1234.50");
700
701 let num = -1234.5;
702
703 slint_shared_string_from_number_precision(&mut s, num, 1);
704 assert_eq!(s.as_str(), "-1e3");
705
706 slint_shared_string_from_number_precision(&mut s, num, 2);
707 assert_eq!(s.as_str(), "-1.2e3");
708
709 slint_shared_string_from_number_precision(&mut s, num, 6);
710 assert_eq!(s.as_str(), "-1234.50");
711
712 let num = 0.00000012345;
713
714 slint_shared_string_from_number_precision(&mut s, num, 1);
715 assert_eq!(s.as_str(), "1e-7");
716
717 slint_shared_string_from_number_precision(&mut s, num, 10);
718 assert_eq!(s.as_str(), "1.234500000e-7");
719 }
720
721 #[unsafe(no_mangle)]
725 pub unsafe extern "C" fn slint_shared_string_append(
726 self_: &mut SharedString,
727 bytes: *const c_char,
728 len: usize,
729 ) {
730 let str = core::str::from_utf8(unsafe { core::slice::from_raw_parts(bytes, len) }).unwrap();
731 self_.push_str(str);
732 }
733 #[test]
734 fn test_slint_shared_string_append() {
735 let mut s = SharedString::default();
736 let mut append = |x: &str| unsafe {
737 slint_shared_string_append(&mut s, x.as_bytes().as_ptr(), x.len());
738 };
739 append("Hello");
740 append(", ");
741 append("world");
742 append("");
743 append("!");
744 assert_eq!(s.as_str(), "Hello, world!");
745 }
746
747 #[unsafe(no_mangle)]
748 pub unsafe extern "C" fn slint_shared_string_to_lowercase(
749 out: &mut SharedString,
750 ss: &SharedString,
751 ) {
752 *out = SharedString::from(ss.to_lowercase());
753 }
754 #[test]
755 fn test_slint_shared_string_to_lowercase() {
756 let s = SharedString::from("Hello");
757 let mut out = SharedString::default();
758
759 unsafe {
760 slint_shared_string_to_lowercase(&mut out, &s);
761 }
762 assert_eq!(out.as_str(), "hello");
763 }
764
765 #[unsafe(no_mangle)]
766 pub unsafe extern "C" fn slint_shared_string_to_uppercase(
767 out: &mut SharedString,
768 ss: &SharedString,
769 ) {
770 *out = SharedString::from(ss.to_uppercase());
771 }
772 #[test]
773 fn test_slint_shared_string_to_uppercase() {
774 let s = SharedString::from("Hello");
775 let mut out = SharedString::default();
776
777 unsafe {
778 slint_shared_string_to_uppercase(&mut out, &s);
779 }
780 assert_eq!(out.as_str(), "HELLO");
781 }
782}
783
784#[cfg(feature = "serde")]
785#[test]
786fn test_serialize_deserialize_sharedstring() {
787 let v = SharedString::from("data");
788 let serialized = serde_json::to_string(&v).unwrap();
789 let deserialized: SharedString = serde_json::from_str(&serialized).unwrap();
790 assert_eq!(v, deserialized);
791}
792
793#[cfg(feature = "serde")]
794#[test]
795fn test_serialize_deserialize_sharedstring_from_reader() {
796 let v = SharedString::from("data");
797 let serialized = serde_json::to_string(&v).unwrap();
798 let deserialized: SharedString = serde_json::from_reader(serialized.as_bytes()).unwrap();
799 assert_eq!(v, deserialized);
800}
801
802#[test]
803fn test_extend_from_chars() {
804 let mut s = SharedString::from("x");
805 s.extend(core::iter::repeat_n('a', 4).chain(core::iter::once('🍌')));
806 assert_eq!(s.as_str(), "xaaaa🍌");
807}
808
809#[test]
810fn test_collect_from_chars() {
811 let s: SharedString = core::iter::repeat_n('a', 4).chain(core::iter::once('🍌')).collect();
812 assert_eq!(s.as_str(), "aaaa🍌");
813}