roe/unicode/
titlecase.rs

1use crate::unicode::std_case_mapping_iter::CaseMappingIter;
2use crate::unicode::ucd_generated_case_mapping::SORTED_TITLECASE_MAPPING;
3use core::iter::FusedIterator;
4
5/// Take a [`char`] and return its Unicode titlecase as 3 `char`s.
6///
7/// Trailing NUL bytes in the returned array should be ignored.
8///
9/// # Examples
10///
11/// ```
12/// use roe::to_titlecase;
13///
14/// assert_eq!(to_titlecase('DŽ'), ['Dž', '\0', '\0']);
15///
16/// // Ligatures
17/// assert_eq!(to_titlecase('ffl'), ['F', 'f', 'l']);
18///
19/// // Locale is ignored
20/// assert_eq!(to_titlecase('i'), ['I', '\0', '\0']);
21///
22/// // A character already titlecased map to itself
23/// assert_eq!(to_titlecase('A'), ['A', '\0', '\0']);
24/// ```
25#[allow(clippy::module_name_repetitions)]
26#[must_use]
27pub fn to_titlecase(c: char) -> [char; 3] {
28    let codepoint = c as u32;
29    if let Ok(index) = SORTED_TITLECASE_MAPPING.binary_search_by(|&(key, _)| key.cmp(&codepoint)) {
30        let chars = SORTED_TITLECASE_MAPPING[index].1;
31        [
32            char::from_u32(chars[0]).unwrap_or(c),
33            char::from_u32(chars[1]).unwrap_or('\0'),
34            char::from_u32(chars[2]).unwrap_or('\0'),
35        ]
36    } else {
37        [c, '\0', '\0']
38    }
39}
40
41/// Returns an iterator that yields the titlecase equivalent of a `char`.
42///
43/// This `struct` is created by the [`to_titlecase`] method.
44#[allow(clippy::module_name_repetitions)]
45#[derive(Clone, Debug)]
46pub struct ToTitlecase(CaseMappingIter);
47
48impl Iterator for ToTitlecase {
49    type Item = char;
50    fn next(&mut self) -> Option<char> {
51        self.0.next()
52    }
53    fn size_hint(&self) -> (usize, Option<usize>) {
54        self.0.size_hint()
55    }
56}
57
58impl DoubleEndedIterator for ToTitlecase {
59    fn next_back(&mut self) -> Option<char> {
60        self.0.next_back()
61    }
62}
63
64impl FusedIterator for ToTitlecase {}
65
66impl ExactSizeIterator for ToTitlecase {}
67
68pub trait Titlecase {
69    fn to_titlecase(self) -> ToTitlecase;
70}
71
72impl Titlecase for char {
73    fn to_titlecase(self) -> ToTitlecase {
74        ToTitlecase(CaseMappingIter::new(to_titlecase(self)))
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use alloc::vec::Vec;
81
82    use crate::unicode::titlecase::Titlecase;
83
84    #[test]
85    fn test_char_to_titlecase() {
86        assert_eq!('ß'.to_titlecase().collect::<Vec<_>>(), ['S', 's']);
87        assert_eq!('DŽ'.to_titlecase().collect::<Vec<_>>(), ['Dž']);
88        assert_eq!('ffl'.to_titlecase().collect::<Vec<_>>(), ['F', 'f', 'l']);
89        assert_eq!('i'.to_titlecase().collect::<Vec<_>>(), ['I']);
90        assert_eq!('A'.to_titlecase().collect::<Vec<_>>(), ['A']);
91    }
92
93    #[test]
94    fn test_next_back() {
95        let mut iter = 'ffl'.to_titlecase();
96        assert_eq!(iter.next_back(), Some('l'));
97        assert_eq!(iter.next_back(), Some('f'));
98        assert_eq!(iter.next_back(), Some('F'));
99        assert_eq!(iter.next_back(), None);
100    }
101}