icu_normalizer/
properties.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Access to the Unicode properties or property-based operations that
6//! are required for NFC and NFD.
7//!
8//! Applications should generally use the full normalizers that are
9//! provided at the top level of this crate. However, the APIs in this
10//! module are provided for callers such as HarfBuzz that specifically
11//! want access to the raw canonical composition operation e.g. for use in a
12//! glyph-availability-guided custom normalizer.
13
14use crate::char_from_u16;
15use crate::char_from_u32;
16use crate::in_inclusive_range;
17use crate::provider::CanonicalCompositions;
18use crate::provider::DecompositionData;
19use crate::provider::DecompositionTables;
20use crate::provider::NonRecursiveDecompositionSupplement;
21use crate::provider::NormalizerNfcV1;
22use crate::provider::NormalizerNfdDataV1;
23use crate::provider::NormalizerNfdSupplementV1;
24use crate::provider::NormalizerNfdTablesV1;
25use crate::trie_value_has_ccc;
26use crate::CanonicalCombiningClass;
27use crate::BACKWARD_COMBINING_MARKER;
28use crate::FDFA_MARKER;
29use crate::HANGUL_L_BASE;
30use crate::HANGUL_N_COUNT;
31use crate::HANGUL_S_BASE;
32use crate::HANGUL_S_COUNT;
33use crate::HANGUL_T_BASE;
34use crate::HANGUL_T_COUNT;
35use crate::HANGUL_V_BASE;
36use crate::HIGH_ZEROS_MASK;
37use crate::LOW_ZEROS_MASK;
38use crate::NON_ROUND_TRIP_MARKER;
39use icu_provider::prelude::*;
40
41/// Borrowed version of the raw canonical composition operation.
42///
43/// Callers should generally use `ComposingNormalizer` instead of this API.
44/// However, this API is provided for callers such as HarfBuzz that specifically
45/// want access to the raw canonical composition operation e.g. for use in a
46/// glyph-availability-guided custom normalizer.
47#[derive(Debug, Copy, Clone)]
48pub struct CanonicalCompositionBorrowed<'a> {
49    canonical_compositions: &'a CanonicalCompositions<'a>,
50}
51
52#[cfg(feature = "compiled_data")]
53impl Default for CanonicalCompositionBorrowed<'static> {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl CanonicalCompositionBorrowed<'static> {
60    /// Cheaply converts a [`CanonicalCompositionBorrowed<'static>`] into a [`CanonicalComposition`].
61    ///
62    /// Note: Due to branching and indirection, using [`CanonicalComposition`] might inhibit some
63    /// compile-time optimizations that are possible with [`CanonicalCompositionBorrowed`].
64    pub const fn static_to_owned(self) -> CanonicalComposition {
65        CanonicalComposition {
66            canonical_compositions: DataPayload::from_static_ref(self.canonical_compositions),
67        }
68    }
69
70    /// Constructs a new `CanonicalComposition` using compiled data.
71    ///
72    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
73    ///
74    /// [πŸ“š Help choosing a constructor](icu_provider::constructors)
75    #[cfg(feature = "compiled_data")]
76    pub const fn new() -> Self {
77        Self {
78            canonical_compositions: crate::provider::Baked::SINGLETON_NORMALIZER_NFC_V1,
79        }
80    }
81}
82
83impl CanonicalCompositionBorrowed<'_> {
84    /// Performs canonical composition (including Hangul) on a pair of
85    /// characters or returns `None` if these characters don't compose.
86    /// Composition exclusions are taken into account.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// let comp = icu::normalizer::properties::CanonicalCompositionBorrowed::new();
92    ///
93    /// assert_eq!(comp.compose('a', 'b'), None); // Just two non-composing starters
94    /// assert_eq!(comp.compose('a', '\u{0308}'), Some('Γ€'));
95    /// assert_eq!(comp.compose('αΊΉ', '\u{0302}'), Some('ệ'));
96    /// assert_eq!(comp.compose('𝅗', 'π…₯'), None); // Composition exclusion
97    /// assert_eq!(comp.compose('ে', 'ΰ¦Ύ'), Some('ΰ§‹')); // Second is starter
98    /// assert_eq!(comp.compose('α„€', 'α…‘'), Some('κ°€')); // Hangul LV
99    /// assert_eq!(comp.compose('κ°€', 'ᆨ'), Some('각')); // Hangul LVT
100    /// ```
101    #[inline(always)]
102    pub fn compose(self, starter: char, second: char) -> Option<char> {
103        crate::compose(
104            self.canonical_compositions.canonical_compositions.iter(),
105            starter,
106            second,
107        )
108    }
109}
110
111/// The raw canonical composition operation.
112///
113/// Callers should generally use `ComposingNormalizer` instead of this API.
114/// However, this API is provided for callers such as HarfBuzz that specifically
115/// want access to the raw canonical composition operation e.g. for use in a
116/// glyph-availability-guided custom normalizer.
117#[derive(Debug)]
118pub struct CanonicalComposition {
119    canonical_compositions: DataPayload<NormalizerNfcV1>,
120}
121
122#[cfg(feature = "compiled_data")]
123impl Default for CanonicalComposition {
124    fn default() -> Self {
125        Self::new().static_to_owned()
126    }
127}
128
129impl CanonicalComposition {
130    /// Constructs a borrowed version of this type for more efficient querying.
131    pub fn as_borrowed(&self) -> CanonicalCompositionBorrowed<'_> {
132        CanonicalCompositionBorrowed {
133            canonical_compositions: self.canonical_compositions.get(),
134        }
135    }
136
137    /// Constructs a new `CanonicalCompositionBorrowed` using compiled data.
138    ///
139    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
140    ///
141    /// [πŸ“š Help choosing a constructor](icu_provider::constructors)
142    #[cfg(feature = "compiled_data")]
143    #[allow(clippy::new_ret_no_self)]
144    pub const fn new() -> CanonicalCompositionBorrowed<'static> {
145        CanonicalCompositionBorrowed::new()
146    }
147
148    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
149        functions: [
150            new: skip,
151            try_new_with_buffer_provider,
152            try_new_unstable,
153            Self,
154        ]
155    );
156
157    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
158    pub fn try_new_unstable<D>(provider: &D) -> Result<Self, DataError>
159    where
160        D: DataProvider<NormalizerNfcV1> + ?Sized,
161    {
162        let canonical_compositions: DataPayload<NormalizerNfcV1> =
163            provider.load(Default::default())?.payload;
164        Ok(CanonicalComposition {
165            canonical_compositions,
166        })
167    }
168}
169
170/// The outcome of non-recursive canonical decomposition of a character.
171#[allow(clippy::exhaustive_enums)]
172#[derive(Debug, PartialEq, Eq)]
173pub enum Decomposed {
174    /// The character is its own canonical decomposition.
175    Default,
176    /// The character decomposes to a single different character.
177    Singleton(char),
178    /// The character decomposes to two characters.
179    Expansion(char, char),
180}
181
182/// Borrowed version of the raw (non-recursive) canonical decomposition operation.
183///
184/// Callers should generally use `DecomposingNormalizer` instead of this API.
185/// However, this API is provided for callers such as HarfBuzz that specifically
186/// want access to non-recursive canonical decomposition e.g. for use in a
187/// glyph-availability-guided custom normalizer.
188#[derive(Debug)]
189pub struct CanonicalDecompositionBorrowed<'a> {
190    decompositions: &'a DecompositionData<'a>,
191    tables: &'a DecompositionTables<'a>,
192    non_recursive: &'a NonRecursiveDecompositionSupplement<'a>,
193}
194
195#[cfg(feature = "compiled_data")]
196impl Default for CanonicalDecompositionBorrowed<'static> {
197    fn default() -> Self {
198        Self::new()
199    }
200}
201
202impl CanonicalDecompositionBorrowed<'static> {
203    /// Cheaply converts a [`CanonicalDecompositionBorrowed<'static>`] into a [`CanonicalDecomposition`].
204    ///
205    /// Note: Due to branching and indirection, using [`CanonicalDecomposition`] might inhibit some
206    /// compile-time optimizations that are possible with [`CanonicalDecompositionBorrowed`].
207    pub const fn static_to_owned(self) -> CanonicalDecomposition {
208        CanonicalDecomposition {
209            decompositions: DataPayload::from_static_ref(self.decompositions),
210            tables: DataPayload::from_static_ref(self.tables),
211            non_recursive: DataPayload::from_static_ref(self.non_recursive),
212        }
213    }
214
215    /// Construct from compiled data.
216    ///
217    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
218    ///
219    /// [πŸ“š Help choosing a constructor](icu_provider::constructors)
220    #[cfg(feature = "compiled_data")]
221    pub const fn new() -> Self {
222        const _: () = assert!(
223            crate::provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
224                .scalars16
225                .const_len()
226                + crate::provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
227                    .scalars24
228                    .const_len()
229                <= 0xFFF,
230            "future extension"
231        );
232
233        Self {
234            decompositions: crate::provider::Baked::SINGLETON_NORMALIZER_NFD_DATA_V1,
235            tables: crate::provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1,
236            non_recursive: crate::provider::Baked::SINGLETON_NORMALIZER_NFD_SUPPLEMENT_V1,
237        }
238    }
239}
240
241impl CanonicalDecompositionBorrowed<'_> {
242    /// Performs non-recursive canonical decomposition (including for Hangul).
243    ///
244    /// ```
245    ///     use icu::normalizer::properties::Decomposed;
246    ///     let decomp = icu::normalizer::properties::CanonicalDecompositionBorrowed::new();
247    ///
248    ///     assert_eq!(decomp.decompose('e'), Decomposed::Default);
249    ///     assert_eq!(
250    ///         decomp.decompose('ệ'),
251    ///         Decomposed::Expansion('αΊΉ', '\u{0302}')
252    ///     );
253    ///     assert_eq!(decomp.decompose('각'), Decomposed::Expansion('κ°€', 'ᆨ'));
254    ///     assert_eq!(decomp.decompose('\u{212B}'), Decomposed::Singleton('Γ…')); // ANGSTROM SIGN
255    ///     assert_eq!(decomp.decompose('\u{2126}'), Decomposed::Singleton('Ξ©')); // OHM SIGN
256    ///     assert_eq!(decomp.decompose('\u{1F71}'), Decomposed::Singleton('Ξ¬')); // oxia
257    /// ```
258    #[inline]
259    pub fn decompose(&self, c: char) -> Decomposed {
260        let lvt = u32::from(c).wrapping_sub(HANGUL_S_BASE);
261        if lvt >= HANGUL_S_COUNT {
262            return self.decompose_non_hangul(c);
263        }
264        // Invariant: lvt ≀ HANGUL_S_COUNT = 1172
265        let t = lvt % HANGUL_T_COUNT;
266        // Invariant: t ≀ (1172 / HANGUL_T_COUNT = 1172 / 28 = 41)
267        if t == 0 {
268            let l = lvt / HANGUL_N_COUNT;
269            // Invariant: v ≀ (1172 / HANGUL_N_COUNT = 1172 / 588 β‰ˆ 2)
270            let v = (lvt % HANGUL_N_COUNT) / HANGUL_T_COUNT;
271            // Invariant: v < (HANGUL_N_COUNT / HANGUL_T_COUNT = 588 / 28 = 21)
272            return Decomposed::Expansion(
273                // Safety: HANGUL_*_BASE are 0x1nnn, addding numbers that are 21 and 41
274                // max will keep it in range, less than 0xD800
275                unsafe { char::from_u32_unchecked(HANGUL_L_BASE + l) },
276                unsafe { char::from_u32_unchecked(HANGUL_V_BASE + v) },
277            );
278        }
279        let lv = lvt - t;
280        // Invariant: lvt < 1172
281        // Safe because values known to be in range
282        Decomposed::Expansion(
283            // Safety: HANGUL_*_BASE are 0x1nnn, addding numbers that are 1172 and 41
284            // max will keep it in range, less than 0xD800
285            unsafe { char::from_u32_unchecked(HANGUL_S_BASE + lv) },
286            unsafe { char::from_u32_unchecked(HANGUL_T_BASE + t) },
287        )
288    }
289
290    /// Performs non-recursive canonical decomposition except Hangul syllables
291    /// are reported as `Decomposed::Default`.
292    #[inline(always)]
293    fn decompose_non_hangul(&self, c: char) -> Decomposed {
294        let decomposition = self.decompositions.trie.get(c);
295        // The REPLACEMENT CHARACTER has `NON_ROUND_TRIP_MARKER` set,
296        // and that flag needs to be ignored here.
297        if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0 {
298            return Decomposed::Default;
299        }
300        // The loop is only broken out of as goto forward
301        #[allow(clippy::never_loop)]
302        loop {
303            let high_zeros = (decomposition & HIGH_ZEROS_MASK) == 0;
304            let low_zeros = (decomposition & LOW_ZEROS_MASK) == 0;
305            if !high_zeros && !low_zeros {
306                // Decomposition into two BMP characters: starter and non-starter
307                if in_inclusive_range(c, '\u{1F71}', '\u{1FFB}') {
308                    // Look in the other trie due to oxia singleton
309                    // mappings to corresponding character with tonos.
310                    break;
311                }
312                let starter = char_from_u32(decomposition & 0x7FFF);
313                let combining = char_from_u32((decomposition >> 15) & 0x7FFF);
314                return Decomposed::Expansion(starter, combining);
315            }
316            if high_zeros {
317                // Decomposition into one BMP character or non-starter
318                if trie_value_has_ccc(decomposition) {
319                    // Non-starter
320                    if !in_inclusive_range(c, '\u{0340}', '\u{0F81}') {
321                        return Decomposed::Default;
322                    }
323                    return match c {
324                        '\u{0340}' => {
325                            // COMBINING GRAVE TONE MARK
326                            Decomposed::Singleton('\u{0300}')
327                        }
328                        '\u{0341}' => {
329                            // COMBINING ACUTE TONE MARK
330                            Decomposed::Singleton('\u{0301}')
331                        }
332                        '\u{0343}' => {
333                            // COMBINING GREEK KORONIS
334                            Decomposed::Singleton('\u{0313}')
335                        }
336                        '\u{0344}' => {
337                            // COMBINING GREEK DIALYTIKA TONOS
338                            Decomposed::Expansion('\u{0308}', '\u{0301}')
339                        }
340                        '\u{0F73}' => {
341                            // TIBETAN VOWEL SIGN II
342                            Decomposed::Expansion('\u{0F71}', '\u{0F72}')
343                        }
344                        '\u{0F75}' => {
345                            // TIBETAN VOWEL SIGN UU
346                            Decomposed::Expansion('\u{0F71}', '\u{0F74}')
347                        }
348                        '\u{0F81}' => {
349                            // TIBETAN VOWEL SIGN REVERSED II
350                            Decomposed::Expansion('\u{0F71}', '\u{0F80}')
351                        }
352                        _ => Decomposed::Default,
353                    };
354                }
355                let singleton = decomposition as u16;
356                debug_assert_ne!(
357                    singleton, FDFA_MARKER,
358                    "How come we got the U+FDFA NFKD marker here?"
359                );
360                return Decomposed::Singleton(char_from_u16(singleton));
361            }
362            if c == '\u{212B}' {
363                // ANGSTROM SIGN
364                return Decomposed::Singleton('\u{00C5}');
365            }
366            // Only 12 of 14 bits used as of Unicode 16.
367            let offset = (((decomposition & !(0b11 << 30)) >> 16) as usize) - 1;
368            // Only 3 of 4 bits used as of Unicode 16.
369            let len_bits = decomposition & 0b1111;
370            let tables = self.tables;
371            if offset < tables.scalars16.len() {
372                if len_bits != 0 {
373                    // i.e. logical len isn't 2
374                    break;
375                }
376                if let Some(first) = tables.scalars16.get(offset) {
377                    if let Some(second) = tables.scalars16.get(offset + 1) {
378                        // Two BMP starters
379                        return Decomposed::Expansion(char_from_u16(first), char_from_u16(second));
380                    }
381                }
382                // GIGO case
383                debug_assert!(false);
384                return Decomposed::Default;
385            }
386            let len = len_bits + 1;
387            if len > 2 {
388                break;
389            }
390            let offset24 = offset - tables.scalars16.len();
391            if let Some(first_c) = tables.scalars24.get(offset24) {
392                if len == 1 {
393                    return Decomposed::Singleton(first_c);
394                }
395                if let Some(second_c) = tables.scalars24.get(offset24 + 1) {
396                    return Decomposed::Expansion(first_c, second_c);
397                }
398            }
399            // GIGO case
400            debug_assert!(false);
401            return Decomposed::Default;
402        }
403        let non_recursive = self.non_recursive;
404        let non_recursive_decomposition = non_recursive.trie.get(c);
405        if non_recursive_decomposition == 0 {
406            // GIGO case
407            debug_assert!(false);
408            return Decomposed::Default;
409        }
410        let trail_or_complex = (non_recursive_decomposition >> 16) as u16;
411        let lead = non_recursive_decomposition as u16;
412        if lead != 0 && trail_or_complex != 0 {
413            // Decomposition into two BMP characters
414            return Decomposed::Expansion(char_from_u16(lead), char_from_u16(trail_or_complex));
415        }
416        if lead != 0 {
417            // Decomposition into one BMP character
418            return Decomposed::Singleton(char_from_u16(lead));
419        }
420        // Decomposition into two non-BMP characters
421        // Low is offset into a table plus one to keep it non-zero.
422        let offset = usize::from(trail_or_complex - 1);
423        if let Some(first) = non_recursive.scalars24.get(offset) {
424            if let Some(second) = non_recursive.scalars24.get(offset + 1) {
425                return Decomposed::Expansion(first, second);
426            }
427        }
428        // GIGO case
429        debug_assert!(false);
430        Decomposed::Default
431    }
432}
433
434/// The raw (non-recursive) canonical decomposition operation.
435///
436/// Callers should generally use `DecomposingNormalizer` instead of this API.
437/// However, this API is provided for callers such as HarfBuzz that specifically
438/// want access to non-recursive canonical decomposition e.g. for use in a
439/// glyph-availability-guided custom normalizer.
440#[derive(Debug)]
441pub struct CanonicalDecomposition {
442    decompositions: DataPayload<NormalizerNfdDataV1>,
443    tables: DataPayload<NormalizerNfdTablesV1>,
444    non_recursive: DataPayload<NormalizerNfdSupplementV1>,
445}
446
447#[cfg(feature = "compiled_data")]
448impl Default for CanonicalDecomposition {
449    fn default() -> Self {
450        Self::new().static_to_owned()
451    }
452}
453
454impl CanonicalDecomposition {
455    /// Constructs a borrowed version of this type for more efficient querying.
456    pub fn as_borrowed(&self) -> CanonicalDecompositionBorrowed<'_> {
457        CanonicalDecompositionBorrowed {
458            decompositions: self.decompositions.get(),
459            tables: self.tables.get(),
460            non_recursive: self.non_recursive.get(),
461        }
462    }
463
464    /// Construct from compiled data.
465    ///
466    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
467    ///
468    /// [πŸ“š Help choosing a constructor](icu_provider::constructors)
469    #[cfg(feature = "compiled_data")]
470    #[allow(clippy::new_ret_no_self)]
471    pub const fn new() -> CanonicalDecompositionBorrowed<'static> {
472        CanonicalDecompositionBorrowed::new()
473    }
474
475    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
476        functions: [
477            new: skip,
478            try_new_with_buffer_provider,
479            try_new_unstable,
480            Self,
481        ]
482    );
483
484    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
485    pub fn try_new_unstable<D>(provider: &D) -> Result<Self, DataError>
486    where
487        D: DataProvider<NormalizerNfdDataV1>
488            + DataProvider<NormalizerNfdTablesV1>
489            + DataProvider<NormalizerNfdSupplementV1>
490            + ?Sized,
491    {
492        let decompositions: DataPayload<NormalizerNfdDataV1> =
493            provider.load(Default::default())?.payload;
494        let tables: DataPayload<NormalizerNfdTablesV1> = provider.load(Default::default())?.payload;
495
496        if tables.get().scalars16.len() + tables.get().scalars24.len() > 0xFFF {
497            // The data is from a future where there exists a normalization flavor whose
498            // complex decompositions take more than 0xFFF but fewer than 0x1FFF code points
499            // of space. If a good use case from such a decomposition flavor arises, we can
500            // dynamically change the bit masks so that the length mask becomes 0x1FFF instead
501            // of 0xFFF and the all-non-starters mask becomes 0 instead of 0x1000. However,
502            // since for now the masks are hard-coded, error out.
503            return Err(DataError::custom("future extension"));
504        }
505
506        let non_recursive: DataPayload<NormalizerNfdSupplementV1> =
507            provider.load(Default::default())?.payload;
508
509        Ok(CanonicalDecomposition {
510            decompositions,
511            tables,
512            non_recursive,
513        })
514    }
515}
516
517/// Borrowed version of lookup of the Canonical_Combining_Class Unicode property.
518///
519/// # Example
520///
521/// ```
522/// use icu::properties::props::CanonicalCombiningClass;
523/// use icu::normalizer::properties::CanonicalCombiningClassMapBorrowed;
524///
525/// let map = CanonicalCombiningClassMapBorrowed::new();
526/// assert_eq!(map.get('a'), CanonicalCombiningClass::NotReordered); // U+0061: LATIN SMALL LETTER A
527/// assert_eq!(map.get32(0x0301), CanonicalCombiningClass::Above); // U+0301: COMBINING ACUTE ACCENT
528/// ```
529#[derive(Debug)]
530pub struct CanonicalCombiningClassMapBorrowed<'a> {
531    /// The data trie
532    decompositions: &'a DecompositionData<'a>,
533}
534
535#[cfg(feature = "compiled_data")]
536impl Default for CanonicalCombiningClassMapBorrowed<'static> {
537    fn default() -> Self {
538        Self::new()
539    }
540}
541
542impl CanonicalCombiningClassMapBorrowed<'static> {
543    /// Cheaply converts a [`CanonicalCombiningClassMapBorrowed<'static>`] into a [`CanonicalCombiningClassMap`].
544    ///
545    /// Note: Due to branching and indirection, using [`CanonicalCombiningClassMap`] might inhibit some
546    /// compile-time optimizations that are possible with [`CanonicalCombiningClassMapBorrowed`].
547    pub const fn static_to_owned(self) -> CanonicalCombiningClassMap {
548        CanonicalCombiningClassMap {
549            decompositions: DataPayload::from_static_ref(self.decompositions),
550        }
551    }
552
553    /// Construct from compiled data.
554    ///
555    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
556    ///
557    /// [πŸ“š Help choosing a constructor](icu_provider::constructors)
558    #[cfg(feature = "compiled_data")]
559    pub const fn new() -> Self {
560        CanonicalCombiningClassMapBorrowed {
561            decompositions: crate::provider::Baked::SINGLETON_NORMALIZER_NFD_DATA_V1,
562        }
563    }
564}
565
566impl CanonicalCombiningClassMapBorrowed<'_> {
567    /// Look up the canonical combining class for a scalar value.
568    ///
569    /// The return value is a u8 representing the canonical combining class,
570    /// you may enable the `"icu_properties"` feature if you would like to use a typed
571    /// `CanonicalCombiningClass`.
572    #[inline(always)]
573    pub fn get_u8(&self, c: char) -> u8 {
574        self.get32_u8(u32::from(c))
575    }
576
577    /// Look up the canonical combining class for a scalar value
578    /// represented as `u32`. If the argument is outside the scalar
579    /// value range, `Not_Reordered` is returned.
580    ///
581    /// The return value is a u8 representing the canonical combining class,
582    /// you may enable the `"icu_properties"` feature if you would like to use a typed
583    /// `CanonicalCombiningClass`.
584    pub fn get32_u8(&self, c: u32) -> u8 {
585        let trie_value = self.decompositions.trie.get32(c);
586        if trie_value_has_ccc(trie_value) {
587            trie_value as u8
588        } else {
589            ccc!(NotReordered, 0).to_icu4c_value()
590        }
591    }
592
593    /// Look up the canonical combining class for a scalar value
594    ///
595    /// ✨ *Enabled with the `icu_properties` Cargo feature.*
596    #[inline(always)]
597    #[cfg(feature = "icu_properties")]
598    pub fn get(&self, c: char) -> CanonicalCombiningClass {
599        CanonicalCombiningClass::from_icu4c_value(self.get_u8(c))
600    }
601
602    /// Look up the canonical combining class for a scalar value
603    /// represented as `u32`. If the argument is outside the scalar
604    /// value range, `CanonicalCombiningClass::NotReordered` is returned.
605    ///
606    /// ✨ *Enabled with the `icu_properties` Cargo feature.*
607    #[cfg(feature = "icu_properties")]
608    pub fn get32(&self, c: u32) -> CanonicalCombiningClass {
609        CanonicalCombiningClass::from_icu4c_value(self.get32_u8(c))
610    }
611}
612
613/// Lookup of the Canonical_Combining_Class Unicode property.
614#[derive(Debug)]
615pub struct CanonicalCombiningClassMap {
616    /// The data trie
617    decompositions: DataPayload<NormalizerNfdDataV1>,
618}
619
620#[cfg(feature = "compiled_data")]
621impl Default for CanonicalCombiningClassMap {
622    fn default() -> Self {
623        Self::new().static_to_owned()
624    }
625}
626
627impl CanonicalCombiningClassMap {
628    /// Constructs a borrowed version of this type for more efficient querying.
629    pub fn as_borrowed(&self) -> CanonicalCombiningClassMapBorrowed<'_> {
630        CanonicalCombiningClassMapBorrowed {
631            decompositions: self.decompositions.get(),
632        }
633    }
634
635    /// Construct from compiled data.
636    ///
637    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
638    ///
639    /// [πŸ“š Help choosing a constructor](icu_provider::constructors)
640    #[cfg(feature = "compiled_data")]
641    #[allow(clippy::new_ret_no_self)]
642    pub const fn new() -> CanonicalCombiningClassMapBorrowed<'static> {
643        CanonicalCombiningClassMapBorrowed::new()
644    }
645
646    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
647        functions: [
648            new: skip,
649            try_new_with_buffer_provider,
650            try_new_unstable,
651            Self,
652    ]);
653
654    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
655    pub fn try_new_unstable<D>(provider: &D) -> Result<Self, DataError>
656    where
657        D: DataProvider<NormalizerNfdDataV1> + ?Sized,
658    {
659        let decompositions: DataPayload<NormalizerNfdDataV1> =
660            provider.load(Default::default())?.payload;
661        Ok(CanonicalCombiningClassMap { decompositions })
662    }
663}