Skip to main content

ferroui_base/media/text_formatting/unicode/
codepoint.rs

1use 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/// A Unicode code point (not necessarily a scalar value: lone surrogates and
13/// values above U+10FFFF can be represented, as upstream).
14#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
15pub struct Codepoint {
16    value: u32,
17}
18
19impl Codepoint {
20    /// The replacement codepoint that is used for non supported values.
21    pub const REPLACEMENT_CODEPOINT: Codepoint = Codepoint::new(0xFFFD);
22
23    /// The replacement codepoint that is used for non supported values.
24    #[inline(always)]
25    pub const fn replacement_codepoint() -> Codepoint {
26        Self::REPLACEMENT_CODEPOINT
27    }
28
29    /// Creates a new instance of [`Codepoint`] with the specified value.
30    #[inline(always)]
31    pub const fn new(value: u32) -> Self {
32        Self { value }
33    }
34
35    /// Get the codepoint's value.
36    #[inline(always)]
37    pub const fn value(self) -> u32 {
38        self.value
39    }
40
41    /// Gets the [`GeneralCategory`].
42    #[inline]
43    pub fn general_category(self) -> GeneralCategory {
44        UnicodeData::get_general_category(self.value)
45    }
46
47    /// Gets the [`Script`].
48    #[inline]
49    pub fn script(self) -> Script {
50        UnicodeData::get_script(self.value)
51    }
52
53    /// Gets the [`BidiClass`].
54    #[inline]
55    pub fn bi_di_class(self) -> BidiClass {
56        UnicodeData::get_bi_di_class(self.value)
57    }
58
59    /// Gets the [`BidiPairedBracketType`].
60    #[inline]
61    pub fn paired_bracket_type(self) -> BidiPairedBracketType {
62        UnicodeData::get_bi_di_paired_bracket_type(self.value)
63    }
64
65    /// Gets the [`LineBreakClass`].
66    #[inline]
67    pub fn line_break_class(self) -> LineBreakClass {
68        UnicodeData::get_line_break_class(self.value)
69    }
70
71    /// Gets the [`WordBreakClass`].
72    #[inline]
73    pub fn word_break_class(self) -> WordBreakClass {
74        UnicodeData::get_word_break_class(self.value)
75    }
76
77    /// Gets the [`SentenceBreakClass`] (UAX #29).
78    #[inline]
79    pub fn sentence_break_class(self) -> SentenceBreakClass {
80        UnicodeData::get_sentence_break_class(self.value)
81    }
82
83    /// Gets the [`GraphemeBreakClass`].
84    #[inline]
85    pub fn grapheme_break_class(self) -> GraphemeBreakClass {
86        UnicodeData::get_grapheme_cluster_break(self.value)
87    }
88
89    /// Gets the [`EastAsianWidthClass`].
90    #[inline]
91    pub fn east_asian_width_class(self) -> EastAsianWidthClass {
92        UnicodeData::get_east_asian_width_class(self.value)
93    }
94
95    /// Determines whether this [`Codepoint`] has the Unicode `Emoji` property.
96    ///
97    /// The property marks a codepoint that can be presented as emoji; it says nothing about which
98    /// presentation is the default. Use [`Codepoint::has_emoji_presentation`] for that.
99    #[inline]
100    pub fn is_emoji(self) -> bool {
101        UnicodeData::get_is_emoji(self.value)
102    }
103
104    /// Determines whether this [`Codepoint`] has the Unicode `Emoji_Presentation`
105    /// property, i.e. whether it defaults to an emoji presentation when no variation selector
106    /// follows it.
107    #[inline]
108    pub fn has_emoji_presentation(self) -> bool {
109        UnicodeData::get_has_emoji_presentation(self.value)
110    }
111
112    /// Determines whether this [`Codepoint`] has the Unicode
113    /// `Default_Ignorable_Code_Point` property.
114    ///
115    /// Default ignorable codepoints (variation selectors, joiners, the byte order mark, ...) are
116    /// meant to have no visible rendering of their own, so a font is not expected to provide a
117    /// glyph for them.
118    #[inline]
119    pub fn is_default_ignorable(self) -> bool {
120        UnicodeData::get_is_default_ignorable(self.value)
121    }
122
123    /// Determines whether the codepoint's Unicode Script_Extensions property contains
124    /// `script`.
125    ///
126    /// Returns `true` when the codepoint participates in `script` per UAX #24
127    /// (Script_Extensions); `false` otherwise.
128    ///
129    /// Backed by the UCD `ScriptExtensions.txt` data baked into [`UnicodeData`].
130    /// Codepoints without an explicit Script_Extensions entry fall back to the singleton set
131    /// of their primary [`Script`] property.
132    #[inline(always)]
133    pub fn has_script_extension(self, script: Script) -> bool {
134        UnicodeData::has_script_extension(self.value, script)
135    }
136
137    /// Determines whether this [`Codepoint`] is an east asian char.
138    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    /// Determines whether this [`Codepoint`] is a break char.
146    pub const fn is_break_char(self) -> bool {
147        matches!(self.value, 0x000A | 0x000B | 0x000C | 0x000D | 0x0085 | 0x2028 | 0x2029)
148    }
149
150    /// Determines whether this [`Codepoint`] is white space.
151    #[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    /// Gets the canonical representation of a given codepoint.
161    /// <https://www.unicode.org/L2/L2013/13123-norm-and-bpa.pdf>
162    ///
163    /// Returns the mapped canonical code point, or the passed `code_point`.
164    #[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    /// Gets the codepoint representing the bracket pairing for this instance,
178    /// or `None` if this instance has no bracket pairing.
179    #[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    /// Reads the [`Codepoint`] at specified position.
189    ///
190    /// Returns the codepoint and the count of code units that were read
191    /// (always 1 or 2; an index outside of the text or an unpaired surrogate
192    /// yields the replacement codepoint and a count of 1).
193    #[inline(always)]
194    pub fn read_at(text: &[u16], index: usize) -> (Codepoint, usize) {
195        // Perf note: this method is performance critical for text layout, modify with care!
196
197        let Some(&unit) = text.get(index) else {
198            return (Self::REPLACEMENT_CODEPOINT, 1);
199        };
200
201        let code = unit as u32;
202
203        //# Surrogate
204        if is_in_range_inclusive(code, 0xD800, 0xDFFF) {
205            //# High surrogate
206            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            //# Low surrogate
217            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    /// Returns `true` if `cp` is between `lower_bound` and `upper_bound`, inclusive.
233    #[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
244/// The implicit conversion to `int` upstream.
245impl From<Codepoint> for i32 {
246    #[inline]
247    fn from(codepoint: Codepoint) -> i32 {
248        codepoint.value as i32
249    }
250}
251
252/// The implicit conversion to `uint` upstream.
253impl From<Codepoint> for u32 {
254    #[inline]
255    fn from(codepoint: Codepoint) -> u32 {
256        codepoint.value
257    }
258}