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}