Skip to main content

apple_cf/cf/
text.rs

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}