ferroui_base/media/text_formatting/unicode/
codepoint.rs1use super::bidi_class::BidiClass;
2use super::bidi_paired_bracket_type::BidiPairedBracketType;
3use super::east_asian_width_class::EastAsianWidthClass;
4use super::general_category::GeneralCategory;
5use super::grapheme_break_class::GraphemeBreakClass;
6use super::line_break_class::LineBreakClass;
7use super::script::Script;
8use super::sentence_break_class::SentenceBreakClass;
9use super::unicode_data::UnicodeData;
10use super::word_break_class::WordBreakClass;
11
12#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
15pub struct Codepoint {
16 value: u32,
17}
18
19impl Codepoint {
20 pub const REPLACEMENT_CODEPOINT: Codepoint = Codepoint::new(0xFFFD);
22
23 #[inline(always)]
25 pub const fn replacement_codepoint() -> Codepoint {
26 Self::REPLACEMENT_CODEPOINT
27 }
28
29 #[inline(always)]
31 pub const fn new(value: u32) -> Self {
32 Self { value }
33 }
34
35 #[inline(always)]
37 pub const fn value(self) -> u32 {
38 self.value
39 }
40
41 #[inline]
43 pub fn general_category(self) -> GeneralCategory {
44 UnicodeData::get_general_category(self.value)
45 }
46
47 #[inline]
49 pub fn script(self) -> Script {
50 UnicodeData::get_script(self.value)
51 }
52
53 #[inline]
55 pub fn bi_di_class(self) -> BidiClass {
56 UnicodeData::get_bi_di_class(self.value)
57 }
58
59 #[inline]
61 pub fn paired_bracket_type(self) -> BidiPairedBracketType {
62 UnicodeData::get_bi_di_paired_bracket_type(self.value)
63 }
64
65 #[inline]
67 pub fn line_break_class(self) -> LineBreakClass {
68 UnicodeData::get_line_break_class(self.value)
69 }
70
71 #[inline]
73 pub fn word_break_class(self) -> WordBreakClass {
74 UnicodeData::get_word_break_class(self.value)
75 }
76
77 #[inline]
79 pub fn sentence_break_class(self) -> SentenceBreakClass {
80 UnicodeData::get_sentence_break_class(self.value)
81 }
82
83 #[inline]
85 pub fn grapheme_break_class(self) -> GraphemeBreakClass {
86 UnicodeData::get_grapheme_cluster_break(self.value)
87 }
88
89 #[inline]
91 pub fn east_asian_width_class(self) -> EastAsianWidthClass {
92 UnicodeData::get_east_asian_width_class(self.value)
93 }
94
95 #[inline]
100 pub fn is_emoji(self) -> bool {
101 UnicodeData::get_is_emoji(self.value)
102 }
103
104 #[inline]
108 pub fn has_emoji_presentation(self) -> bool {
109 UnicodeData::get_has_emoji_presentation(self.value)
110 }
111
112 #[inline]
119 pub fn is_default_ignorable(self) -> bool {
120 UnicodeData::get_is_default_ignorable(self.value)
121 }
122
123 #[inline(always)]
133 pub fn has_script_extension(self, script: Script) -> bool {
134 UnicodeData::has_script_extension(self.value, script)
135 }
136
137 pub fn is_east_asian(self) -> bool {
139 matches!(
140 self.east_asian_width_class(),
141 EastAsianWidthClass::Fullwidth | EastAsianWidthClass::Halfwidth | EastAsianWidthClass::Wide
142 )
143 }
144
145 pub const fn is_break_char(self) -> bool {
147 matches!(self.value, 0x000A | 0x000B | 0x000C | 0x000D | 0x0085 | 0x2028 | 0x2029)
148 }
149
150 #[inline(always)]
152 pub fn is_white_space(self) -> bool {
153 const WHITE_SPACE_MASK: u64 = (1u64 << GeneralCategory::Control as i32)
154 | (1u64 << GeneralCategory::Format as i32)
155 | (1u64 << GeneralCategory::SpaceSeparator as i32);
156
157 ((1u64 << self.general_category() as i32) & WHITE_SPACE_MASK) != 0
158 }
159
160 #[inline(always)]
165 pub(crate) const fn get_canonical_type(code_point: Codepoint) -> Codepoint {
166 if code_point.value == 0x3008 {
167 return Codepoint::new(0x2329);
168 }
169
170 if code_point.value == 0x3009 {
171 return Codepoint::new(0x232A);
172 }
173
174 code_point
175 }
176
177 #[inline(always)]
180 pub fn try_get_paired_bracket(self) -> Option<Codepoint> {
181 if self.paired_bracket_type() == BidiPairedBracketType::None {
182 return None;
183 }
184
185 Some(UnicodeData::get_bi_di_paired_bracket(self.value))
186 }
187
188 #[inline(always)]
194 pub fn read_at(text: &[u16], index: usize) -> (Codepoint, usize) {
195 let Some(&unit) = text.get(index) else {
198 return (Self::REPLACEMENT_CODEPOINT, 1);
199 };
200
201 let code = unit as u32;
202
203 if is_in_range_inclusive(code, 0xD800, 0xDFFF) {
205 if code <= 0xDBFF {
207 if let Some(&next) = text.get(index + 1) {
208 let hi = code;
209 let low = next as u32;
210
211 if is_in_range_inclusive(low, 0xDC00, 0xDFFF) {
212 return (Codepoint::new((hi << 10) + low - ((0xD800 << 10) + 0xDC00 - (1 << 16))), 2);
213 }
214 }
215 }
216 else if index > 0 {
218 let low = code;
219 let hi = text[index - 1] as u32;
220
221 if is_in_range_inclusive(hi, 0xD800, 0xDBFF) {
222 return (Codepoint::new((hi << 10) + low - ((0xD800 << 10) + 0xDC00 - (1 << 16))), 2);
223 }
224 }
225
226 return (Self::REPLACEMENT_CODEPOINT, 1);
227 }
228
229 (Codepoint::new(code), 1)
230 }
231
232 #[inline(always)]
234 pub const fn is_in_range_inclusive(cp: Codepoint, lower_bound: u32, upper_bound: u32) -> bool {
235 is_in_range_inclusive(cp.value, lower_bound, upper_bound)
236 }
237}
238
239#[inline(always)]
240const fn is_in_range_inclusive(value: u32, lower_bound: u32, upper_bound: u32) -> bool {
241 value.wrapping_sub(lower_bound) <= upper_bound.wrapping_sub(lower_bound)
242}
243
244impl From<Codepoint> for i32 {
246 #[inline]
247 fn from(codepoint: Codepoint) -> i32 {
248 codepoint.value as i32
249 }
250}
251
252impl From<Codepoint> for u32 {
254 #[inline]
255 fn from(codepoint: Codepoint) -> u32 {
256 codepoint.value
257 }
258}