1use std::iter::FusedIterator;
2use std::ops::Range;
3use std::ptr::NonNull;
4
5use super::{CFCharacterSet, CFLocale, CFString};
6use crate::raw;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum CFCharacterSetKind {
10 DecimalDigit,
11 NonBase,
12}
13
14impl CFCharacterSet {
15 #[must_use]
16 #[allow(clippy::missing_panics_doc)]
17 pub fn predefined(kind: CFCharacterSetKind) -> Self {
18 let kind = match kind {
19 CFCharacterSetKind::DecimalDigit => raw::kCFCharacterSetDecimalDigit,
20 CFCharacterSetKind::NonBase => raw::kCFCharacterSetNonBase,
21 };
22 let set = unsafe { raw::CFCharacterSetGetPredefined(i64::from(kind)) };
23 unsafe { Self::from_raw_borrowed(set.cast_mut().cast()) }.expect("predefined character set")
24 }
25}
26
27impl CFString {
28 #[must_use]
29 pub fn composed_character_range(&self, index: usize) -> Option<Range<usize>> {
30 let length = self.len();
31 if index >= length {
32 return None;
33 }
34 let range = unsafe {
35 raw::CFStringGetRangeOfComposedCharactersAtIndex(
36 self.as_ptr().cast(),
37 raw::CFIndex::try_from(index).ok()?,
38 )
39 };
40 checked_range(range, length)
41 }
42
43 pub fn composed_character_ranges(&self) -> impl Iterator<Item = Range<usize>> + '_ {
44 let mut offset = 0;
45 std::iter::from_fn(move || {
46 let range = self.composed_character_range(offset)?;
47 offset = range.end;
48 Some(range)
49 })
50 }
51}
52
53#[derive(Clone, Copy, Debug, Eq, PartialEq)]
54pub enum CFStringTokenUnit {
55 Word,
56 WordBoundary,
57}
58
59pub struct CFStringTokenizer<'a> {
60 raw: NonNull<raw::__CFStringTokenizer>,
61 string: &'a CFString,
62 finished: bool,
63}
64
65impl<'a> CFStringTokenizer<'a> {
66 #[must_use]
67 pub fn new(
68 string: &'a CFString,
69 unit: CFStringTokenUnit,
70 locale: Option<&CFLocale>,
71 ) -> Option<Self> {
72 let options = match unit {
73 CFStringTokenUnit::Word => raw::kCFStringTokenizerUnitWord,
74 CFStringTokenUnit::WordBoundary => raw::kCFStringTokenizerUnitWordBoundary,
75 };
76 let tokenizer = unsafe {
77 raw::CFStringTokenizerCreate(
78 std::ptr::null(),
79 string.as_ptr().cast(),
80 raw::CFRange {
81 location: 0,
82 length: raw::CFIndex::try_from(string.len()).ok()?,
83 },
84 u64::from(options),
85 locale.map_or(std::ptr::null(), |locale| locale.as_ptr().cast()),
86 )
87 };
88 Some(Self {
89 raw: NonNull::new(tokenizer)?,
90 string,
91 finished: false,
92 })
93 }
94}
95
96impl Iterator for CFStringTokenizer<'_> {
97 type Item = Range<usize>;
98
99 fn next(&mut self) -> Option<Self::Item> {
100 if self.finished {
101 return None;
102 }
103 let kind = unsafe { raw::CFStringTokenizerAdvanceToNextToken(self.raw.as_ptr()) };
104 if kind == u64::from(raw::kCFStringTokenizerTokenNone) {
105 self.finished = true;
106 return None;
107 }
108 let range = unsafe { raw::CFStringTokenizerGetCurrentTokenRange(self.raw.as_ptr()) };
109 let range = checked_range(range, self.string.len());
110 self.finished = range.is_none();
111 range
112 }
113}
114
115impl FusedIterator for CFStringTokenizer<'_> {}
116
117impl Drop for CFStringTokenizer<'_> {
118 fn drop(&mut self) {
119 unsafe { raw::CFRelease(self.raw.as_ptr().cast()) };
120 }
121}
122
123fn checked_range(range: raw::CFRange, length: usize) -> Option<Range<usize>> {
124 let start = usize::try_from(range.location).ok()?;
125 let count = usize::try_from(range.length).ok()?;
126 let end = start.checked_add(count)?;
127 (start < end && end <= length).then_some(start..end)
128}