Skip to main content

icu_normalizer/
lib.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// https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations
6#![cfg_attr(not(any(test, doc)), no_std)]
7#![cfg_attr(
8    not(test),
9    deny(
10        clippy::indexing_slicing,
11        clippy::unwrap_used,
12        clippy::expect_used,
13        clippy::panic,
14    )
15)]
16#![warn(missing_docs)]
17
18//! Normalizing text into Unicode Normalization Forms.
19//!
20//! This module is published as its own crate ([`icu_normalizer`](https://docs.rs/icu_normalizer/latest/icu_normalizer/))
21//! and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latter for more details on the ICU4X project.
22//!
23//! # Functionality
24//!
25//! The top level of the crate provides normalization of input into the four normalization forms defined in [UAX #15: Unicode
26//! Normalization Forms](https://www.unicode.org/reports/tr15/): NFC, NFD, NFKC, and NFKD.
27//!
28//! Three kinds of contiguous inputs are supported: known-well-formed UTF-8 (`&str`), potentially-not-well-formed UTF-8,
29//! and potentially-not-well-formed UTF-16. Additionally, an iterator over `char` can be wrapped in a normalizing iterator.
30//!
31//! The `uts46` module provides the combination of mapping and normalization operations for [UTS #46: Unicode IDNA
32//! Compatibility Processing](https://www.unicode.org/reports/tr46/). This functionality is not meant to be used by
33//! applications directly. Instead, it is meant as a building block for a full implementation of UTS #46, such as the
34//! [`idna`](https://docs.rs/idna/latest/idna/) crate.
35//!
36//! The `properties` module provides the non-recursive canonical decomposition operation on a per `char` basis and
37//! the canonical compositon operation given two `char`s. It also provides access to the Canonical Combining Class
38//! property. These operations are primarily meant for [HarfBuzz](https://harfbuzz.github.io/), the types
39//! [`CanonicalComposition`](properties::CanonicalComposition), [`CanonicalDecomposition`](properties::CanonicalDecomposition),
40//! and [`CanonicalCombiningClassMap`](properties::CanonicalCombiningClassMap) implement the [`harfbuzz_traits`] if
41//! the `harfbuzz_traits` Cargo feature is enabled.
42//!
43//! Notably, this normalizer does _not_ provide the normalization “quick check” that can result in “maybe” in
44//! addition to “yes” and “no”. The normalization checks provided by this crate always give a definitive
45//! non-“maybe” answer.
46//!
47//! # Examples
48//!
49//! ```
50//! let nfc = icu_normalizer::ComposingNormalizerBorrowed::new_nfc();
51//! assert_eq!(nfc.normalize("a\u{0308}"), "ä");
52//! assert!(nfc.is_normalized("ä"));
53//!
54//! let nfd = icu_normalizer::DecomposingNormalizerBorrowed::new_nfd();
55//! assert_eq!(nfd.normalize("ä"), "a\u{0308}");
56//! assert!(!nfd.is_normalized("ä"));
57//! ```
58
59extern crate alloc;
60
61// TODO: The plan is to replace
62// `#[cfg(not(icu4x_unstable_fast_trie_only))]`
63// with
64// `#[cfg(feature = "serde")]`
65// and
66// `#[cfg(icu4x_unstable_fast_trie_only)]`
67// with
68// `#[cfg(not(feature = "serde"))]`
69//
70// Before doing so:
71// * The type of the UTS 46 trie needs to be
72//   disentangled from the type of the NFD/NFKD tries.
73//   This will involve a more generic iterator hidden
74//   inside the public iterator types.
75// * datagen needs to emit fast-mode tries for the
76//   NFD and NFKD tries.
77// * The markers and possibly the data struct type
78//   for NFD and NFKD need to be revised per policy.
79
80#[cfg(not(icu4x_unstable_fast_trie_only))]
81type Trie<'trie> = CodePointTrie<'trie, u32>;
82
83#[cfg(icu4x_unstable_fast_trie_only)]
84type Trie<'trie> = FastCodePointTrie<'trie, u32>;
85
86#[cfg(feature = "harfbuzz_traits")]
87mod harfbuzz;
88pub mod properties;
89pub mod provider;
90pub mod uts46;
91
92use crate::provider::CanonicalCompositions;
93use crate::provider::DecompositionData;
94use crate::provider::NormalizerNfdDataV1;
95use crate::provider::NormalizerNfkdDataV1;
96use crate::provider::NormalizerUts46DataV1;
97use alloc::borrow::Cow;
98use alloc::string::String;
99use icu_collections::char16trie::Char16Trie;
100use icu_collections::char16trie::Char16TrieIterator;
101use icu_collections::char16trie::TrieResult;
102#[cfg(not(icu4x_unstable_fast_trie_only))]
103use icu_collections::codepointtrie::CodePointTrie;
104#[cfg(icu4x_unstable_fast_trie_only)]
105use icu_collections::codepointtrie::FastCodePointTrie;
106#[cfg(icu4x_unstable_fast_trie_only)]
107use icu_collections::codepointtrie::TypedCodePointTrie;
108#[cfg(feature = "icu_properties")]
109use icu_properties::props::CanonicalCombiningClass;
110use icu_provider::prelude::*;
111use provider::DecompositionTables;
112use provider::NormalizerNfcV1;
113use provider::NormalizerNfdTablesV1;
114use provider::NormalizerNfkdTablesV1;
115use smallvec::SmallVec;
116#[cfg(feature = "utf8_iter")]
117use utf8_iter::Utf8CharsEx;
118#[cfg(feature = "utf16_iter")]
119use utf16_iter::Utf16CharsEx;
120use zerovec::{ZeroSlice, zeroslice};
121
122// The optimizations in the area where `likely` is used
123// are extremely brittle. `likely` is useful in the typed-trie
124// case on the UTF-16 fast path, but in order not to disturb
125// the untyped-trie case on the UTF-16 fast path, make the
126// annotations no-ops in the untyped-trie case.
127
128// `cold_path` and `likely` come from
129// https://github.com/rust-lang/hashbrown/commit/64bd7db1d1b148594edfde112cdb6d6260e2cfc3 .
130// See https://github.com/rust-lang/hashbrown/commit/64bd7db1d1b148594edfde112cdb6d6260e2cfc3#commitcomment-164768806
131// for permission to relicense under Unicode-3.0.
132
133#[cfg(all(icu4x_unstable_fast_trie_only, feature = "utf16_iter"))]
134#[inline(always)]
135#[cold]
136fn cold_path() {}
137
138#[cfg(all(icu4x_unstable_fast_trie_only, feature = "utf16_iter"))]
139#[inline(always)]
140pub(crate) fn likely(b: bool) -> bool {
141    if b {
142        true
143    } else {
144        cold_path();
145        false
146    }
147}
148
149// End import from https://github.com/rust-lang/hashbrown/commit/64bd7db1d1b148594edfde112cdb6d6260e2cfc3 .
150
151/// No-op for typed trie case.
152#[cfg(all(not(icu4x_unstable_fast_trie_only), feature = "utf16_iter"))]
153#[inline(always)]
154fn likely(b: bool) -> bool {
155    b
156}
157
158// This type exists as a shim for `icu_properties` `CanonicalCombiningClass` when the crate is disabled
159// It should not be exposed to users.
160#[cfg(not(feature = "icu_properties"))]
161#[derive(#[automatically_derived]
impl ::core::marker::Copy for CanonicalCombiningClass { }Copy, #[automatically_derived]
impl ::core::clone::Clone for CanonicalCombiningClass {
    #[inline]
    fn clone(&self) -> CanonicalCombiningClass {
        let _: ::core::clone::AssertParamIsClone<u8>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::cmp::Eq for CanonicalCombiningClass {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<u8>;
    }
}Eq, #[automatically_derived]
impl ::core::cmp::PartialEq for CanonicalCombiningClass {
    #[inline]
    fn eq(&self, other: &CanonicalCombiningClass) -> bool {
        self.0 == other.0
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::PartialOrd for CanonicalCombiningClass {
    #[inline]
    fn partial_cmp(&self, other: &CanonicalCombiningClass)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
    }
}PartialOrd, #[automatically_derived]
impl ::core::cmp::Ord for CanonicalCombiningClass {
    #[inline]
    fn cmp(&self, other: &CanonicalCombiningClass) -> ::core::cmp::Ordering {
        ::core::cmp::Ord::cmp(&self.0, &other.0)
    }
}Ord)]
162struct CanonicalCombiningClass(u8);
163
164#[cfg(not(feature = "icu_properties"))]
165#[allow(non_upper_case_globals)]
166impl CanonicalCombiningClass {
167    // See https://www.unicode.org/reports/tr44/#Canonical_Combining_Class_Values
168    const NotReordered: Self = Self(0);
169    const Above: Self = Self(230);
170    const KanaVoicing: Self = Self(8);
171}
172
173/// Treatment of the ignorable marker (0xFFFFFFFF) in data.
174#[derive(#[automatically_derived]
impl ::core::fmt::Debug for IgnorableBehavior {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                IgnorableBehavior::Unsupported => "Unsupported",
                IgnorableBehavior::Ignored => "Ignored",
                IgnorableBehavior::ReplacementCharacter =>
                    "ReplacementCharacter",
            })
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for IgnorableBehavior {
    #[inline]
    fn eq(&self, other: &IgnorableBehavior) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for IgnorableBehavior {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {}
}Eq)]
175enum IgnorableBehavior {
176    /// 0xFFFFFFFF in data is not supported.
177    Unsupported,
178    /// Ignorables are ignored.
179    Ignored,
180    /// Ignorables are treated as singleton decompositions
181    /// to the REPLACEMENT CHARACTER.
182    ReplacementCharacter,
183}
184
185/// Marker for UTS 46 ignorables.
186///
187/// See trie-value-format.md
188const IGNORABLE_MARKER: u32 = 0xFFFFFFFF;
189
190/// Marker that the decomposition does not round trip via NFC.
191///
192/// See trie-value-format.md
193const NON_ROUND_TRIP_MARKER: u32 = 1 << 30;
194
195/// Marker that the first character of the decomposition
196/// can combine backwards.
197///
198/// See trie-value-format.md
199const BACKWARD_COMBINING_MARKER: u32 = 1 << 31;
200
201/// Mask for the bits have to be zero for this to be a BMP
202/// singleton decomposition, or value baked into the surrogate
203/// range.
204///
205/// See trie-value-format.md
206const HIGH_ZEROS_MASK: u32 = 0x3FFF0000;
207
208/// Mask for the bits have to be zero for this to be a complex
209/// decomposition.
210///
211/// See trie-value-format.md
212const LOW_ZEROS_MASK: u32 = 0xFFE0;
213
214/// Checks if a trie value carries a (non-zero) canonical
215/// combining class.
216///
217/// See trie-value-format.md
218#[inline]
219fn trie_value_has_ccc(trie_value: u32) -> bool {
220    (trie_value & 0x3FFFFE00) == 0xD800
221}
222
223/// Checks if the trie signifies a special non-starter decomposition.
224///
225/// See trie-value-format.md
226fn trie_value_indicates_special_non_starter_decomposition(trie_value: u32) -> bool {
227    (trie_value & 0x3FFFFF00) == 0xD900
228}
229
230/// Checks if a trie value signifies a character whose decomposition
231/// starts with a non-starter.
232///
233/// See trie-value-format.md
234fn decomposition_starts_with_non_starter(trie_value: u32) -> bool {
235    trie_value_has_ccc(trie_value)
236}
237
238/// Extracts a canonical combining class (possibly zero) from a trie value.
239///
240/// See trie-value-format.md
241fn ccc_from_trie_value(trie_value: u32) -> CanonicalCombiningClass {
242    if trie_value_has_ccc(trie_value) {
243        CanonicalCombiningClass(trie_value as u8)
244    } else {
245        CanonicalCombiningClass::NotReordered
246    }
247}
248
249/// The tail (everything after the first character) of the NFKD form U+FDFA
250/// as 16-bit units.
251static FDFA_NFKD: [u16; 17] = [
252    0x644, 0x649, 0x20, 0x627, 0x644, 0x644, 0x647, 0x20, 0x639, 0x644, 0x64A, 0x647, 0x20, 0x648,
253    0x633, 0x644, 0x645,
254];
255
256/// Marker value for U+FDFA in NFKD. (Unified with Hangul syllable marker,
257/// but they differ by `NON_ROUND_TRIP_MARKER`.)
258///
259/// See trie-value-format.md
260const FDFA_MARKER: u16 = 1;
261
262// These constants originate from page 143 of Unicode 14.0
263/// Syllable base
264const HANGUL_S_BASE: u32 = 0xAC00;
265/// Lead jamo base
266const HANGUL_L_BASE: u32 = 0x1100;
267/// Vowel jamo base
268const HANGUL_V_BASE: u32 = 0x1161;
269/// Trail jamo base (deliberately off by one to account for the absence of a trail)
270const HANGUL_T_BASE: u32 = 0x11A7;
271/// Lead jamo count
272const HANGUL_L_COUNT: u32 = 19;
273/// Vowel jamo count
274const HANGUL_V_COUNT: u32 = 21;
275/// Trail jamo count (deliberately off by one to account for the absence of a trail)
276const HANGUL_T_COUNT: u32 = 28;
277/// Vowel jamo count times trail jamo count
278const HANGUL_N_COUNT: u32 = 588;
279/// Syllable count
280const HANGUL_S_COUNT: u32 = 11172;
281
282/// One past the conjoining jamo block
283const HANGUL_JAMO_LIMIT: u32 = 0x1200;
284
285/// If `opt` is `Some`, unwrap it. If `None`, panic if debug assertions
286/// are enabled and return `default` if debug assertions are not enabled.
287///
288/// Use this only if the only reason why `opt` could be `None` is bogus
289/// data from the provider.
290#[inline(always)]
291fn unwrap_or_gigo<T>(opt: Option<T>, default: T) -> T {
292    if let Some(val) = opt {
293        val
294    } else {
295        // GIGO case
296        if true {
    if !false { ::core::panicking::panic("assertion failed: false") };
};debug_assert!(false);
297        default
298    }
299}
300
301/// Convert a `u32` _obtained from data provider data_ to `char`.
302#[inline(always)]
303fn char_from_u32(u: u32) -> char {
304    unwrap_or_gigo(char::from_u32(u), char::REPLACEMENT_CHARACTER)
305}
306
307/// Convert a `u16` _obtained from data provider data_ to `char`.
308#[inline(always)]
309fn char_from_u16(u: u16) -> char {
310    char_from_u32(u32::from(u))
311}
312
313const EMPTY_U16: &ZeroSlice<u16> = ::zerovec::ZeroSlice::new_empty()zeroslice![];
314
315const EMPTY_CHAR: &ZeroSlice<char> = ::zerovec::ZeroSlice::new_empty()zeroslice![];
316
317#[inline(always)]
318fn in_inclusive_range(c: char, start: char, end: char) -> bool {
319    u32::from(c).wrapping_sub(u32::from(start)) <= (u32::from(end) - u32::from(start))
320}
321
322#[inline(always)]
323#[cfg(feature = "utf16_iter")]
324fn in_inclusive_range16(u: u16, start: u16, end: u16) -> bool {
325    u.wrapping_sub(start) <= (end - start)
326}
327
328/// Performs canonical composition (including Hangul) on a pair of
329/// characters or returns `None` if these characters don't compose.
330/// Composition exclusions are taken into account.
331#[inline]
332fn compose(iter: Char16TrieIterator, starter: char, second: char) -> Option<char> {
333    let v = u32::from(second).wrapping_sub(HANGUL_V_BASE);
334    if v >= HANGUL_JAMO_LIMIT - HANGUL_V_BASE {
335        return compose_non_hangul(iter, starter, second);
336    }
337    if v < HANGUL_V_COUNT {
338        let l = u32::from(starter).wrapping_sub(HANGUL_L_BASE);
339        if l < HANGUL_L_COUNT {
340            let lv = l * HANGUL_N_COUNT + v * HANGUL_T_COUNT;
341            // Safe, because the inputs are known to be in range.
342            return Some(unsafe { char::from_u32_unchecked(HANGUL_S_BASE + lv) });
343        }
344        return None;
345    }
346    if in_inclusive_range(second, '\u{11A8}', '\u{11C2}') {
347        let lv = u32::from(starter).wrapping_sub(HANGUL_S_BASE);
348        if lv < HANGUL_S_COUNT && lv % HANGUL_T_COUNT == 0 {
349            let lvt = lv + (u32::from(second) - HANGUL_T_BASE);
350            // Safe, because the inputs are known to be in range.
351            return Some(unsafe { char::from_u32_unchecked(HANGUL_S_BASE + lvt) });
352        }
353    }
354    None
355}
356
357/// Performs (non-Hangul) canonical composition on a pair of characters
358/// or returns `None` if these characters don't compose. Composition
359/// exclusions are taken into account.
360fn compose_non_hangul(mut iter: Char16TrieIterator, starter: char, second: char) -> Option<char> {
361    // To make the trie smaller, the pairs are stored second character first.
362    // Given how this method is used in ways where it's known that `second`
363    // is or isn't a starter. We could potentially split the trie into two
364    // tries depending on whether `second` is a starter.
365    match iter.next(second) {
366        TrieResult::NoMatch => None,
367        TrieResult::NoValue => match iter.next(starter) {
368            TrieResult::NoMatch => None,
369            TrieResult::FinalValue(i) => {
370                if let Some(c) = char::from_u32(i as u32) {
371                    Some(c)
372                } else {
373                    // GIGO case
374                    if true {
    if !false { ::core::panicking::panic("assertion failed: false") };
};debug_assert!(false);
375                    None
376                }
377            }
378            TrieResult::NoValue | TrieResult::Intermediate(_) => {
379                // GIGO case
380                if true {
    if !false { ::core::panicking::panic("assertion failed: false") };
};debug_assert!(false);
381                None
382            }
383        },
384        TrieResult::FinalValue(_) | TrieResult::Intermediate(_) => {
385            // GIGO case
386            if true {
    if !false { ::core::panicking::panic("assertion failed: false") };
};debug_assert!(false);
387            None
388        }
389    }
390}
391
392/// See trie-value-format.md
393#[inline(always)]
394fn starter_and_decomposes_to_self_impl(trie_val: u32) -> bool {
395    // The REPLACEMENT CHARACTER has `NON_ROUND_TRIP_MARKER` set,
396    // and this function needs to ignore that.
397    (trie_val & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0
398}
399
400/// See trie-value-format.md
401#[inline(always)]
402fn potential_passthrough_and_cannot_combine_backwards_impl(trie_val: u32) -> bool {
403    (trie_val & (NON_ROUND_TRIP_MARKER | BACKWARD_COMBINING_MARKER)) == 0
404}
405
406/// Struct for holding together a character and the value
407/// looked up for it from the NFD trie in a more explicit
408/// way than an anonymous pair.
409/// Also holds a flag about the supplementary-trie provenance.
410#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CharacterAndTrieValue {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "CharacterAndTrieValue", "character", &self.character, "trie_val",
            &&self.trie_val)
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for CharacterAndTrieValue {
    #[inline]
    fn eq(&self, other: &CharacterAndTrieValue) -> bool {
        self.character == other.character && self.trie_val == other.trie_val
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for CharacterAndTrieValue {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<char>;
        let _: ::core::cmp::AssertParamIsEq<u32>;
    }
}Eq)]
411struct CharacterAndTrieValue {
412    character: char,
413    /// See trie-value-format.md
414    trie_val: u32,
415}
416
417impl CharacterAndTrieValue {
418    #[inline(always)]
419    pub fn new(c: char, trie_value: u32) -> Self {
420        CharacterAndTrieValue {
421            character: c,
422            trie_val: trie_value,
423        }
424    }
425
426    #[inline(always)]
427    pub fn starter_and_decomposes_to_self(&self) -> bool {
428        starter_and_decomposes_to_self_impl(self.trie_val)
429    }
430
431    /// See trie-value-format.md
432    #[inline(always)]
433    #[cfg(feature = "utf8_iter")]
434    pub fn starter_and_decomposes_to_self_except_replacement(&self) -> bool {
435        // This intentionally leaves `NON_ROUND_TRIP_MARKER` in the value
436        // to be compared with zero. U+FFFD has that flag set despite really
437        // being being round-tripping in order to make UTF-8 errors
438        // ineligible for passthrough.
439        (self.trie_val & !BACKWARD_COMBINING_MARKER) == 0
440    }
441
442    /// See trie-value-format.md
443    #[inline(always)]
444    pub fn can_combine_backwards(&self) -> bool {
445        (self.trie_val & BACKWARD_COMBINING_MARKER) != 0
446    }
447    /// See trie-value-format.md
448    #[inline(always)]
449    pub fn potential_passthrough(&self) -> bool {
450        (self.trie_val & NON_ROUND_TRIP_MARKER) == 0
451    }
452    /// See trie-value-format.md
453    #[inline(always)]
454    pub fn potential_passthrough_and_cannot_combine_backwards(&self) -> bool {
455        potential_passthrough_and_cannot_combine_backwards_impl(self.trie_val)
456    }
457}
458
459/// Pack a `char` and a `CanonicalCombiningClass` in
460/// 32 bits (the former in the lower 24 bits and the
461/// latter in the high 8 bits). The latter can be
462/// initialized to 0xFF upon creation, in which case
463/// it can be actually set later by calling
464/// `set_ccc_from_trie_if_not_already_set`. This is
465/// a micro optimization to avoid the Canonical
466/// Combining Class trie lookup when there is only
467/// one combining character in a sequence. This type
468/// is intentionally non-`Copy` to get compiler help
469/// in making sure that the class is set on the
470/// instance on which it is intended to be set
471/// and not on a temporary copy.
472///
473/// Note that 0xFF is won't be assigned to an actual
474/// canonical combining class per definition D104
475/// in The Unicode Standard.
476//
477// NOTE: The Pernosco debugger has special knowledge
478// of this struct. Please do not change the bit layout
479// or the crate-module-qualified name of this struct
480// without coordination.
481#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CharacterAndClass {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f,
            "CharacterAndClass", &&self.0)
    }
}Debug)]
482struct CharacterAndClass(u32);
483
484impl CharacterAndClass {
485    pub fn new(c: char, ccc: CanonicalCombiningClass) -> Self {
486        CharacterAndClass(u32::from(c) | (u32::from(ccc.0) << 24))
487    }
488    pub fn new_with_placeholder(c: char) -> Self {
489        CharacterAndClass(u32::from(c) | ((0xFF) << 24))
490    }
491    pub fn new_with_trie_value(c_tv: CharacterAndTrieValue) -> Self {
492        Self::new(c_tv.character, ccc_from_trie_value(c_tv.trie_val))
493    }
494    pub fn new_starter(c: char) -> Self {
495        CharacterAndClass(u32::from(c))
496    }
497    /// This method must exist for Pernosco to apply its special rendering.
498    /// Also, this must not be dead code!
499    pub fn character(&self) -> char {
500        // Safe, because the low 24 bits came from a `char`
501        // originally.
502        unsafe { char::from_u32_unchecked(self.0 & 0xFFFFFF) }
503    }
504    /// This method must exist for Pernosco to apply its special rendering.
505    pub fn ccc(&self) -> CanonicalCombiningClass {
506        CanonicalCombiningClass((self.0 >> 24) as u8)
507    }
508
509    pub fn character_and_ccc(&self) -> (char, CanonicalCombiningClass) {
510        (self.character(), self.ccc())
511    }
512    pub fn set_ccc_from_trie_if_not_already_set(&mut self, trie: &Trie) {
513        if self.0 >> 24 != 0xFF {
514            return;
515        }
516        let scalar = self.0 & 0xFFFFFF;
517        self.0 = ((ccc_from_trie_value(trie.get32_u32(scalar)).0 as u32) << 24) | scalar;
518    }
519}
520
521// This function exists as a borrow check helper.
522#[inline(always)]
523fn sort_slice_by_ccc(slice: &mut [CharacterAndClass], trie: &Trie) {
524    // We don't look up the canonical combining class for starters
525    // of for single combining characters between starters. When
526    // there's more than one combining character between starters,
527    // we look up the canonical combining class for each character
528    // exactly once.
529    if slice.len() < 2 {
530        return;
531    }
532    slice
533        .iter_mut()
534        .for_each(|cc| cc.set_ccc_from_trie_if_not_already_set(trie));
535    slice.sort_by_key(|cc| cc.ccc());
536}
537
538/// An iterator adaptor that turns an `Iterator` over `char` into
539/// a lazily-decomposed `char` sequence.
540#[derive(#[automatically_derived]
impl<'data, I: ::core::fmt::Debug> ::core::fmt::Debug for
    Decomposition<'data, I> where I: Iterator<Item = char> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["delegate", "buffer", "buffer_pos", "pending", "trie",
                        "scalars16", "scalars24", "supplementary_scalars16",
                        "supplementary_scalars24",
                        "decomposition_passthrough_bound", "ignorable_behavior"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.delegate, &self.buffer, &self.buffer_pos, &self.pending,
                        &self.trie, &self.scalars16, &self.scalars24,
                        &self.supplementary_scalars16,
                        &self.supplementary_scalars24,
                        &self.decomposition_passthrough_bound,
                        &&self.ignorable_behavior];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "Decomposition",
            names, values)
    }
}Debug)]
541pub struct Decomposition<'data, I>
542where
543    I: Iterator<Item = char>,
544{
545    delegate: I,
546    buffer: SmallVec<[CharacterAndClass; 17]>, // Enough to hold NFKD for U+FDFA
547    /// The index of the next item to be read from `buffer`.
548    /// The purpose if this index is to avoid having to move
549    /// the rest upon every read.
550    buffer_pos: usize,
551    // At the start of `next()` if not `None`, this is a pending unnormalized
552    // starter. When `Decomposition` appears alone, this is never a non-starter.
553    // However, when `Decomposition` appears inside a `Composition`, this
554    // may become a non-starter before `decomposing_next()` is called.
555    pending: Option<CharacterAndTrieValue>, // None at end of stream
556    // See trie-value-format.md
557    trie: &'data Trie<'data>,
558    scalars16: &'data ZeroSlice<u16>,
559    scalars24: &'data ZeroSlice<char>,
560    supplementary_scalars16: &'data ZeroSlice<u16>,
561    supplementary_scalars24: &'data ZeroSlice<char>,
562    /// The lowest character for which either of the following does
563    /// not hold:
564    /// 1. Decomposes to self.
565    /// 2. Decomposition starts with a non-starter
566    decomposition_passthrough_bound: u32, // never above 0xC0
567    ignorable_behavior: IgnorableBehavior, // Arguably should be a type parameter
568}
569
570impl<'data, I> Decomposition<'data, I>
571where
572    I: Iterator<Item = char>,
573{
574    /// Constructs a decomposing iterator adapter from a delegate
575    /// iterator and references to the necessary data, without
576    /// supplementary data.
577    ///
578    /// Use `DecomposingNormalizer::normalize_iter()` instead unless
579    /// there's a good reason to use this constructor directly.
580    ///
581    /// Public but hidden in order to be able to use this from the
582    /// collator.
583    #[doc(hidden)] // used in collator
584    pub fn new(
585        delegate: I,
586        decompositions: &'data DecompositionData,
587        tables: &'data DecompositionTables,
588    ) -> Self {
589        Self::new_with_supplements(
590            delegate,
591            decompositions,
592            tables,
593            None,
594            0xC0,
595            IgnorableBehavior::Unsupported,
596        )
597    }
598
599    /// Constructs a decomposing iterator adapter from a delegate
600    /// iterator and references to the necessary data, including
601    /// supplementary data.
602    ///
603    /// Use `DecomposingNormalizer::normalize_iter()` instead unless
604    /// there's a good reason to use this constructor directly.
605    fn new_with_supplements(
606        delegate: I,
607        decompositions: &'data DecompositionData,
608        tables: &'data DecompositionTables,
609        supplementary_tables: Option<&'data DecompositionTables>,
610        decomposition_passthrough_bound: u8,
611        ignorable_behavior: IgnorableBehavior,
612    ) -> Self {
613        let mut ret = Decomposition::<I> {
614            delegate,
615            buffer: SmallVec::new(), // Normalized
616            buffer_pos: 0,
617            // Initialize with a placeholder starter in case
618            // the real stream starts with a non-starter.
619            pending: Some(CharacterAndTrieValue::new('\u{FFFF}', 0)),
620            #[allow(clippy::useless_conversion, clippy::expect_used)] // Expectation always succeeds when untyped tries are in use
621            trie: <&Trie>::try_from(&decompositions.trie).expect("Unexpected trie type in data"),
622            scalars16: &tables.scalars16,
623            scalars24: &tables.scalars24,
624            supplementary_scalars16: if let Some(supplementary) = supplementary_tables {
625                &supplementary.scalars16
626            } else {
627                EMPTY_U16
628            },
629            supplementary_scalars24: if let Some(supplementary) = supplementary_tables {
630                &supplementary.scalars24
631            } else {
632                EMPTY_CHAR
633            },
634            decomposition_passthrough_bound: u32::from(decomposition_passthrough_bound),
635            ignorable_behavior,
636        };
637        let _ = ret.next(); // Remove the U+FFFF placeholder
638        ret
639    }
640
641    fn push_decomposition16(
642        &mut self,
643        offset: usize,
644        len: usize,
645        only_non_starters_in_trail: bool,
646        slice16: &ZeroSlice<u16>,
647    ) -> (char, usize) {
648        let (starter, tail) = slice16
649            .get_subslice(offset..offset + len)
650            .and_then(|slice| slice.split_first())
651            .map_or_else(
652                || {
653                    // GIGO case
654                    if true {
    if !false { ::core::panicking::panic("assertion failed: false") };
};debug_assert!(false);
655                    (char::REPLACEMENT_CHARACTER, EMPTY_U16)
656                },
657                |(first, trail)| (char_from_u16(first), trail),
658            );
659        if only_non_starters_in_trail {
660            // All the rest are combining
661            self.buffer.extend(
662                tail.iter()
663                    .map(|u| CharacterAndClass::new_with_placeholder(char_from_u16(u))),
664            );
665            (starter, 0)
666        } else {
667            let mut i = 0;
668            let mut combining_start = 0;
669            for u in tail.iter() {
670                let ch = char_from_u16(u);
671                let trie_value = self.trie.get(ch);
672                self.buffer.push(CharacterAndClass::new_with_trie_value(
673                    CharacterAndTrieValue::new(ch, trie_value),
674                ));
675                i += 1;
676                // Half-width kana and iota subscript don't occur in the tails
677                // of these multicharacter decompositions.
678                if !decomposition_starts_with_non_starter(trie_value) {
679                    combining_start = i;
680                }
681            }
682            (starter, combining_start)
683        }
684    }
685
686    fn push_decomposition32(
687        &mut self,
688        offset: usize,
689        len: usize,
690        only_non_starters_in_trail: bool,
691        slice32: &ZeroSlice<char>,
692    ) -> (char, usize) {
693        let (starter, tail) = slice32
694            .get_subslice(offset..offset + len)
695            .and_then(|slice| slice.split_first())
696            .unwrap_or_else(|| {
697                // GIGO case
698                if true {
    if !false { ::core::panicking::panic("assertion failed: false") };
};debug_assert!(false);
699                (char::REPLACEMENT_CHARACTER, EMPTY_CHAR)
700            });
701        if only_non_starters_in_trail {
702            // All the rest are combining
703            self.buffer
704                .extend(tail.iter().map(CharacterAndClass::new_with_placeholder));
705            (starter, 0)
706        } else {
707            let mut i = 0;
708            let mut combining_start = 0;
709            for ch in tail.iter() {
710                let trie_value = self.trie.get(ch);
711                self.buffer.push(CharacterAndClass::new_with_trie_value(
712                    CharacterAndTrieValue::new(ch, trie_value),
713                ));
714                i += 1;
715                // Half-width kana and iota subscript don't occur in the tails
716                // of these multicharacter decompositions.
717                if !decomposition_starts_with_non_starter(trie_value) {
718                    combining_start = i;
719                }
720            }
721            (starter, combining_start)
722        }
723    }
724
725    #[inline(always)]
726    fn attach_trie_value(&self, c: char) -> CharacterAndTrieValue {
727        CharacterAndTrieValue::new(c, self.trie.get(c))
728    }
729
730    fn delegate_next_no_pending(&mut self) -> Option<CharacterAndTrieValue> {
731        if true {
    if !self.pending.is_none() {
        ::core::panicking::panic("assertion failed: self.pending.is_none()")
    };
};debug_assert!(self.pending.is_none());
732        loop {
733            let c = self.delegate.next()?;
734
735            // TODO(#2384): Measure if this check is actually an optimization.
736            if u32::from(c) < self.decomposition_passthrough_bound {
737                return Some(CharacterAndTrieValue::new(c, 0));
738            }
739
740            let trie_val = self.trie.get(c);
741            // TODO: Can we do something better about the cost of this branch in the
742            // non-UTS 46 case?
743            if trie_val == IGNORABLE_MARKER {
744                match self.ignorable_behavior {
745                    IgnorableBehavior::Unsupported => {
746                        if true {
    if !false { ::core::panicking::panic("assertion failed: false") };
};debug_assert!(false);
747                    }
748                    IgnorableBehavior::ReplacementCharacter => {
749                        return Some(CharacterAndTrieValue::new(
750                            c,
751                            u32::from(char::REPLACEMENT_CHARACTER) | NON_ROUND_TRIP_MARKER,
752                        ));
753                    }
754                    IgnorableBehavior::Ignored => {
755                        // Else ignore this character by reading the next one from the delegate.
756                        continue;
757                    }
758                }
759            }
760            return Some(CharacterAndTrieValue::new(c, trie_val));
761        }
762    }
763
764    fn delegate_next(&mut self) -> Option<CharacterAndTrieValue> {
765        if let Some(pending) = self.pending.take() {
766            // Only happens as part of `Composition` and as part of
767            // the contiguous-buffer methods of `DecomposingNormalizer`.
768            // I.e. does not happen as part of standalone iterator
769            // usage of `Decomposition`.
770            Some(pending)
771        } else {
772            self.delegate_next_no_pending()
773        }
774    }
775
776    fn decomposing_next(&mut self, c_and_trie_val: CharacterAndTrieValue) -> char {
777        let (starter, combining_start) = {
778            let c = c_and_trie_val.character;
779            // See trie-value-format.md
780            let decomposition = c_and_trie_val.trie_val;
781            // The REPLACEMENT CHARACTER has `NON_ROUND_TRIP_MARKER` set,
782            // and that flag needs to be ignored here.
783            if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0 {
784                // The character is its own decomposition
785                (c, 0)
786            } else {
787                let high_zeros = (decomposition & HIGH_ZEROS_MASK) == 0;
788                let low_zeros = (decomposition & LOW_ZEROS_MASK) == 0;
789                if !high_zeros && !low_zeros {
790                    // Decomposition into two BMP characters: starter and non-starter
791                    let starter = char_from_u32(decomposition & 0x7FFF);
792                    let combining = char_from_u32((decomposition >> 15) & 0x7FFF);
793                    self.buffer
794                        .push(CharacterAndClass::new_with_placeholder(combining));
795                    (starter, 0)
796                } else if high_zeros {
797                    // Do the check by looking at `c` instead of looking at a marker
798                    // in `singleton` below, because if we looked at the trie value,
799                    // we'd still have to check that `c` is in the Hangul syllable
800                    // range in order for the subsequent interpretations as `char`
801                    // to be safe.
802                    // Alternatively, `FDFA_MARKER` and the Hangul marker could
803                    // be unified. That would add a branch for Hangul and remove
804                    // a branch from singleton decompositions. It seems more
805                    // important to favor Hangul syllables than singleton
806                    // decompositions.
807                    // Note that it would be valid to hoist this Hangul check
808                    // one or even two steps earlier in this check hierarchy.
809                    // Right now, it's assumed the kind of decompositions into
810                    // BMP starter and non-starter, which occur in many languages,
811                    // should be checked before Hangul syllables, which are about
812                    // one language specifically. Hopefully, we get some
813                    // instruction-level parallelism out of the disjointness of
814                    // operations on `c` and `decomposition`.
815                    let hangul_offset = u32::from(c).wrapping_sub(HANGUL_S_BASE); // SIndex in the spec
816                    if hangul_offset < HANGUL_S_COUNT {
817                        if true {
    {
        match (&decomposition, &1) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val, ::core::option::Option::None);
                }
            }
        }
    };
};debug_assert_eq!(decomposition, 1);
818                        // Hangul syllable
819                        // The math here comes from page 144 of Unicode 14.0
820                        let l = hangul_offset / HANGUL_N_COUNT;
821                        let v = (hangul_offset % HANGUL_N_COUNT) / HANGUL_T_COUNT;
822                        let t = hangul_offset % HANGUL_T_COUNT;
823
824                        // The unsafe blocks here are OK, because the values stay
825                        // within the Hangul jamo block and, therefore, the scalar
826                        // value range by construction.
827                        self.buffer.push(CharacterAndClass::new_starter(unsafe {
828                            char::from_u32_unchecked(HANGUL_V_BASE + v)
829                        }));
830                        let first = unsafe { char::from_u32_unchecked(HANGUL_L_BASE + l) };
831                        if t != 0 {
832                            self.buffer.push(CharacterAndClass::new_starter(unsafe {
833                                char::from_u32_unchecked(HANGUL_T_BASE + t)
834                            }));
835                            (first, 2)
836                        } else {
837                            (first, 1)
838                        }
839                    } else {
840                        let singleton = decomposition as u16;
841                        if singleton != FDFA_MARKER {
842                            // Decomposition into one BMP character
843                            let starter = char_from_u16(singleton);
844                            (starter, 0)
845                        } else {
846                            // Special case for the NFKD form of U+FDFA.
847                            self.buffer.extend(FDFA_NFKD.map(|u| {
848                                // SAFETY: `FDFA_NFKD` is known not to contain
849                                // surrogates.
850                                CharacterAndClass::new_starter(unsafe {
851                                    char::from_u32_unchecked(u32::from(u))
852                                })
853                            }));
854                            ('\u{0635}', 17)
855                        }
856                    }
857                } else {
858                    if true {
    if !low_zeros { ::core::panicking::panic("assertion failed: low_zeros") };
};debug_assert!(low_zeros);
859                    // Only 12 of 14 bits used as of Unicode 16.
860                    let offset = (((decomposition & !(0b11 << 30)) >> 16) as usize) - 1;
861                    // Only 3 of 4 bits used as of Unicode 16.
862                    let len_bits = decomposition & 0b1111;
863                    let only_non_starters_in_trail = (decomposition & 0b10000) != 0;
864                    if offset < self.scalars16.len() {
865                        self.push_decomposition16(
866                            offset,
867                            (len_bits + 2) as usize,
868                            only_non_starters_in_trail,
869                            self.scalars16,
870                        )
871                    } else if offset < self.scalars16.len() + self.scalars24.len() {
872                        self.push_decomposition32(
873                            offset - self.scalars16.len(),
874                            (len_bits + 1) as usize,
875                            only_non_starters_in_trail,
876                            self.scalars24,
877                        )
878                    } else if offset
879                        < self.scalars16.len()
880                            + self.scalars24.len()
881                            + self.supplementary_scalars16.len()
882                    {
883                        self.push_decomposition16(
884                            offset - (self.scalars16.len() + self.scalars24.len()),
885                            (len_bits + 2) as usize,
886                            only_non_starters_in_trail,
887                            self.supplementary_scalars16,
888                        )
889                    } else {
890                        self.push_decomposition32(
891                            offset
892                                - (self.scalars16.len()
893                                    + self.scalars24.len()
894                                    + self.supplementary_scalars16.len()),
895                            (len_bits + 1) as usize,
896                            only_non_starters_in_trail,
897                            self.supplementary_scalars24,
898                        )
899                    }
900                }
901            }
902        };
903        // Either we're inside `Composition` or `self.pending.is_none()`.
904
905        self.gather_and_sort_combining(combining_start);
906        starter
907    }
908
909    fn gather_and_sort_combining(&mut self, combining_start: usize) {
910        // Not a `for` loop to avoid holding a mutable reference to `self` across
911        // the loop body.
912        while let Some(ch_and_trie_val) = self.delegate_next() {
913            if !trie_value_has_ccc(ch_and_trie_val.trie_val) {
914                self.pending = Some(ch_and_trie_val);
915                break;
916            } else if !trie_value_indicates_special_non_starter_decomposition(
917                ch_and_trie_val.trie_val,
918            ) {
919                self.buffer
920                    .push(CharacterAndClass::new_with_trie_value(ch_and_trie_val));
921            } else {
922                // The Tibetan special cases are starters that decompose into non-starters.
923                let mapped = match ch_and_trie_val.character {
924                    '\u{0340}' => {
925                        // COMBINING GRAVE TONE MARK
926                        CharacterAndClass::new('\u{0300}', CanonicalCombiningClass::Above)
927                    }
928                    '\u{0341}' => {
929                        // COMBINING ACUTE TONE MARK
930                        CharacterAndClass::new('\u{0301}', CanonicalCombiningClass::Above)
931                    }
932                    '\u{0343}' => {
933                        // COMBINING GREEK KORONIS
934                        CharacterAndClass::new('\u{0313}', CanonicalCombiningClass::Above)
935                    }
936                    '\u{0344}' => {
937                        // COMBINING GREEK DIALYTIKA TONOS
938                        self.buffer.push(CharacterAndClass::new(
939                            '\u{0308}',
940                            CanonicalCombiningClass::Above,
941                        ));
942                        CharacterAndClass::new('\u{0301}', CanonicalCombiningClass::Above)
943                    }
944                    '\u{0F73}' => {
945                        // TIBETAN VOWEL SIGN II
946                        self.buffer.push(CharacterAndClass::new(
947                            '\u{0F71}',
948                            CanonicalCombiningClass(129),
949                        ));
950                        CharacterAndClass::new('\u{0F72}', CanonicalCombiningClass(130))
951                    }
952                    '\u{0F75}' => {
953                        // TIBETAN VOWEL SIGN UU
954                        self.buffer.push(CharacterAndClass::new(
955                            '\u{0F71}',
956                            CanonicalCombiningClass(129),
957                        ));
958                        CharacterAndClass::new('\u{0F74}', CanonicalCombiningClass(132))
959                    }
960                    '\u{0F81}' => {
961                        // TIBETAN VOWEL SIGN REVERSED II
962                        self.buffer.push(CharacterAndClass::new(
963                            '\u{0F71}',
964                            CanonicalCombiningClass(129),
965                        ));
966                        CharacterAndClass::new('\u{0F80}', CanonicalCombiningClass(130))
967                    }
968                    '\u{FF9E}' => {
969                        // HALFWIDTH KATAKANA VOICED SOUND MARK
970                        CharacterAndClass::new('\u{3099}', CanonicalCombiningClass::KanaVoicing)
971                    }
972                    '\u{FF9F}' => {
973                        // HALFWIDTH KATAKANA VOICED SOUND MARK
974                        CharacterAndClass::new('\u{309A}', CanonicalCombiningClass::KanaVoicing)
975                    }
976                    _ => {
977                        // GIGO case
978                        if true {
    if !false { ::core::panicking::panic("assertion failed: false") };
};debug_assert!(false);
979                        CharacterAndClass::new_with_placeholder(char::REPLACEMENT_CHARACTER)
980                    }
981                };
982                self.buffer.push(mapped);
983            }
984        }
985        // Slicing succeeds by construction; we've always ensured that `combining_start`
986        // is in permissible range.
987        #[expect(clippy::indexing_slicing)]
988        sort_slice_by_ccc(&mut self.buffer[combining_start..], self.trie);
989    }
990}
991
992impl<I> Iterator for Decomposition<'_, I>
993where
994    I: Iterator<Item = char>,
995{
996    type Item = char;
997
998    fn next(&mut self) -> Option<char> {
999        if let Some(ret) = self.buffer.get(self.buffer_pos).map(|c| c.character()) {
1000            self.buffer_pos += 1;
1001            if self.buffer_pos == self.buffer.len() {
1002                self.buffer.clear();
1003                self.buffer_pos = 0;
1004            }
1005            return Some(ret);
1006        }
1007        if true {
    {
        match (&self.buffer_pos, &0) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val, ::core::option::Option::None);
                }
            }
        }
    };
};debug_assert_eq!(self.buffer_pos, 0);
1008        let c_and_trie_val = self.pending.take()?;
1009        Some(self.decomposing_next(c_and_trie_val))
1010    }
1011}
1012
1013/// An iterator adaptor that turns an `Iterator` over `char` into
1014/// a lazily-decomposed and then canonically composed `char` sequence.
1015#[derive(#[automatically_derived]
impl<'data, I: ::core::fmt::Debug> ::core::fmt::Debug for
    Composition<'data, I> where I: Iterator<Item = char> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field4_finish(f, "Composition",
            "decomposition", &self.decomposition, "canonical_compositions",
            &self.canonical_compositions, "unprocessed_starter",
            &self.unprocessed_starter, "composition_passthrough_bound",
            &&self.composition_passthrough_bound)
    }
}Debug)]
1016pub struct Composition<'data, I>
1017where
1018    I: Iterator<Item = char>,
1019{
1020    /// The decomposing part of the normalizer than operates before
1021    /// the canonical composition is performed on its output.
1022    decomposition: Decomposition<'data, I>,
1023    /// Non-Hangul canonical composition data.
1024    canonical_compositions: Char16Trie<'data>,
1025    /// To make `next()` yield in cases where there's a non-composing
1026    /// starter in the decomposition buffer, we put it here to let it
1027    /// wait for the next `next()` call (or a jump forward within the
1028    /// `next()` call).
1029    unprocessed_starter: Option<char>,
1030    /// The lowest character for which any one of the following does
1031    /// not hold:
1032    /// 1. Roundtrips via decomposition and recomposition.
1033    /// 2. Decomposition starts with a non-starter
1034    /// 3. Is not a backward-combining starter
1035    composition_passthrough_bound: u32,
1036}
1037
1038impl<'data, I> Composition<'data, I>
1039where
1040    I: Iterator<Item = char>,
1041{
1042    fn new(
1043        decomposition: Decomposition<'data, I>,
1044        canonical_compositions: Char16Trie<'data>,
1045        composition_passthrough_bound: u16,
1046    ) -> Self {
1047        Self {
1048            decomposition,
1049            canonical_compositions,
1050            unprocessed_starter: None,
1051            composition_passthrough_bound: u32::from(composition_passthrough_bound),
1052        }
1053    }
1054
1055    /// Performs canonical composition (including Hangul) on a pair of
1056    /// characters or returns `None` if these characters don't compose.
1057    /// Composition exclusions are taken into account.
1058    #[inline(always)]
1059    pub fn compose(&self, starter: char, second: char) -> Option<char> {
1060        compose(self.canonical_compositions.iter(), starter, second)
1061    }
1062
1063    /// Performs (non-Hangul) canonical composition on a pair of characters
1064    /// or returns `None` if these characters don't compose. Composition
1065    /// exclusions are taken into account.
1066    #[inline(always)]
1067    fn compose_non_hangul(&self, starter: char, second: char) -> Option<char> {
1068        compose_non_hangul(self.canonical_compositions.iter(), starter, second)
1069    }
1070}
1071
1072impl<I> Iterator for Composition<'_, I>
1073where
1074    I: Iterator<Item = char>,
1075{
1076    type Item = char;
1077
1078    #[inline]
1079    fn next(&mut self) -> Option<char> {
1080        let mut undecomposed_starter = CharacterAndTrieValue::new('\u{0}', 0); // The compiler can't figure out that this gets overwritten before use.
1081        if self.unprocessed_starter.is_none() {
1082            // The loop is only broken out of as goto forward
1083            #[expect(clippy::never_loop)]
1084            loop {
1085                if let Some((character, ccc)) = self
1086                    .decomposition
1087                    .buffer
1088                    .get(self.decomposition.buffer_pos)
1089                    .map(|c| c.character_and_ccc())
1090                {
1091                    self.decomposition.buffer_pos += 1;
1092                    if self.decomposition.buffer_pos == self.decomposition.buffer.len() {
1093                        self.decomposition.buffer.clear();
1094                        self.decomposition.buffer_pos = 0;
1095                    }
1096                    if ccc == CanonicalCombiningClass::NotReordered {
1097                        // Previous decomposition contains a starter. This must
1098                        // now become the `unprocessed_starter` for it to have
1099                        // a chance to compose with the upcoming characters.
1100                        //
1101                        // E.g. parenthesized Hangul in NFKC comes through here,
1102                        // but suitable composition exclusion could exercise this
1103                        // in NFC.
1104                        self.unprocessed_starter = Some(character);
1105                        break; // We already have a starter, so skip taking one from `pending`.
1106                    }
1107                    return Some(character);
1108                }
1109                if true {
    {
        match (&self.decomposition.buffer_pos, &0) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val, ::core::option::Option::None);
                }
            }
        }
    };
};debug_assert_eq!(self.decomposition.buffer_pos, 0);
1110                undecomposed_starter = self.decomposition.pending.take()?;
1111                if u32::from(undecomposed_starter.character) < self.composition_passthrough_bound
1112                    || undecomposed_starter.potential_passthrough()
1113                {
1114                    // TODO(#2385): In the NFC case (moot for NFKC and UTS46), if the upcoming
1115                    // character is not below `decomposition_passthrough_bound` but is
1116                    // below `composition_passthrough_bound`, we read from the trie
1117                    // unnecessarily.
1118                    if let Some(upcoming) = self.decomposition.delegate_next_no_pending() {
1119                        let cannot_combine_backwards = u32::from(upcoming.character)
1120                            < self.composition_passthrough_bound
1121                            || !upcoming.can_combine_backwards();
1122                        self.decomposition.pending = Some(upcoming);
1123                        if cannot_combine_backwards {
1124                            // Fast-track succeeded!
1125                            return Some(undecomposed_starter.character);
1126                        }
1127                    } else {
1128                        // End of stream
1129                        return Some(undecomposed_starter.character);
1130                    }
1131                }
1132                break; // Not actually looping
1133            }
1134        }
1135        let mut starter = '\u{0}'; // The compiler can't figure out this gets overwritten before use.
1136
1137        // The point of having this boolean is to have only one call site to
1138        // `self.decomposition.decomposing_next`, which is hopefully beneficial for
1139        // code size under inlining.
1140        let mut attempt_composition = false;
1141        loop {
1142            if let Some(unprocessed) = self.unprocessed_starter.take() {
1143                if true {
    {
        match (&undecomposed_starter, &CharacterAndTrieValue::new('\u{0}', 0))
            {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val, ::core::option::Option::None);
                }
            }
        }
    };
};debug_assert_eq!(undecomposed_starter, CharacterAndTrieValue::new('\u{0}', 0));
1144                if true {
    {
        match (&starter, &'\u{0}') {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val, ::core::option::Option::None);
                }
            }
        }
    };
};debug_assert_eq!(starter, '\u{0}');
1145                starter = unprocessed;
1146            } else {
1147                if true {
    {
        match (&self.decomposition.buffer_pos, &0) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val, ::core::option::Option::None);
                }
            }
        }
    };
};debug_assert_eq!(self.decomposition.buffer_pos, 0);
1148                let next_starter = self.decomposition.decomposing_next(undecomposed_starter);
1149                if !attempt_composition {
1150                    starter = next_starter;
1151                } else if let Some(composed) = self.compose(starter, next_starter) {
1152                    starter = composed;
1153                } else {
1154                    // This is our yield point. We'll pick this up above in the
1155                    // next call to `next()`.
1156                    self.unprocessed_starter = Some(next_starter);
1157                    return Some(starter);
1158                }
1159            }
1160            // We first loop by index to avoid moving the contents of `buffer`, but
1161            // if there's a discontiguous match, we'll start modifying `buffer` instead.
1162            loop {
1163                let (character, ccc) = if let Some((character, ccc)) = self
1164                    .decomposition
1165                    .buffer
1166                    .get(self.decomposition.buffer_pos)
1167                    .map(|c| c.character_and_ccc())
1168                {
1169                    (character, ccc)
1170                } else {
1171                    self.decomposition.buffer.clear();
1172                    self.decomposition.buffer_pos = 0;
1173                    break;
1174                };
1175                if let Some(composed) = self.compose(starter, character) {
1176                    starter = composed;
1177                    self.decomposition.buffer_pos += 1;
1178                    continue;
1179                }
1180                let mut most_recent_skipped_ccc = ccc;
1181                {
1182                    let _ = self
1183                        .decomposition
1184                        .buffer
1185                        .drain(0..self.decomposition.buffer_pos);
1186                }
1187                self.decomposition.buffer_pos = 0;
1188                if most_recent_skipped_ccc == CanonicalCombiningClass::NotReordered {
1189                    // We failed to compose a starter. Discontiguous match not allowed.
1190                    // We leave the starter in `buffer` for `next()` to find.
1191                    return Some(starter);
1192                }
1193                let mut i = 1; // We have skipped one non-starter.
1194                while let Some((character, ccc)) = self
1195                    .decomposition
1196                    .buffer
1197                    .get(i)
1198                    .map(|c| c.character_and_ccc())
1199                {
1200                    if ccc == CanonicalCombiningClass::NotReordered {
1201                        // Discontiguous match not allowed.
1202                        return Some(starter);
1203                    }
1204                    if true {
    if !(ccc >= most_recent_skipped_ccc) {
        ::core::panicking::panic("assertion failed: ccc >= most_recent_skipped_ccc")
    };
};debug_assert!(ccc >= most_recent_skipped_ccc);
1205                    if ccc != most_recent_skipped_ccc {
1206                        // Using the non-Hangul version as a micro-optimization, since
1207                        // we already rejected the case where `second` is a starter
1208                        // above, and conjoining jamo are starters.
1209                        if let Some(composed) = self.compose_non_hangul(starter, character) {
1210                            self.decomposition.buffer.remove(i);
1211                            starter = composed;
1212                            continue;
1213                        }
1214                    }
1215                    most_recent_skipped_ccc = ccc;
1216                    i += 1;
1217                }
1218                break;
1219            }
1220
1221            if true {
    {
        match (&self.decomposition.buffer_pos, &0) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val, ::core::option::Option::None);
                }
            }
        }
    };
};debug_assert_eq!(self.decomposition.buffer_pos, 0);
1222
1223            if !self.decomposition.buffer.is_empty() {
1224                return Some(starter);
1225            }
1226            // Now we need to check if composition with an upcoming starter is possible.
1227            if let Some(pending) = self.decomposition.pending.take() {
1228                // We know that `pending_starter` decomposes to start with a starter.
1229                // Otherwise, it would have been moved to `self.decomposition.buffer`
1230                // by `self.decomposing_next()`. We do this set lookup here in order
1231                // to get an opportunity to go back to the fast track.
1232                // Note that this check has to happen _after_ checking that `pending`
1233                // holds a character, because this flag isn't defined to be meaningful
1234                // when `pending` isn't holding a character.
1235                if u32::from(pending.character) < self.composition_passthrough_bound
1236                    || !pending.can_combine_backwards()
1237                {
1238                    // Won't combine backwards anyway.
1239                    self.decomposition.pending = Some(pending);
1240                    return Some(starter);
1241                }
1242                // Consume what we peeked.
1243                undecomposed_starter = pending;
1244                // The following line is OK, because we're about to loop back
1245                // to `self.decomposition.decomposing_next(c);`, which will
1246                // restore the between-`next()`-calls invariant of `pending`
1247                // before this function returns.
1248                attempt_composition = true;
1249                continue;
1250            }
1251            // End of input
1252            return Some(starter);
1253        }
1254    }
1255}
1256
1257macro_rules! composing_normalize_to {
1258    ($(#[$meta:meta])*,
1259     $normalize_to:ident,
1260     $write:path,
1261     $slice:ty,
1262     $prolog:block,
1263     $always_valid_utf:literal,
1264     $as_slice:ident,
1265     $fast:block,
1266     $text:ident,
1267     $sink:ident,
1268     $composition:ident,
1269     $composition_passthrough_bound:ident,
1270     $undecomposed_starter:ident,
1271     $pending_slice:ident,
1272     $len_utf:ident,
1273    ) => {
1274        $(#[$meta])*
1275        pub fn $normalize_to<W: $write + ?Sized>(
1276            &self,
1277            $text: $slice,
1278            $sink: &mut W,
1279        ) -> core::fmt::Result {
1280            $prolog
1281            let mut $composition = self.normalize_iter($text.chars());
1282            debug_assert_eq!($composition.decomposition.ignorable_behavior, IgnorableBehavior::Unsupported);
1283            for cc in $composition.decomposition.buffer.drain(..) {
1284                $sink.write_char(cc.character())?;
1285            }
1286
1287            // Try to get the compiler to hoist the bound to a register.
1288            let $composition_passthrough_bound = $composition.composition_passthrough_bound;
1289            'outer: loop {
1290                debug_assert_eq!($composition.decomposition.buffer_pos, 0);
1291                let mut $undecomposed_starter =
1292                    if let Some(pending) = $composition.decomposition.pending.take() {
1293                        pending
1294                    } else {
1295                        return Ok(());
1296                    };
1297                if u32::from($undecomposed_starter.character) < $composition_passthrough_bound ||
1298                    $undecomposed_starter.potential_passthrough()
1299                {
1300                    // We don't know if a `REPLACEMENT_CHARACTER` occurred in the slice or
1301                    // was returned in response to an error by the iterator. Assume the
1302                    // latter for correctness even though it pessimizes the former.
1303                    if $always_valid_utf || $undecomposed_starter.character != char::REPLACEMENT_CHARACTER {
1304                        let $pending_slice = &$text[$text.len() - $composition.decomposition.delegate.$as_slice().len() - $undecomposed_starter.character.$len_utf()..];
1305                        // The `$fast` block must either:
1306                        // 1. Return due to reaching EOF
1307                        // 2. Leave a starter with its trie value in `$undecomposed_starter`
1308                        //    and, if there is still more input, leave the next character
1309                        //    and its trie value in `$composition.decomposition.pending`.
1310                        $fast
1311                    }
1312                }
1313                // Fast track above, full algorithm below
1314                let mut starter = $composition
1315                    .decomposition
1316                    .decomposing_next($undecomposed_starter);
1317                'bufferloop: loop {
1318                    // We first loop by index to avoid moving the contents of `buffer`, but
1319                    // if there's a discontiguous match, we'll start modifying `buffer` instead.
1320                    loop {
1321                        let (character, ccc) = if let Some((character, ccc)) = $composition
1322                            .decomposition
1323                            .buffer
1324                            .get($composition.decomposition.buffer_pos)
1325                            .map(|c| c.character_and_ccc())
1326                        {
1327                            (character, ccc)
1328                        } else {
1329                            $composition.decomposition.buffer.clear();
1330                            $composition.decomposition.buffer_pos = 0;
1331                            break;
1332                        };
1333                        if let Some(composed) = $composition.compose(starter, character) {
1334                            starter = composed;
1335                            $composition.decomposition.buffer_pos += 1;
1336                            continue;
1337                        }
1338                        let mut most_recent_skipped_ccc = ccc;
1339                        if most_recent_skipped_ccc == CanonicalCombiningClass::NotReordered {
1340                            // We failed to compose a starter. Discontiguous match not allowed.
1341                            // Write the current `starter` we've been composing, make the unmatched
1342                            // starter in the buffer the new `starter` (we know it's been decomposed)
1343                            // and process the rest of the buffer with that as the starter.
1344                            $sink.write_char(starter)?;
1345                            starter = character;
1346                            $composition.decomposition.buffer_pos += 1;
1347                            continue 'bufferloop;
1348                        } else {
1349                            {
1350                                let _ = $composition
1351                                    .decomposition
1352                                    .buffer
1353                                    .drain(0..$composition.decomposition.buffer_pos);
1354                            }
1355                            $composition.decomposition.buffer_pos = 0;
1356                        }
1357                        let mut i = 1; // We have skipped one non-starter.
1358                        while let Some((character, ccc)) = $composition
1359                            .decomposition
1360                            .buffer
1361                            .get(i)
1362                            .map(|c| c.character_and_ccc())
1363                        {
1364                            if ccc == CanonicalCombiningClass::NotReordered {
1365                                // Discontiguous match not allowed.
1366                                $sink.write_char(starter)?;
1367                                for cc in $composition.decomposition.buffer.drain(..i) {
1368                                    $sink.write_char(cc.character())?;
1369                                }
1370                                starter = character;
1371                                {
1372                                    let removed = $composition.decomposition.buffer.remove(0);
1373                                    debug_assert_eq!(starter, removed.character());
1374                                }
1375                                debug_assert_eq!($composition.decomposition.buffer_pos, 0);
1376                                continue 'bufferloop;
1377                            }
1378                            debug_assert!(ccc >= most_recent_skipped_ccc);
1379                            if ccc != most_recent_skipped_ccc {
1380                                // Using the non-Hangul version as a micro-optimization, since
1381                                // we already rejected the case where `second` is a starter
1382                                // above, and conjoining jamo are starters.
1383                                if let Some(composed) =
1384                                    $composition.compose_non_hangul(starter, character)
1385                                {
1386                                    $composition.decomposition.buffer.remove(i);
1387                                    starter = composed;
1388                                    continue;
1389                                }
1390                            }
1391                            most_recent_skipped_ccc = ccc;
1392                            i += 1;
1393                        }
1394                        break;
1395                    }
1396                    debug_assert_eq!($composition.decomposition.buffer_pos, 0);
1397
1398                    if !$composition.decomposition.buffer.is_empty() {
1399                        $sink.write_char(starter)?;
1400                        for cc in $composition.decomposition.buffer.drain(..) {
1401                            $sink.write_char(cc.character())?;
1402                        }
1403                        // We had non-empty buffer, so can't compose with upcoming.
1404                        continue 'outer;
1405                    }
1406                    // Now we need to check if composition with an upcoming starter is possible.
1407                    if $composition.decomposition.pending.is_some() {
1408                        // We know that `pending_starter` decomposes to start with a starter.
1409                        // Otherwise, it would have been moved to `composition.decomposition.buffer`
1410                        // by `composition.decomposing_next()`. We do this set lookup here in order
1411                        // to get an opportunity to go back to the fast track.
1412                        // Note that this check has to happen _after_ checking that `pending`
1413                        // holds a character, because this flag isn't defined to be meaningful
1414                        // when `pending` isn't holding a character.
1415                        let pending = $composition.decomposition.pending.as_ref().unwrap();
1416                        if u32::from(pending.character) < $composition.composition_passthrough_bound
1417                            || !pending.can_combine_backwards()
1418                        {
1419                            // Won't combine backwards anyway.
1420                            $sink.write_char(starter)?;
1421                            continue 'outer;
1422                        }
1423                        let pending_starter = $composition.decomposition.pending.take().unwrap();
1424                        let decomposed = $composition.decomposition.decomposing_next(pending_starter);
1425                        if let Some(composed) = $composition.compose(starter, decomposed) {
1426                            starter = composed;
1427                        } else {
1428                            $sink.write_char(starter)?;
1429                            starter = decomposed;
1430                        }
1431                        continue 'bufferloop;
1432                    }
1433                    // End of input
1434                    $sink.write_char(starter)?;
1435                    return Ok(());
1436                } // 'bufferloop
1437            }
1438        }
1439    };
1440}
1441
1442macro_rules! decomposing_normalize_to {
1443    ($(#[$meta:meta])*,
1444     $normalize_to:ident,
1445     $write:path,
1446     $slice:ty,
1447     $prolog:block,
1448     $as_slice:ident,
1449     $fast:block,
1450     $text:ident,
1451     $sink:ident,
1452     $decomposition:ident,
1453     $decomposition_passthrough_bound:ident,
1454     $undecomposed_starter:ident,
1455     $pending_slice:ident,
1456     $outer:lifetime, // loop labels use lifetime tokens
1457    ) => {
1458        $(#[$meta])*
1459        pub fn $normalize_to<W: $write + ?Sized>(
1460            &self,
1461            $text: $slice,
1462            $sink: &mut W,
1463        ) -> core::fmt::Result {
1464            $prolog
1465
1466            let mut $decomposition = self.normalize_iter($text.chars());
1467            debug_assert_eq!($decomposition.ignorable_behavior, IgnorableBehavior::Unsupported);
1468
1469            // Try to get the compiler to hoist the bound to a register.
1470            let $decomposition_passthrough_bound = $decomposition.decomposition_passthrough_bound;
1471            $outer: loop {
1472                for cc in $decomposition.buffer.drain(..) {
1473                    $sink.write_char(cc.character())?;
1474                }
1475                debug_assert_eq!($decomposition.buffer_pos, 0);
1476                let mut $undecomposed_starter = if let Some(pending) = $decomposition.pending.take() {
1477                    pending
1478                } else {
1479                    return Ok(());
1480                };
1481                if $undecomposed_starter.starter_and_decomposes_to_self() {
1482                    // Don't bother including `undecomposed_starter` in a contiguous buffer
1483                    // write: Just write it right away:
1484                    $sink.write_char($undecomposed_starter.character)?;
1485
1486                    let $pending_slice = $decomposition.delegate.$as_slice();
1487                    $fast
1488                }
1489                let starter = $decomposition.decomposing_next($undecomposed_starter);
1490                $sink.write_char(starter)?;
1491            }
1492        }
1493    };
1494}
1495
1496macro_rules! normalizer_methods {
1497    () => {
1498        /// Normalize a string slice into a `Cow<'a, str>`.
1499        pub fn normalize<'a>(&self, text: &'a str) -> Cow<'a, str> {
1500            let (head, tail) = self.split_normalized(text);
1501            if tail.is_empty() {
1502                return Cow::Borrowed(head);
1503            }
1504            let mut ret = String::new();
1505            ret.reserve(text.len());
1506            ret.push_str(head);
1507            let _ = self.normalize_to(tail, &mut ret);
1508            Cow::Owned(ret)
1509        }
1510
1511        /// Split a string slice into maximum normalized prefix and unnormalized suffix
1512        /// such that the concatenation of the prefix and the normalization of the suffix
1513        /// is the normalization of the whole input.
1514        pub fn split_normalized<'a>(&self, text: &'a str) -> (&'a str, &'a str) {
1515            let up_to = self.is_normalized_up_to(text);
1516            text.split_at_checked(up_to).unwrap_or_else(|| {
1517                // Internal bug, not even GIGO, never supposed to happen
1518                debug_assert!(false);
1519                ("", text)
1520            })
1521        }
1522
1523        /// Return the index a string slice is normalized up to.
1524        fn is_normalized_up_to(&self, text: &str) -> usize {
1525            let mut sink = IsNormalizedSinkStr::new(text);
1526            let _ = self.normalize_to(text, &mut sink);
1527            text.len() - sink.remaining_len()
1528        }
1529
1530        /// Check whether a string slice is normalized.
1531        pub fn is_normalized(&self, text: &str) -> bool {
1532            self.is_normalized_up_to(text) == text.len()
1533        }
1534
1535        /// Normalize a slice of potentially-invalid UTF-16 into a `Cow<'a, [u16]>`.
1536        ///
1537        /// Unpaired surrogates are mapped to the REPLACEMENT CHARACTER
1538        /// before normalizing.
1539        ///
1540        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
1541        #[cfg(feature = "utf16_iter")]
1542        pub fn normalize_utf16<'a>(&self, text: &'a [u16]) -> Cow<'a, [u16]> {
1543            let (head, tail) = self.split_normalized_utf16(text);
1544            if tail.is_empty() {
1545                return Cow::Borrowed(head);
1546            }
1547            let mut ret = alloc::vec::Vec::with_capacity(text.len());
1548            ret.extend_from_slice(head);
1549            let _ = self.normalize_utf16_to(tail, &mut ret);
1550            Cow::Owned(ret)
1551        }
1552
1553        /// Split a slice of potentially-invalid UTF-16 into maximum normalized (and valid)
1554        /// prefix and unnormalized suffix such that the concatenation of the prefix and the
1555        /// normalization of the suffix is the normalization of the whole input.
1556        ///
1557        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
1558        #[cfg(feature = "utf16_iter")]
1559        pub fn split_normalized_utf16<'a>(&self, text: &'a [u16]) -> (&'a [u16], &'a [u16]) {
1560            let up_to = self.is_normalized_utf16_up_to(text);
1561            text.split_at_checked(up_to).unwrap_or_else(|| {
1562                // Internal bug, not even GIGO, never supposed to happen
1563                debug_assert!(false);
1564                (&[], text)
1565            })
1566        }
1567
1568        /// Return the index a slice of potentially-invalid UTF-16 is normalized up to.
1569        ///
1570        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
1571        #[cfg(feature = "utf16_iter")]
1572        fn is_normalized_utf16_up_to(&self, text: &[u16]) -> usize {
1573            let mut sink = IsNormalizedSinkUtf16::new(text);
1574            let _ = self.normalize_utf16_to(text, &mut sink);
1575            text.len() - sink.remaining_len()
1576        }
1577
1578        /// Checks whether a slice of potentially-invalid UTF-16 is normalized.
1579        ///
1580        /// Unpaired surrogates are treated as the REPLACEMENT CHARACTER.
1581        ///
1582        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
1583        #[cfg(feature = "utf16_iter")]
1584        pub fn is_normalized_utf16(&self, text: &[u16]) -> bool {
1585            self.is_normalized_utf16_up_to(text) == text.len()
1586        }
1587
1588        /// Normalize a slice of potentially-invalid UTF-8 into a `Cow<'a, str>`.
1589        ///
1590        /// Ill-formed byte sequences are mapped to the REPLACEMENT CHARACTER
1591        /// according to the WHATWG Encoding Standard.
1592        ///
1593        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
1594        #[cfg(feature = "utf8_iter")]
1595        pub fn normalize_utf8<'a>(&self, text: &'a [u8]) -> Cow<'a, str> {
1596            let (head, tail) = self.split_normalized_utf8(text);
1597            if tail.is_empty() {
1598                return Cow::Borrowed(head);
1599            }
1600            let mut ret = String::new();
1601            ret.reserve(text.len());
1602            ret.push_str(head);
1603            let _ = self.normalize_utf8_to(tail, &mut ret);
1604            Cow::Owned(ret)
1605        }
1606
1607        /// Split a slice of potentially-invalid UTF-8 into maximum normalized (and valid)
1608        /// prefix and unnormalized suffix such that the concatenation of the prefix and the
1609        /// normalization of the suffix is the normalization of the whole input.
1610        ///
1611        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
1612        #[cfg(feature = "utf8_iter")]
1613        pub fn split_normalized_utf8<'a>(&self, text: &'a [u8]) -> (&'a str, &'a [u8]) {
1614            let up_to = self.is_normalized_utf8_up_to(text);
1615            let (head, tail) = text.split_at_checked(up_to).unwrap_or_else(|| {
1616                // Internal bug, not even GIGO, never supposed to happen
1617                debug_assert!(false);
1618                (&[], text)
1619            });
1620            // SAFETY: The normalization check also checks for
1621            // UTF-8 well-formedness.
1622            (unsafe { str::from_utf8_unchecked(head) }, tail)
1623        }
1624
1625        /// Return the index a slice of potentially-invalid UTF-8 is normalized up to
1626        ///
1627        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
1628        #[cfg(feature = "utf8_iter")]
1629        fn is_normalized_utf8_up_to(&self, text: &[u8]) -> usize {
1630            let mut sink = IsNormalizedSinkUtf8::new(text);
1631            let _ = self.normalize_utf8_to(text, &mut sink);
1632            text.len() - sink.remaining_len()
1633        }
1634
1635        /// Check if a slice of potentially-invalid UTF-8 is normalized.
1636        ///
1637        /// Ill-formed byte sequences are mapped to the REPLACEMENT CHARACTER
1638        /// according to the WHATWG Encoding Standard before checking.
1639        ///
1640        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
1641        #[cfg(feature = "utf8_iter")]
1642        pub fn is_normalized_utf8(&self, text: &[u8]) -> bool {
1643            self.is_normalized_utf8_up_to(text) == text.len()
1644        }
1645    };
1646}
1647
1648/// Borrowed version of a normalizer for performing decomposing normalization.
1649#[derive(#[automatically_derived]
impl<'a> ::core::fmt::Debug for DecomposingNormalizerBorrowed<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field5_finish(f,
            "DecomposingNormalizerBorrowed", "decompositions",
            &self.decompositions, "tables", &self.tables,
            "supplementary_tables", &self.supplementary_tables,
            "decomposition_passthrough_bound",
            &self.decomposition_passthrough_bound,
            "composition_passthrough_bound",
            &&self.composition_passthrough_bound)
    }
}Debug)]
1650pub struct DecomposingNormalizerBorrowed<'a> {
1651    decompositions: &'a DecompositionData<'a>,
1652    tables: &'a DecompositionTables<'a>,
1653    supplementary_tables: Option<&'a DecompositionTables<'a>>,
1654    decomposition_passthrough_bound: u8, // never above 0xC0
1655    composition_passthrough_bound: u16,  // never above 0x0300
1656}
1657
1658impl DecomposingNormalizerBorrowed<'static> {
1659    /// Cheaply converts a [`DecomposingNormalizerBorrowed<'static>`] into a [`DecomposingNormalizer`].
1660    ///
1661    /// Note: Due to branching and indirection, using [`DecomposingNormalizer`] might inhibit some
1662    /// compile-time optimizations that are possible with [`DecomposingNormalizerBorrowed`].
1663    pub const fn static_to_owned(self) -> DecomposingNormalizer {
1664        DecomposingNormalizer {
1665            decompositions: DataPayload::from_static_ref(self.decompositions),
1666            tables: DataPayload::from_static_ref(self.tables),
1667            supplementary_tables: if let Some(s) = self.supplementary_tables {
1668                // `map` not available in const context
1669                Some(DataPayload::from_static_ref(s))
1670            } else {
1671                None
1672            },
1673            decomposition_passthrough_bound: self.decomposition_passthrough_bound,
1674            composition_passthrough_bound: self.composition_passthrough_bound,
1675        }
1676    }
1677
1678    /// NFD constructor using compiled data.
1679    ///
1680    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
1681    ///
1682    /// [📚 Help choosing a constructor](icu_provider::constructors)
1683    #[cfg(feature = "compiled_data")]
1684    pub const fn new_nfd() -> Self {
1685        const _: () = if !(provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1.scalars16.const_len()
                +
                provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1.scalars24.const_len()
            <= 0xFFF) {
    { ::core::panicking::panic_fmt(format_args!("future extension")); }
}assert!(
1686            provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
1687                .scalars16
1688                .const_len()
1689                + provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
1690                    .scalars24
1691                    .const_len()
1692                <= 0xFFF,
1693            "future extension"
1694        );
1695
1696        DecomposingNormalizerBorrowed {
1697            decompositions: provider::Baked::SINGLETON_NORMALIZER_NFD_DATA_V1,
1698            tables: provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1,
1699            supplementary_tables: None,
1700            decomposition_passthrough_bound: 0xC0,
1701            composition_passthrough_bound: 0x0300,
1702        }
1703    }
1704
1705    /// NFKD constructor using compiled data.
1706    ///
1707    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
1708    ///
1709    /// [📚 Help choosing a constructor](icu_provider::constructors)
1710    #[cfg(feature = "compiled_data")]
1711    pub const fn new_nfkd() -> Self {
1712        const _: () = if !(provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1.scalars16.const_len()
                        +
                        provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1.scalars24.const_len()
                    +
                    provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1.scalars16.const_len()
                +
                provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1.scalars24.const_len()
            <= 0xFFF) {
    { ::core::panicking::panic_fmt(format_args!("future extension")); }
}assert!(
1713            provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
1714                .scalars16
1715                .const_len()
1716                + provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
1717                    .scalars24
1718                    .const_len()
1719                + provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1
1720                    .scalars16
1721                    .const_len()
1722                + provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1
1723                    .scalars24
1724                    .const_len()
1725                <= 0xFFF,
1726            "future extension"
1727        );
1728
1729        const _: () = if !(provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap <=
            0x0300) {
    { ::core::panicking::panic_fmt(format_args!("invalid")); }
}assert!(
1730            provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap <= 0x0300,
1731            "invalid"
1732        );
1733
1734        let decomposition_capped =
1735            if provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap < 0xC0 {
1736                provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap
1737            } else {
1738                0xC0
1739            };
1740        let composition_capped =
1741            if provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap < 0x0300 {
1742                provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1.passthrough_cap
1743            } else {
1744                0x0300
1745            };
1746
1747        DecomposingNormalizerBorrowed {
1748            decompositions: provider::Baked::SINGLETON_NORMALIZER_NFKD_DATA_V1,
1749            tables: provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1,
1750            supplementary_tables: Some(provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1),
1751            decomposition_passthrough_bound: decomposition_capped as u8,
1752            composition_passthrough_bound: composition_capped,
1753        }
1754    }
1755
1756    #[cfg(feature = "compiled_data")]
1757    pub(crate) const fn new_uts46_decomposed() -> Self {
1758        const _: () = if !(provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1.scalars16.const_len()
                        +
                        provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1.scalars24.const_len()
                    +
                    provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1.scalars16.const_len()
                +
                provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1.scalars24.const_len()
            <= 0xFFF) {
    { ::core::panicking::panic_fmt(format_args!("future extension")); }
}assert!(
1759            provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
1760                .scalars16
1761                .const_len()
1762                + provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1
1763                    .scalars24
1764                    .const_len()
1765                + provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1
1766                    .scalars16
1767                    .const_len()
1768                + provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1
1769                    .scalars24
1770                    .const_len()
1771                <= 0xFFF,
1772            "future extension"
1773        );
1774
1775        const _: () = if !(provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1.passthrough_cap <=
            0x0300) {
    { ::core::panicking::panic_fmt(format_args!("invalid")); }
}assert!(
1776            provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1.passthrough_cap <= 0x0300,
1777            "invalid"
1778        );
1779
1780        let decomposition_capped =
1781            if provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1.passthrough_cap < 0xC0 {
1782                provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1.passthrough_cap
1783            } else {
1784                0xC0
1785            };
1786        let composition_capped =
1787            if provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1.passthrough_cap < 0x0300 {
1788                provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1.passthrough_cap
1789            } else {
1790                0x0300
1791            };
1792
1793        DecomposingNormalizerBorrowed {
1794            decompositions: provider::Baked::SINGLETON_NORMALIZER_UTS46_DATA_V1,
1795            tables: provider::Baked::SINGLETON_NORMALIZER_NFD_TABLES_V1,
1796            supplementary_tables: Some(provider::Baked::SINGLETON_NORMALIZER_NFKD_TABLES_V1),
1797            decomposition_passthrough_bound: decomposition_capped as u8,
1798            composition_passthrough_bound: composition_capped,
1799        }
1800    }
1801}
1802
1803impl<'data> DecomposingNormalizerBorrowed<'data> {
1804    /// NFD constructor using already-loaded data.
1805    ///
1806    /// This constructor is intended for use by collations.
1807    ///
1808    /// [📚 Help choosing a constructor](icu_provider::constructors)
1809    #[doc(hidden)]
1810    pub fn new_with_data(
1811        decompositions: &'data DecompositionData<'data>,
1812        tables: &'data DecompositionTables<'data>,
1813    ) -> Self {
1814        Self {
1815            decompositions,
1816            tables,
1817            supplementary_tables: None,
1818            decomposition_passthrough_bound: 0xC0,
1819            composition_passthrough_bound: 0x0300,
1820        }
1821    }
1822
1823    /// Wraps a delegate iterator into a decomposing iterator
1824    /// adapter by using the data already held by this normalizer.
1825    pub fn normalize_iter<I: Iterator<Item = char>>(&self, iter: I) -> Decomposition<'data, I> {
1826        Decomposition::new_with_supplements(
1827            iter,
1828            self.decompositions,
1829            self.tables,
1830            self.supplementary_tables,
1831            self.decomposition_passthrough_bound,
1832            IgnorableBehavior::Unsupported,
1833        )
1834    }
1835
1836    /// Normalize a string slice into a `Cow<'a, str>`.
pub fn normalize<'a>(&self, text: &'a str) -> Cow<'a, str> {
    let (head, tail) = self.split_normalized(text);
    if tail.is_empty() { return Cow::Borrowed(head); }
    let mut ret = String::new();
    ret.reserve(text.len());
    ret.push_str(head);
    let _ = self.normalize_to(tail, &mut ret);
    Cow::Owned(ret)
}
/// Split a string slice into maximum normalized prefix and unnormalized suffix
/// such that the concatenation of the prefix and the normalization of the suffix
/// is the normalization of the whole input.
pub fn split_normalized<'a>(&self, text: &'a str) -> (&'a str, &'a str) {
    let up_to = self.is_normalized_up_to(text);
    text.split_at_checked(up_to).unwrap_or_else(||
            {
                if true {
                    if !false {
                        ::core::panicking::panic("assertion failed: false")
                    };
                };
                ("", text)
            })
}
/// Return the index a string slice is normalized up to.
fn is_normalized_up_to(&self, text: &str) -> usize {
    let mut sink = IsNormalizedSinkStr::new(text);
    let _ = self.normalize_to(text, &mut sink);
    text.len() - sink.remaining_len()
}
/// Check whether a string slice is normalized.
pub fn is_normalized(&self, text: &str) -> bool {
    self.is_normalized_up_to(text) == text.len()
}normalizer_methods!();
1837
1838    #[doc = r" Normalize a string slice into a `Write` sink."]
pub fn normalize_to<W: core::fmt::Write +
    ?Sized>(&self, text: &str, sink: &mut W) -> core::fmt::Result {
    {}
    let mut decomposition = self.normalize_iter(text.chars());
    if true {
        {
            match (&decomposition.ignorable_behavior,
                    &IgnorableBehavior::Unsupported) {
                (left_val, right_val) => {
                    if !(*left_val == *right_val) {
                        let kind = ::core::panicking::AssertKind::Eq;
                        ::core::panicking::assert_failed(kind, &*left_val,
                            &*right_val, ::core::option::Option::None);
                    }
                }
            }
        };
    };
    let decomposition_passthrough_bound =
        decomposition.decomposition_passthrough_bound;
    'outer: loop {
        for cc in decomposition.buffer.drain(..) {
            sink.write_char(cc.character())?;
        }
        if true {
            {
                match (&decomposition.buffer_pos, &0) {
                    (left_val, right_val) => {
                        if !(*left_val == *right_val) {
                            let kind = ::core::panicking::AssertKind::Eq;
                            ::core::panicking::assert_failed(kind, &*left_val,
                                &*right_val, ::core::option::Option::None);
                        }
                    }
                }
            };
        };
        let mut undecomposed_starter =
            if let Some(pending) = decomposition.pending.take() {
                pending
            } else { return Ok(()); };
        if undecomposed_starter.starter_and_decomposes_to_self() {
            sink.write_char(undecomposed_starter.character)?;
            let pending_slice = decomposition.delegate.as_str();
            {
                let decomposition_passthrough_byte_bound =
                    if decomposition_passthrough_bound == 0xC0 {
                        0xC3u8
                    } else { decomposition_passthrough_bound.min(0x80) as u8 };

                #[expect(clippy::unwrap_used)]
                'fast: loop {
                    let mut code_unit_iter =
                        decomposition.delegate.as_str().as_bytes().iter();
                    'fastest: loop {
                        if let Some(&upcoming_byte) = code_unit_iter.next() {
                            if upcoming_byte < decomposition_passthrough_byte_bound {
                                continue 'fastest;
                            }
                            decomposition.delegate =
                                pending_slice[pending_slice.len() -
                                                    code_unit_iter.as_slice().len() - 1..].chars();
                            break 'fastest;
                        }
                        sink.write_str(pending_slice)?;
                        return Ok(());
                    }
                    let upcoming = decomposition.delegate.next().unwrap();
                    let upcoming_with_trie_value =
                        decomposition.attach_trie_value(upcoming);
                    if upcoming_with_trie_value.starter_and_decomposes_to_self()
                        {
                        continue 'fast;
                    }
                    let consumed_so_far_slice =
                        &pending_slice[..pending_slice.len() -
                                            decomposition.delegate.as_str().len() -
                                        upcoming.len_utf8()];
                    sink.write_str(consumed_so_far_slice)?;
                    if decomposition_starts_with_non_starter(upcoming_with_trie_value.trie_val)
                        {
                        decomposition.pending = Some(upcoming_with_trie_value);
                        decomposition.gather_and_sort_combining(0);
                        continue 'outer;
                    }
                    undecomposed_starter = upcoming_with_trie_value;
                    if true {
                        if !decomposition.pending.is_none() {
                            ::core::panicking::panic("assertion failed: decomposition.pending.is_none()")
                        };
                    };
                    break 'fast;
                }
            }
        }
        let starter = decomposition.decomposing_next(undecomposed_starter);
        sink.write_char(starter)?;
    }
}decomposing_normalize_to!(
1839        /// Normalize a string slice into a `Write` sink.
1840        ,
1841        normalize_to,
1842        core::fmt::Write,
1843        &str,
1844        {
1845        },
1846        as_str,
1847        {
1848            let decomposition_passthrough_byte_bound = if decomposition_passthrough_bound == 0xC0 {
1849                0xC3u8
1850            } else {
1851                decomposition_passthrough_bound.min(0x80) as u8
1852            };
1853            // The attribute belongs on an inner statement, but Rust doesn't allow it there.
1854            #[expect(clippy::unwrap_used)]
1855            'fast: loop {
1856                let mut code_unit_iter = decomposition.delegate.as_str().as_bytes().iter();
1857                'fastest: loop {
1858                    if let Some(&upcoming_byte) = code_unit_iter.next() {
1859                        if upcoming_byte < decomposition_passthrough_byte_bound {
1860                            // Fast-track succeeded!
1861                            continue 'fastest;
1862                        }
1863                        // This deliberately isn't panic-free, since the code pattern
1864                        // that was OK for the composing counterpart regressed
1865                        // English and French performance if done here, too.
1866                        decomposition.delegate = pending_slice[pending_slice.len() - code_unit_iter.as_slice().len() - 1..].chars();
1867                        break 'fastest;
1868                    }
1869                    // End of stream
1870                    sink.write_str(pending_slice)?;
1871                    return Ok(());
1872                }
1873
1874                // `unwrap()` OK, because the slice is valid UTF-8 and we know there
1875                // is an upcoming byte.
1876                let upcoming = decomposition.delegate.next().unwrap();
1877                let upcoming_with_trie_value = decomposition.attach_trie_value(upcoming);
1878                if upcoming_with_trie_value.starter_and_decomposes_to_self() {
1879                    continue 'fast;
1880                }
1881                let consumed_so_far_slice = &pending_slice[..pending_slice.len()
1882                    - decomposition.delegate.as_str().len()
1883                    - upcoming.len_utf8()];
1884                sink.write_str(consumed_so_far_slice)?;
1885
1886                // Now let's figure out if we got a starter or a non-starter.
1887                if decomposition_starts_with_non_starter(
1888                    upcoming_with_trie_value.trie_val,
1889                ) {
1890                    // Let this trie value to be reprocessed in case it is
1891                    // one of the rare decomposing ones.
1892                    decomposition.pending = Some(upcoming_with_trie_value);
1893                    decomposition.gather_and_sort_combining(0);
1894                    continue 'outer;
1895                }
1896                undecomposed_starter = upcoming_with_trie_value;
1897                debug_assert!(decomposition.pending.is_none());
1898                break 'fast;
1899            }
1900        },
1901        text,
1902        sink,
1903        decomposition,
1904        decomposition_passthrough_bound,
1905        undecomposed_starter,
1906        pending_slice,
1907        'outer,
1908    );
1909
1910    decomposing_normalize_to!(
1911        /// Normalize a slice of potentially-invalid UTF-8 into a `Write` sink.
1912        ///
1913        /// Ill-formed byte sequences are mapped to the REPLACEMENT CHARACTER
1914        /// according to the WHATWG Encoding Standard.
1915        ///
1916        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
1917        #[cfg(feature = "utf8_iter")]
1918        ,
1919        normalize_utf8_to,
1920        core::fmt::Write,
1921        &[u8],
1922        {
1923        },
1924        as_slice,
1925        {
1926            let decomposition_passthrough_byte_bound = decomposition_passthrough_bound.min(0x80) as u8;
1927            'fast: loop {
1928                let mut code_unit_iter = decomposition.delegate.as_slice().iter();
1929                'fastest: loop {
1930                    if let Some(&upcoming_byte) = code_unit_iter.next() {
1931                        if upcoming_byte < decomposition_passthrough_byte_bound {
1932                            // Fast-track succeeded!
1933                            continue 'fastest;
1934                        }
1935                        break 'fastest;
1936                    }
1937                    // End of stream
1938                    sink.write_str(unsafe { str::from_utf8_unchecked(pending_slice) })?;
1939                    return Ok(());
1940                }
1941                #[expect(clippy::indexing_slicing)]
1942                {decomposition.delegate = pending_slice[pending_slice.len() - code_unit_iter.as_slice().len() - 1..].chars();}
1943
1944                // `unwrap()` OK, because the slice is valid UTF-8 and we know there
1945                // is an upcoming byte.
1946                #[expect(clippy::unwrap_used)]
1947                let upcoming = decomposition.delegate.next().unwrap();
1948                let upcoming_with_trie_value = decomposition.attach_trie_value(upcoming);
1949                if upcoming_with_trie_value.starter_and_decomposes_to_self_except_replacement() {
1950                    // Note: The trie value of the REPLACEMENT CHARACTER is
1951                    // intentionally formatted to fail the
1952                    // `starter_and_decomposes_to_self` test even though it
1953                    // really is a starter that decomposes to self. This
1954                    // Allows moving the branch on REPLACEMENT CHARACTER
1955                    // below this `continue`.
1956                    continue 'fast;
1957                }
1958
1959                // TODO: Annotate as unlikely.
1960                if upcoming == char::REPLACEMENT_CHARACTER {
1961                    // We might have an error, so fall out of the fast path.
1962
1963                    // Since the U+FFFD might signify an error, we can't
1964                    // assume `upcoming.len_utf8()` for the backoff length.
1965                    #[expect(clippy::indexing_slicing)]
1966                    let mut consumed_so_far = pending_slice[..pending_slice.len() - decomposition.delegate.as_slice().len()].chars();
1967                    let back = consumed_so_far.next_back();
1968                    debug_assert_eq!(back, Some(char::REPLACEMENT_CHARACTER));
1969                    let consumed_so_far_slice = consumed_so_far.as_slice();
1970                    sink.write_str(unsafe { str::from_utf8_unchecked(consumed_so_far_slice) } )?;
1971
1972                    // We could call `gather_and_sort_combining` here and
1973                    // `continue 'outer`, but this should be better for code
1974                    // size.
1975                    undecomposed_starter = upcoming_with_trie_value;
1976                    debug_assert!(decomposition.pending.is_none());
1977                    break 'fast;
1978                }
1979
1980                #[expect(clippy::indexing_slicing)]
1981                let consumed_so_far_slice = &pending_slice[..pending_slice.len()
1982                    - decomposition.delegate.as_slice().len()
1983                    - upcoming.len_utf8()];
1984                sink.write_str(unsafe { str::from_utf8_unchecked(consumed_so_far_slice) } )?;
1985
1986                // Now let's figure out if we got a starter or a non-starter.
1987                if decomposition_starts_with_non_starter(
1988                    upcoming_with_trie_value.trie_val,
1989                ) {
1990                    // Let this trie value to be reprocessed in case it is
1991                    // one of the rare decomposing ones.
1992                    decomposition.pending = Some(upcoming_with_trie_value);
1993                    decomposition.gather_and_sort_combining(0);
1994                    continue 'outer;
1995                }
1996                undecomposed_starter = upcoming_with_trie_value;
1997                debug_assert!(decomposition.pending.is_none());
1998                break 'fast;
1999            }
2000        },
2001        text,
2002        sink,
2003        decomposition,
2004        decomposition_passthrough_bound,
2005        undecomposed_starter,
2006        pending_slice,
2007        'outer,
2008    );
2009
2010    decomposing_normalize_to!(
2011        /// Normalize a slice of potentially-invalid UTF-16 into a `Write16` sink.
2012        ///
2013        /// Unpaired surrogates are mapped to the REPLACEMENT CHARACTER
2014        /// before normalizing.
2015        ///
2016        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
2017        #[cfg(feature = "utf16_iter")]
2018        ,
2019        normalize_utf16_to,
2020        write16::Write16,
2021        &[u16],
2022        {
2023            sink.size_hint(text.len())?;
2024        },
2025        as_slice,
2026        {
2027            // This loop is only broken out of as goto forward and only as release-build recovery from
2028            // detecting an internal bug without panic. (In debug builds, internal bugs panic instead.)
2029            #[expect(clippy::never_loop)]
2030            'fastwrap: loop {
2031                // Commented out `code_unit_iter` and used `ptr` and `end` to
2032                // work around https://github.com/rust-lang/rust/issues/144684 .
2033                //
2034                // let mut code_unit_iter = decomposition.delegate.as_slice().iter();
2035                let delegate_as_slice = decomposition.delegate.as_slice();
2036                let mut ptr: *const u16 = delegate_as_slice.as_ptr();
2037                // SAFETY: materializing a pointer immediately past the end of an
2038                // allocation is OK.
2039                let end: *const u16 = unsafe { ptr.add(delegate_as_slice.len()) };
2040                'fast: loop {
2041                    // if let Some(&upcoming_code_unit) = code_unit_iter.next() {
2042                    if ptr != end {
2043                        // SAFETY: We just checked that `ptr` has not reached `end`.
2044                        // `ptr` always advances by one, and we always have a check
2045                        // per advancement.
2046                        let upcoming_code_unit = unsafe { *ptr };
2047                        // SAFETY: Since `ptr` hadn't reached `end`, yet, advancing
2048                        // by one points to the same allocation or to immediately
2049                        // after, which is OK.
2050                        ptr = unsafe { ptr.add(1) };
2051
2052                        let mut upcoming32 = u32::from(upcoming_code_unit);
2053                        // The performance of what logically is supposed to be this
2054                        // branch is _incredibly_ brittle and what LLVM ends up doing
2055                        // that affects the performance of what's logically about this
2056                        // decision can swing to double/halve the throughput for Basic
2057                        // Latin in ways that are completely unintuitive. Basically _any_
2058                        // change to _any_ code that participates in how LLVM sees the
2059                        // code around here can make the perf fall over. In seems that
2060                        // manually annotating this branch as likely has worse effects
2061                        // on non-Basic-Latin input that the case where LLVM just happens to
2062                        // do the right thing.
2063                        //
2064                        // What happens with this branch may depend on what sink type
2065                        // this code is monomorphized over.
2066                        //
2067                        // What a terrible sink of developer time!
2068                        if upcoming32 < decomposition_passthrough_bound {
2069                            continue 'fast;
2070                        }
2071                        // We might be doing a trie lookup by surrogate. Surrogates get
2072                        // a decomposition to U+FFFD.
2073                        let mut trie_value = decomposition.trie.get16(upcoming_code_unit);
2074                        if starter_and_decomposes_to_self_impl(trie_value) {
2075                            continue 'fast;
2076                        }
2077                        // We might now be looking at a surrogate.
2078                        // The loop is only broken out of as goto forward
2079                        #[expect(clippy::never_loop)]
2080                        'surrogateloop: loop {
2081                            // LLVM's optimizations are incredibly brittle for the code _above_,
2082                            // and using `likely` _below_ without using it _above_ helps!
2083                            // What a massive sink of developer time!
2084                            // Seriously, the effect of these annotations is massively
2085                            // unintuitive. Measure everything!
2086                            // Notably, the `if likely(...)` formulation optimizes differently
2087                            // than just putting `cold_path()` on the `else` path!
2088                            let surrogate_base = upcoming32.wrapping_sub(0xD800);
2089                            if likely(surrogate_base > (0xDFFF - 0xD800)) {
2090                                // Not surrogate
2091                                break 'surrogateloop;
2092                            }
2093                            if likely(surrogate_base <= (0xDBFF - 0xD800)) {
2094                                // let iter_backup = code_unit_iter.clone();
2095                                // if let Some(&low) = code_unit_iter.next() {
2096                                if ptr != end {
2097                                    // SAFETY: We just checked that `ptr` has not reached `end`.
2098                                    // `ptr` always advances by one, and we always have a check
2099                                    // per advancement.
2100                                    let low = unsafe { *ptr };
2101                                    if likely(in_inclusive_range16(low, 0xDC00, 0xDFFF)) {
2102                                        // SAFETY: Since `ptr` hadn't reached `end`, yet, advancing
2103                                        // by one points to the same allocation or to immediately
2104                                        // after, which is OK.
2105                                        ptr = unsafe { ptr.add(1) };
2106
2107                                        upcoming32 = (upcoming32 << 10) + u32::from(low)
2108                                            - (((0xD800u32 << 10) - 0x10000u32) + 0xDC00u32);
2109                                        // Successfully-paired surrogate. Read from the trie again.
2110                                        trie_value = {
2111                                            // Semantically, this bit of conditional compilation makes no sense.
2112                                            // The purpose is to keep LLVM seeing the untyped trie case the way
2113                                            // it did before so as not to regress the performance of the untyped
2114                                            // case due to unintuitive optimizer effects. If you care about the
2115                                            // perf of the untyped trie case and have better ideas, please try
2116                                            // something better.
2117                                            #[cfg(not(icu4x_unstable_fast_trie_only))]
2118                                            {decomposition.trie.get32(upcoming32)}
2119                                            #[cfg(icu4x_unstable_fast_trie_only)]
2120                                            {decomposition.trie.get32_supplementary(upcoming32)}
2121                                        };
2122                                        if likely(starter_and_decomposes_to_self_impl(trie_value)) {
2123                                            continue 'fast;
2124                                        }
2125                                        break 'surrogateloop;
2126                                    // } else {
2127                                    //     code_unit_iter = iter_backup;
2128                                    }
2129                                }
2130                            }
2131                            // unpaired surrogate
2132                            upcoming32 = 0xFFFD; // Safe value for `char::from_u32_unchecked` and matches later potential error check.
2133                            // trie_value already holds a decomposition to U+FFFD.
2134                            break 'surrogateloop;
2135                        }
2136
2137                        let upcoming = unsafe { char::from_u32_unchecked(upcoming32) };
2138                        let upcoming_with_trie_value = CharacterAndTrieValue::new(upcoming, trie_value);
2139
2140
2141                        let Some(consumed_so_far_slice) = pending_slice.get(..pending_slice.len() -
2142                            // code_unit_iter.as_slice().len()
2143                            // SAFETY: `ptr` and `end` have been derived from the same allocation
2144                            // and `ptr` is never greater than `end`.
2145                            unsafe { end.offset_from(ptr) as usize }
2146                            - upcoming.len_utf16()) else {
2147                            // If we ever come here, it's a bug, but let's avoid panic code paths in release builds.
2148                            debug_assert!(false);
2149                            // Throw away the results of the fast path.
2150                            break 'fastwrap;
2151                        };
2152                        sink.write_slice(consumed_so_far_slice)?;
2153
2154                        if decomposition_starts_with_non_starter(
2155                            upcoming_with_trie_value.trie_val,
2156                        ) {
2157                            // Sync with main iterator
2158                            // decomposition.delegate = code_unit_iter.as_slice().chars();
2159                            // SAFETY: `ptr` and `end` have been derived from the same allocation
2160                            // and `ptr` is never greater than `end`.
2161                            decomposition.delegate = unsafe { core::slice::from_raw_parts(ptr, end.offset_from(ptr) as usize) }.chars();
2162                            // Let this trie value to be reprocessed in case it is
2163                            // one of the rare decomposing ones.
2164                            decomposition.pending = Some(upcoming_with_trie_value);
2165                            decomposition.gather_and_sort_combining(0);
2166                            continue 'outer;
2167                        }
2168                        undecomposed_starter = upcoming_with_trie_value;
2169                        debug_assert!(decomposition.pending.is_none());
2170                        break 'fast;
2171                    }
2172                    // End of stream
2173                    sink.write_slice(pending_slice)?;
2174                    return Ok(());
2175                }
2176                // Sync the main iterator
2177                // decomposition.delegate = code_unit_iter.as_slice().chars();
2178                // SAFETY: `ptr` and `end` have been derived from the same allocation
2179                // and `ptr` is never greater than `end`.
2180                decomposition.delegate = unsafe { core::slice::from_raw_parts(ptr, end.offset_from(ptr) as usize) }.chars();
2181                break 'fastwrap;
2182            }
2183        },
2184        text,
2185        sink,
2186        decomposition,
2187        decomposition_passthrough_bound,
2188        undecomposed_starter,
2189        pending_slice,
2190        'outer,
2191    );
2192}
2193
2194/// A normalizer for performing decomposing normalization.
2195#[derive(#[automatically_derived]
impl ::core::fmt::Debug for DecomposingNormalizer {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field5_finish(f,
            "DecomposingNormalizer", "decompositions", &self.decompositions,
            "tables", &self.tables, "supplementary_tables",
            &self.supplementary_tables, "decomposition_passthrough_bound",
            &self.decomposition_passthrough_bound,
            "composition_passthrough_bound",
            &&self.composition_passthrough_bound)
    }
}Debug)]
2196pub struct DecomposingNormalizer {
2197    decompositions: DataPayload<NormalizerNfdDataV1>,
2198    tables: DataPayload<NormalizerNfdTablesV1>,
2199    supplementary_tables: Option<DataPayload<NormalizerNfkdTablesV1>>,
2200    decomposition_passthrough_bound: u8, // never above 0xC0
2201    composition_passthrough_bound: u16,  // never above 0x0300
2202}
2203
2204impl DecomposingNormalizer {
2205    /// Constructs a borrowed version of this type for more efficient querying.
2206    pub fn as_borrowed(&self) -> DecomposingNormalizerBorrowed<'_> {
2207        DecomposingNormalizerBorrowed {
2208            decompositions: self.decompositions.get(),
2209            tables: self.tables.get(),
2210            supplementary_tables: self.supplementary_tables.as_ref().map(|s| s.get()),
2211            decomposition_passthrough_bound: self.decomposition_passthrough_bound,
2212            composition_passthrough_bound: self.composition_passthrough_bound,
2213        }
2214    }
2215
2216    /// NFD constructor using compiled data.
2217    ///
2218    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
2219    ///
2220    /// [📚 Help choosing a constructor](icu_provider::constructors)
2221    #[cfg(feature = "compiled_data")]
2222    pub const fn new_nfd() -> DecomposingNormalizerBorrowed<'static> {
2223        DecomposingNormalizerBorrowed::new_nfd()
2224    }
2225
2226    icu_provider::gen_buffer_data_constructors!(
2227        () -> error: DataError,
2228        functions: [
2229            new_nfd: skip,
2230            try_new_nfd_with_buffer_provider,
2231            try_new_nfd_unstable,
2232            Self,
2233        ]
2234    );
2235
2236    #[doc = "A version of [`Self::new_nfd`] that uses custom data provided by a [`DataProvider`].\n\n[\u{1f4da} Help choosing a constructor](icu_provider::constructors)\n\n<div class=\"stab unstable\">\u{26a0}\u{fe0f} The bounds on <tt>provider</tt> may change over time, including in SemVer minor releases.</div>"icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_nfd)]
2237    pub fn try_new_nfd_unstable<D>(provider: &D) -> Result<Self, DataError>
2238    where
2239        D: DataProvider<NormalizerNfdDataV1> + DataProvider<NormalizerNfdTablesV1> + ?Sized,
2240    {
2241        let decompositions: DataPayload<NormalizerNfdDataV1> =
2242            provider.load(Default::default())?.payload;
2243        let tables: DataPayload<NormalizerNfdTablesV1> = provider.load(Default::default())?.payload;
2244
2245        if tables.get().scalars16.len() + tables.get().scalars24.len() > 0xFFF {
2246            // The data is from a future where there exists a normalization flavor whose
2247            // complex decompositions take more than 0xFFF but fewer than 0x1FFF code points
2248            // of space. If a good use case from such a decomposition flavor arises, we can
2249            // dynamically change the bit masks so that the length mask becomes 0x1FFF instead
2250            // of 0xFFF and the all-non-starters mask becomes 0 instead of 0x1000. However,
2251            // since for now the masks are hard-coded, error out.
2252            return Err(
2253                DataError::custom("future extension").with_marker(NormalizerNfdTablesV1::INFO)
2254            );
2255        }
2256
2257        let cap = decompositions.get().passthrough_cap;
2258        if cap > 0x0300 {
2259            return Err(DataError::custom("invalid").with_marker(NormalizerNfdDataV1::INFO));
2260        }
2261        let decomposition_capped = cap.min(0xC0);
2262        let composition_capped = cap.min(0x0300);
2263
2264        Ok(DecomposingNormalizer {
2265            decompositions,
2266            tables,
2267            supplementary_tables: None,
2268            decomposition_passthrough_bound: decomposition_capped as u8,
2269            composition_passthrough_bound: composition_capped,
2270        })
2271    }
2272
2273    icu_provider::gen_buffer_data_constructors!(
2274        () -> error: DataError,
2275        functions: [
2276            new_nfkd: skip,
2277            try_new_nfkd_with_buffer_provider,
2278            try_new_nfkd_unstable,
2279            Self,
2280        ]
2281    );
2282
2283    /// NFKD constructor using compiled data.
2284    ///
2285    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
2286    ///
2287    /// [📚 Help choosing a constructor](icu_provider::constructors)
2288    #[cfg(feature = "compiled_data")]
2289    pub const fn new_nfkd() -> DecomposingNormalizerBorrowed<'static> {
2290        DecomposingNormalizerBorrowed::new_nfkd()
2291    }
2292
2293    #[doc = "A version of [`Self::new_nfkd`] that uses custom data provided by a [`DataProvider`].\n\n[\u{1f4da} Help choosing a constructor](icu_provider::constructors)\n\n<div class=\"stab unstable\">\u{26a0}\u{fe0f} The bounds on <tt>provider</tt> may change over time, including in SemVer minor releases.</div>"icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_nfkd)]
2294    pub fn try_new_nfkd_unstable<D>(provider: &D) -> Result<Self, DataError>
2295    where
2296        D: DataProvider<NormalizerNfkdDataV1>
2297            + DataProvider<NormalizerNfdTablesV1>
2298            + DataProvider<NormalizerNfkdTablesV1>
2299            + ?Sized,
2300    {
2301        let decompositions: DataPayload<NormalizerNfkdDataV1> =
2302            provider.load(Default::default())?.payload;
2303        let tables: DataPayload<NormalizerNfdTablesV1> = provider.load(Default::default())?.payload;
2304        let supplementary_tables: DataPayload<NormalizerNfkdTablesV1> =
2305            provider.load(Default::default())?.payload;
2306
2307        if tables.get().scalars16.len()
2308            + tables.get().scalars24.len()
2309            + supplementary_tables.get().scalars16.len()
2310            + supplementary_tables.get().scalars24.len()
2311            > 0xFFF
2312        {
2313            // The data is from a future where there exists a normalization flavor whose
2314            // complex decompositions take more than 0xFFF but fewer than 0x1FFF code points
2315            // of space. If a good use case from such a decomposition flavor arises, we can
2316            // dynamically change the bit masks so that the length mask becomes 0x1FFF instead
2317            // of 0xFFF and the all-non-starters mask becomes 0 instead of 0x1000. However,
2318            // since for now the masks are hard-coded, error out.
2319            return Err(
2320                DataError::custom("future extension").with_marker(NormalizerNfdTablesV1::INFO)
2321            );
2322        }
2323
2324        let cap = decompositions.get().passthrough_cap;
2325        if cap > 0x0300 {
2326            return Err(DataError::custom("invalid").with_marker(NormalizerNfkdDataV1::INFO));
2327        }
2328        let decomposition_capped = cap.min(0xC0);
2329        let composition_capped = cap.min(0x0300);
2330
2331        Ok(DecomposingNormalizer {
2332            decompositions: decompositions.cast(),
2333            tables,
2334            supplementary_tables: Some(supplementary_tables),
2335            decomposition_passthrough_bound: decomposition_capped as u8,
2336            composition_passthrough_bound: composition_capped,
2337        })
2338    }
2339
2340    /// UTS 46 decomposed constructor (testing only)
2341    ///
2342    /// This is a special building block normalization for IDNA. It is the decomposed counterpart of
2343    /// ICU4C's UTS 46 normalization with two exceptions: characters that UTS 46 disallows and
2344    /// ICU4C maps to U+FFFD and characters that UTS 46 maps to the empty string normalize as in
2345    /// NFD in this normalization. In both cases, the previous UTS 46 processing before using
2346    /// normalization is expected to deal with these characters. Making the disallowed characters
2347    /// behave like this is beneficial to data size, and this normalizer implementation cannot
2348    /// deal with a character normalizing to the empty string, which doesn't happen in NFD or
2349    /// NFKD as of Unicode 14.
2350    ///
2351    /// Warning: In this normalization, U+0345 COMBINING GREEK YPOGEGRAMMENI exhibits a behavior
2352    /// that no character in Unicode exhibits in NFD, NFKD, NFC, or NFKC: Case folding turns
2353    /// U+0345 from a reordered character into a non-reordered character before reordering happens.
2354    /// Therefore, the output of this normalization may differ for different inputs that are
2355    /// canonically equivalent with each other if they differ by how U+0345 is ordered relative
2356    /// to other reorderable characters.
2357    pub(crate) fn try_new_uts46_decomposed_unstable<D>(provider: &D) -> Result<Self, DataError>
2358    where
2359        D: DataProvider<NormalizerUts46DataV1>
2360            + DataProvider<NormalizerNfdTablesV1>
2361            + DataProvider<NormalizerNfkdTablesV1>
2362            // UTS 46 tables merged into CompatibilityDecompositionTablesV1
2363            + ?Sized,
2364    {
2365        let decompositions: DataPayload<NormalizerUts46DataV1> =
2366            provider.load(Default::default())?.payload;
2367        let tables: DataPayload<NormalizerNfdTablesV1> = provider.load(Default::default())?.payload;
2368        let supplementary_tables: DataPayload<NormalizerNfkdTablesV1> =
2369            provider.load(Default::default())?.payload;
2370
2371        if tables.get().scalars16.len()
2372            + tables.get().scalars24.len()
2373            + supplementary_tables.get().scalars16.len()
2374            + supplementary_tables.get().scalars24.len()
2375            > 0xFFF
2376        {
2377            // The data is from a future where there exists a normalization flavor whose
2378            // complex decompositions take more than 0xFFF but fewer than 0x1FFF code points
2379            // of space. If a good use case from such a decomposition flavor arises, we can
2380            // dynamically change the bit masks so that the length mask becomes 0x1FFF instead
2381            // of 0xFFF and the all-non-starters mask becomes 0 instead of 0x1000. However,
2382            // since for now the masks are hard-coded, error out.
2383            return Err(
2384                DataError::custom("future extension").with_marker(NormalizerNfdTablesV1::INFO)
2385            );
2386        }
2387
2388        let cap = decompositions.get().passthrough_cap;
2389        if cap > 0x0300 {
2390            return Err(DataError::custom("invalid").with_marker(NormalizerUts46DataV1::INFO));
2391        }
2392        let decomposition_capped = cap.min(0xC0);
2393        let composition_capped = cap.min(0x0300);
2394
2395        Ok(DecomposingNormalizer {
2396            decompositions: decompositions.cast(),
2397            tables,
2398            supplementary_tables: Some(supplementary_tables),
2399            decomposition_passthrough_bound: decomposition_capped as u8,
2400            composition_passthrough_bound: composition_capped,
2401        })
2402    }
2403}
2404
2405/// Borrowed version of a normalizer for performing composing normalization.
2406#[derive(#[automatically_derived]
impl<'a> ::core::fmt::Debug for ComposingNormalizerBorrowed<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "ComposingNormalizerBorrowed", "decomposing_normalizer",
            &self.decomposing_normalizer, "canonical_compositions",
            &&self.canonical_compositions)
    }
}Debug)]
2407pub struct ComposingNormalizerBorrowed<'a> {
2408    decomposing_normalizer: DecomposingNormalizerBorrowed<'a>,
2409    canonical_compositions: &'a CanonicalCompositions<'a>,
2410}
2411
2412impl ComposingNormalizerBorrowed<'static> {
2413    /// Cheaply converts a [`ComposingNormalizerBorrowed<'static>`] into a [`ComposingNormalizer`].
2414    ///
2415    /// Note: Due to branching and indirection, using [`ComposingNormalizer`] might inhibit some
2416    /// compile-time optimizations that are possible with [`ComposingNormalizerBorrowed`].
2417    pub const fn static_to_owned(self) -> ComposingNormalizer {
2418        ComposingNormalizer {
2419            decomposing_normalizer: self.decomposing_normalizer.static_to_owned(),
2420            canonical_compositions: DataPayload::from_static_ref(self.canonical_compositions),
2421        }
2422    }
2423
2424    /// NFC constructor using compiled data.
2425    ///
2426    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
2427    ///
2428    /// [📚 Help choosing a constructor](icu_provider::constructors)
2429    #[cfg(feature = "compiled_data")]
2430    pub const fn new_nfc() -> Self {
2431        ComposingNormalizerBorrowed {
2432            decomposing_normalizer: DecomposingNormalizerBorrowed::new_nfd(),
2433            canonical_compositions: provider::Baked::SINGLETON_NORMALIZER_NFC_V1,
2434        }
2435    }
2436
2437    /// NFKC constructor using compiled data.
2438    ///
2439    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
2440    ///
2441    /// [📚 Help choosing a constructor](icu_provider::constructors)
2442    #[cfg(feature = "compiled_data")]
2443    pub const fn new_nfkc() -> Self {
2444        ComposingNormalizerBorrowed {
2445            decomposing_normalizer: DecomposingNormalizerBorrowed::new_nfkd(),
2446            canonical_compositions: provider::Baked::SINGLETON_NORMALIZER_NFC_V1,
2447        }
2448    }
2449
2450    /// This is a special building block normalization for IDNA that implements parts of the Map
2451    /// step and the following Normalize step.
2452    ///
2453    /// Warning: In this normalization, U+0345 COMBINING GREEK YPOGEGRAMMENI exhibits a behavior
2454    /// that no character in Unicode exhibits in NFD, NFKD, NFC, or NFKC: Case folding turns
2455    /// U+0345 from a reordered character into a non-reordered character before reordering happens.
2456    /// Therefore, the output of this normalization may differ for different inputs that are
2457    /// canonically equivalents with each other if they differ by how U+0345 is ordered relative
2458    /// to other reorderable characters.
2459    #[cfg(feature = "compiled_data")]
2460    pub(crate) const fn new_uts46() -> Self {
2461        ComposingNormalizerBorrowed {
2462            decomposing_normalizer: DecomposingNormalizerBorrowed::new_uts46_decomposed(),
2463            canonical_compositions: provider::Baked::SINGLETON_NORMALIZER_NFC_V1,
2464        }
2465    }
2466}
2467
2468impl<'data> ComposingNormalizerBorrowed<'data> {
2469    /// Wraps a delegate iterator into a composing iterator
2470    /// adapter by using the data already held by this normalizer.
2471    pub fn normalize_iter<I: Iterator<Item = char>>(&self, iter: I) -> Composition<'data, I> {
2472        self.normalize_iter_private(iter, IgnorableBehavior::Unsupported)
2473    }
2474
2475    fn normalize_iter_private<I: Iterator<Item = char>>(
2476        &self,
2477        iter: I,
2478        ignorable_behavior: IgnorableBehavior,
2479    ) -> Composition<'data, I> {
2480        Composition::new(
2481            Decomposition::new_with_supplements(
2482                iter,
2483                self.decomposing_normalizer.decompositions,
2484                self.decomposing_normalizer.tables,
2485                self.decomposing_normalizer.supplementary_tables,
2486                self.decomposing_normalizer.decomposition_passthrough_bound,
2487                ignorable_behavior,
2488            ),
2489            self.canonical_compositions.canonical_compositions.clone(),
2490            self.decomposing_normalizer.composition_passthrough_bound,
2491        )
2492    }
2493
2494    /// Normalize a string slice into a `Cow<'a, str>`.
pub fn normalize<'a>(&self, text: &'a str) -> Cow<'a, str> {
    let (head, tail) = self.split_normalized(text);
    if tail.is_empty() { return Cow::Borrowed(head); }
    let mut ret = String::new();
    ret.reserve(text.len());
    ret.push_str(head);
    let _ = self.normalize_to(tail, &mut ret);
    Cow::Owned(ret)
}
/// Split a string slice into maximum normalized prefix and unnormalized suffix
/// such that the concatenation of the prefix and the normalization of the suffix
/// is the normalization of the whole input.
pub fn split_normalized<'a>(&self, text: &'a str) -> (&'a str, &'a str) {
    let up_to = self.is_normalized_up_to(text);
    text.split_at_checked(up_to).unwrap_or_else(||
            {
                if true {
                    if !false {
                        ::core::panicking::panic("assertion failed: false")
                    };
                };
                ("", text)
            })
}
/// Return the index a string slice is normalized up to.
fn is_normalized_up_to(&self, text: &str) -> usize {
    let mut sink = IsNormalizedSinkStr::new(text);
    let _ = self.normalize_to(text, &mut sink);
    text.len() - sink.remaining_len()
}
/// Check whether a string slice is normalized.
pub fn is_normalized(&self, text: &str) -> bool {
    self.is_normalized_up_to(text) == text.len()
}normalizer_methods!();
2495
2496    #[doc = r" Normalize a string slice into a `Write` sink."]
pub fn normalize_to<W: core::fmt::Write +
    ?Sized>(&self, text: &str, sink: &mut W) -> core::fmt::Result {
    {}
    let mut composition = self.normalize_iter(text.chars());
    if true {
        {
            match (&composition.decomposition.ignorable_behavior,
                    &IgnorableBehavior::Unsupported) {
                (left_val, right_val) => {
                    if !(*left_val == *right_val) {
                        let kind = ::core::panicking::AssertKind::Eq;
                        ::core::panicking::assert_failed(kind, &*left_val,
                            &*right_val, ::core::option::Option::None);
                    }
                }
            }
        };
    };
    for cc in composition.decomposition.buffer.drain(..) {
        sink.write_char(cc.character())?;
    }
    let composition_passthrough_bound =
        composition.composition_passthrough_bound;
    'outer: loop {
        if true {
            {
                match (&composition.decomposition.buffer_pos, &0) {
                    (left_val, right_val) => {
                        if !(*left_val == *right_val) {
                            let kind = ::core::panicking::AssertKind::Eq;
                            ::core::panicking::assert_failed(kind, &*left_val,
                                &*right_val, ::core::option::Option::None);
                        }
                    }
                }
            };
        };
        let mut undecomposed_starter =
            if let Some(pending) = composition.decomposition.pending.take() {
                pending
            } else { return Ok(()); };
        if u32::from(undecomposed_starter.character) <
                    composition_passthrough_bound ||
                undecomposed_starter.potential_passthrough() {
            if true ||
                    undecomposed_starter.character !=
                        char::REPLACEMENT_CHARACTER {
                let pending_slice =
                    &text[text.len() -
                                        composition.decomposition.delegate.as_str().len() -
                                    undecomposed_starter.character.len_utf8()..];
                {
                    let composition_passthrough_byte_bound =
                        if composition_passthrough_bound == 0x300 {
                            0xCCu8
                        } else { composition_passthrough_bound.min(0x80) as u8 };

                    #[expect(clippy::unwrap_used)]
                    'fast: loop {
                        let mut code_unit_iter =
                            composition.decomposition.delegate.as_str().as_bytes().iter();
                        'fastest: loop {
                            if let Some(&upcoming_byte) = code_unit_iter.next() {
                                if upcoming_byte < composition_passthrough_byte_bound {
                                    continue 'fastest;
                                }
                                let Some(remaining_slice) =
                                    pending_slice.get(pending_slice.len() -
                                                    code_unit_iter.as_slice().len() -
                                                1..) else {
                                        if true {
                                            if !false {
                                                ::core::panicking::panic("assertion failed: false")
                                            };
                                        };
                                        break 'fastest;
                                    };
                                composition.decomposition.delegate =
                                    remaining_slice.chars();
                                break 'fastest;
                            }
                            sink.write_str(pending_slice)?;
                            return Ok(());
                        }
                        let upcoming =
                            composition.decomposition.delegate.next().unwrap();
                        let upcoming_with_trie_value =
                            composition.decomposition.attach_trie_value(upcoming);
                        if upcoming_with_trie_value.potential_passthrough_and_cannot_combine_backwards()
                            {
                            continue 'fast;
                        }
                        composition.decomposition.pending =
                            Some(upcoming_with_trie_value);
                        let mut consumed_so_far =
                            pending_slice[..pending_slice.len() -
                                                composition.decomposition.delegate.as_str().len() -
                                            upcoming.len_utf8()].chars();
                        undecomposed_starter =
                            composition.decomposition.attach_trie_value(consumed_so_far.next_back().unwrap());
                        let consumed_so_far_slice = consumed_so_far.as_str();
                        sink.write_str(consumed_so_far_slice)?;
                        break 'fast;
                    }
                }
            }
        }
        let mut starter =
            composition.decomposition.decomposing_next(undecomposed_starter);
        'bufferloop: loop {
            loop {
                let (character, ccc) =
                    if let Some((character, ccc)) =
                            composition.decomposition.buffer.get(composition.decomposition.buffer_pos).map(|c|
                                    c.character_and_ccc()) {
                        (character, ccc)
                    } else {
                        composition.decomposition.buffer.clear();
                        composition.decomposition.buffer_pos = 0;
                        break;
                    };
                if let Some(composed) =
                        composition.compose(starter, character) {
                    starter = composed;
                    composition.decomposition.buffer_pos += 1;
                    continue;
                }
                let mut most_recent_skipped_ccc = ccc;
                if most_recent_skipped_ccc ==
                        CanonicalCombiningClass::NotReordered {
                    sink.write_char(starter)?;
                    starter = character;
                    composition.decomposition.buffer_pos += 1;
                    continue 'bufferloop;
                } else {
                    {
                        let _ =
                            composition.decomposition.buffer.drain(0..composition.decomposition.buffer_pos);
                    }
                    composition.decomposition.buffer_pos = 0;
                }
                let mut i = 1;
                while let Some((character, ccc)) =
                        composition.decomposition.buffer.get(i).map(|c|
                                c.character_and_ccc()) {
                    if ccc == CanonicalCombiningClass::NotReordered {
                        sink.write_char(starter)?;
                        for cc in composition.decomposition.buffer.drain(..i) {
                            sink.write_char(cc.character())?;
                        }
                        starter = character;
                        {
                            let removed = composition.decomposition.buffer.remove(0);
                            if true {
                                {
                                    match (&starter, &removed.character()) {
                                        (left_val, right_val) => {
                                            if !(*left_val == *right_val) {
                                                let kind = ::core::panicking::AssertKind::Eq;
                                                ::core::panicking::assert_failed(kind, &*left_val,
                                                    &*right_val, ::core::option::Option::None);
                                            }
                                        }
                                    }
                                };
                            };
                        }
                        if true {
                            {
                                match (&composition.decomposition.buffer_pos, &0) {
                                    (left_val, right_val) => {
                                        if !(*left_val == *right_val) {
                                            let kind = ::core::panicking::AssertKind::Eq;
                                            ::core::panicking::assert_failed(kind, &*left_val,
                                                &*right_val, ::core::option::Option::None);
                                        }
                                    }
                                }
                            };
                        };
                        continue 'bufferloop;
                    }
                    if true {
                        if !(ccc >= most_recent_skipped_ccc) {
                            ::core::panicking::panic("assertion failed: ccc >= most_recent_skipped_ccc")
                        };
                    };
                    if ccc != most_recent_skipped_ccc {
                        if let Some(composed) =
                                composition.compose_non_hangul(starter, character) {
                            composition.decomposition.buffer.remove(i);
                            starter = composed;
                            continue;
                        }
                    }
                    most_recent_skipped_ccc = ccc;
                    i += 1;
                }
                break;
            }
            if true {
                {
                    match (&composition.decomposition.buffer_pos, &0) {
                        (left_val, right_val) => {
                            if !(*left_val == *right_val) {
                                let kind = ::core::panicking::AssertKind::Eq;
                                ::core::panicking::assert_failed(kind, &*left_val,
                                    &*right_val, ::core::option::Option::None);
                            }
                        }
                    }
                };
            };
            if !composition.decomposition.buffer.is_empty() {
                sink.write_char(starter)?;
                for cc in composition.decomposition.buffer.drain(..) {
                    sink.write_char(cc.character())?;
                }
                continue 'outer;
            }
            if composition.decomposition.pending.is_some() {
                let pending =
                    composition.decomposition.pending.as_ref().unwrap();
                if u32::from(pending.character) <
                            composition.composition_passthrough_bound ||
                        !pending.can_combine_backwards() {
                    sink.write_char(starter)?;
                    continue 'outer;
                }
                let pending_starter =
                    composition.decomposition.pending.take().unwrap();
                let decomposed =
                    composition.decomposition.decomposing_next(pending_starter);
                if let Some(composed) =
                        composition.compose(starter, decomposed) {
                    starter = composed;
                } else { sink.write_char(starter)?; starter = decomposed; }
                continue 'bufferloop;
            }
            sink.write_char(starter)?;
            return Ok(());
        }
    }
}composing_normalize_to!(
2497        /// Normalize a string slice into a `Write` sink.
2498        ,
2499        normalize_to,
2500        core::fmt::Write,
2501        &str,
2502        {},
2503        true,
2504        as_str,
2505        {
2506            // Let's hope LICM hoists this outside `'outer`.
2507            let composition_passthrough_byte_bound = if composition_passthrough_bound == 0x300 {
2508                0xCCu8
2509            } else {
2510                // We can make this fancy if a normalization other than NFC where looking at
2511                // non-ASCII lead bytes is worthwhile is ever introduced.
2512                composition_passthrough_bound.min(0x80) as u8
2513            };
2514            // Attributes have to be on blocks, so hoisting all the way here.
2515            #[expect(clippy::unwrap_used)]
2516            'fast: loop {
2517                let mut code_unit_iter = composition.decomposition.delegate.as_str().as_bytes().iter();
2518                'fastest: loop {
2519                    if let Some(&upcoming_byte) = code_unit_iter.next() {
2520                        if upcoming_byte < composition_passthrough_byte_bound {
2521                            // Fast-track succeeded!
2522                            continue 'fastest;
2523                        }
2524                        let Some(remaining_slice) = pending_slice.get(pending_slice.len() - code_unit_iter.as_slice().len() - 1..) else {
2525                            // If we ever come here, it's an internal bug. Let's avoid panic code paths in release builds.
2526                            debug_assert!(false);
2527                            // Throw away the fastest-path result in case of an internal bug.
2528                            break 'fastest;
2529                        };
2530                        composition.decomposition.delegate = remaining_slice.chars();
2531                        break 'fastest;
2532                    }
2533                    // End of stream
2534                    sink.write_str(pending_slice)?;
2535                    return Ok(());
2536                }
2537                // `unwrap()` OK, because the slice is valid UTF-8 and we know there
2538                // is an upcoming byte.
2539                let upcoming = composition.decomposition.delegate.next().unwrap();
2540                let upcoming_with_trie_value = composition.decomposition.attach_trie_value(upcoming);
2541                if upcoming_with_trie_value.potential_passthrough_and_cannot_combine_backwards() {
2542                    // Can't combine backwards, hence a plain (non-backwards-combining)
2543                    // starter albeit past `composition_passthrough_bound`
2544
2545                    // Fast-track succeeded!
2546                    continue 'fast;
2547                }
2548                // We need to fall off the fast path.
2549                composition.decomposition.pending = Some(upcoming_with_trie_value);
2550
2551                // slicing and unwrap OK, because we've just evidently read enough previously.
2552                let mut consumed_so_far = pending_slice[..pending_slice.len() - composition.decomposition.delegate.as_str().len() - upcoming.len_utf8()].chars();
2553                // `unwrap` OK, because we've previously manage to read the previous character
2554                undecomposed_starter = composition.decomposition.attach_trie_value(consumed_so_far.next_back().unwrap());
2555                let consumed_so_far_slice = consumed_so_far.as_str();
2556                sink.write_str(consumed_so_far_slice)?;
2557                break 'fast;
2558            }
2559        },
2560        text,
2561        sink,
2562        composition,
2563        composition_passthrough_bound,
2564        undecomposed_starter,
2565        pending_slice,
2566        len_utf8,
2567    );
2568
2569    composing_normalize_to!(
2570        /// Normalize a slice of potentially-invalid UTF-8 into a `Write` sink.
2571        ///
2572        /// Ill-formed byte sequences are mapped to the REPLACEMENT CHARACTER
2573        /// according to the WHATWG Encoding Standard.
2574        ///
2575        /// ✨ *Enabled with the `utf8_iter` Cargo feature.*
2576        #[cfg(feature = "utf8_iter")]
2577        ,
2578        normalize_utf8_to,
2579        core::fmt::Write,
2580        &[u8],
2581        {},
2582        false,
2583        as_slice,
2584        {
2585            'fast: loop {
2586                if let Some(upcoming) = composition.decomposition.delegate.next() {
2587                    if u32::from(upcoming) < composition_passthrough_bound {
2588                        // Fast-track succeeded!
2589                        continue 'fast;
2590                    }
2591                    // TODO: Be statically aware of fast/small trie.
2592                    let upcoming_with_trie_value = composition.decomposition.attach_trie_value(upcoming);
2593                    if upcoming_with_trie_value.potential_passthrough_and_cannot_combine_backwards() {
2594                        // Note: The trie value of the REPLACEMENT CHARACTER is
2595                        // intentionally formatted to fail the
2596                        // `potential_passthrough_and_cannot_combine_backwards`
2597                        // test even though it really is a starter that decomposes
2598                        // to self and cannot combine backwards. This
2599                        // Allows moving the branch on REPLACEMENT CHARACTER
2600                        // below this `continue`.
2601                        continue 'fast;
2602                    }
2603                    // We need to fall off the fast path.
2604
2605                    // TODO(#2006): Annotate as unlikely
2606                    if upcoming == char::REPLACEMENT_CHARACTER {
2607                        // Can't tell if this is an error or a literal U+FFFD in
2608                        // the input. Assuming the former to be sure.
2609
2610                        // Since the U+FFFD might signify an error, we can't
2611                        // assume `upcoming.len_utf8()` for the backoff length.
2612                        #[expect(clippy::indexing_slicing)]
2613                        let mut consumed_so_far = pending_slice[..pending_slice.len() - composition.decomposition.delegate.as_slice().len()].chars();
2614                        let back = consumed_so_far.next_back();
2615                        debug_assert_eq!(back, Some(char::REPLACEMENT_CHARACTER));
2616                        let consumed_so_far_slice = consumed_so_far.as_slice();
2617                        sink.write_str(unsafe { str::from_utf8_unchecked(consumed_so_far_slice) })?;
2618                        undecomposed_starter = CharacterAndTrieValue::new(char::REPLACEMENT_CHARACTER, 0);
2619                        composition.decomposition.pending = None;
2620                        break 'fast;
2621                    }
2622
2623                    composition.decomposition.pending = Some(upcoming_with_trie_value);
2624                    // slicing and unwrap OK, because we've just evidently read enough previously.
2625                    // `unwrap` OK, because we've previously manage to read the previous character
2626                    #[expect(clippy::indexing_slicing)]
2627                    let mut consumed_so_far = pending_slice[..pending_slice.len() - composition.decomposition.delegate.as_slice().len() - upcoming.len_utf8()].chars();
2628                    #[expect(clippy::unwrap_used)]
2629                    {
2630                        // TODO: If the previous character was below the passthrough bound,
2631                        // we really need to read from the trie. Otherwise, we could maintain
2632                        // the most-recent trie value. Need to measure what's more expensive:
2633                        // Remembering the trie value on each iteration or re-reading the
2634                        // last one after the fast-track run.
2635                        undecomposed_starter = composition.decomposition.attach_trie_value(consumed_so_far.next_back().unwrap());
2636                    }
2637                    let consumed_so_far_slice = consumed_so_far.as_slice();
2638                    sink.write_str(unsafe { str::from_utf8_unchecked(consumed_so_far_slice)})?;
2639                    break 'fast;
2640                }
2641                // End of stream
2642                sink.write_str(unsafe { str::from_utf8_unchecked(pending_slice) })?;
2643                return Ok(());
2644            }
2645        },
2646        text,
2647        sink,
2648        composition,
2649        composition_passthrough_bound,
2650        undecomposed_starter,
2651        pending_slice,
2652        len_utf8,
2653    );
2654
2655    composing_normalize_to!(
2656        /// Normalize a slice of potentially-invalid UTF-16 into a `Write16` sink.
2657        ///
2658        /// Unpaired surrogates are mapped to the REPLACEMENT CHARACTER
2659        /// before normalizing.
2660        ///
2661        /// ✨ *Enabled with the `utf16_iter` Cargo feature.*
2662        #[cfg(feature = "utf16_iter")]
2663        ,
2664        normalize_utf16_to,
2665        write16::Write16,
2666        &[u16],
2667        {
2668            sink.size_hint(text.len())?;
2669        },
2670        false,
2671        as_slice,
2672        {
2673            // This loop is only broken out of as goto forward and only as release-build recovery from
2674            // detecting an internal bug without panic. (In debug builds, internal bugs panic instead.)
2675            #[expect(clippy::never_loop)]
2676            'fastwrap: loop {
2677                // Commented out `code_unit_iter` and used `ptr` and `end` to
2678                // work around https://github.com/rust-lang/rust/issues/144684 .
2679                //
2680                // let mut code_unit_iter = composition.decomposition.delegate.as_slice().iter();
2681                let delegate_as_slice = composition.decomposition.delegate.as_slice();
2682                let mut ptr: *const u16 = delegate_as_slice.as_ptr();
2683                // SAFETY: materializing a pointer immediately past the end of an
2684                // allocation is OK.
2685                let end: *const u16 = unsafe { ptr.add(delegate_as_slice.len()) };
2686
2687                'fast: loop {
2688                    // if let Some(&upcoming_code_unit) = code_unit_iter.next() {
2689                    if ptr != end {
2690                        // SAFETY: We just checked that `ptr` has not reached `end`.
2691                        // `ptr` always advances by one, and we always have a check
2692                        // per advancement.
2693                        let upcoming_code_unit = unsafe { *ptr };
2694                        // SAFETY: Since `ptr` hadn't reached `end`, yet, advancing
2695                        // by one points to the same allocation or to immediately
2696                        // after, which is OK.
2697                        ptr = unsafe { ptr.add(1) };
2698
2699                        let mut upcoming32 = u32::from(upcoming_code_unit); // may be surrogate
2700                        // The performance of what logically is supposed to be this
2701                        // branch is somewhat brittle and what LLVM ends up doing
2702                        // that affects the performance of what's logically about this
2703                        // decision can swing to double/halve the throughput for Basic
2704                        // Latin in ways that are completely unintuitive. Basically _any_
2705                        // change to _any_ code that participates in how LLVM sees the
2706                        // code around here can make the perf fall over. In seems that
2707                        // manually annotating this branch as likely has worse effects
2708                        // on non-Basic-Latin input that the case where LLVM just happens to
2709                        // do the right thing.
2710                        //
2711                        // What happens with this branch may depend on what sink type
2712                        // this code is monomorphized over.
2713                        //
2714                        // What a terrible sink of developer time!
2715                        if upcoming32 < composition_passthrough_bound {
2716                            // No need for surrogate or U+FFFD check, because
2717                            // `composition_passthrough_bound` cannot be higher than
2718                            // U+0300.
2719                            // Fast-track succeeded!
2720                            continue 'fast;
2721                        }
2722                        // We might be doing a trie lookup by surrogate. Surrogates get
2723                        // a decomposition to U+FFFD.
2724                        let mut trie_value = composition.decomposition.trie.get16(upcoming_code_unit);
2725                        if potential_passthrough_and_cannot_combine_backwards_impl(trie_value) {
2726                            // Can't combine backwards, hence a plain (non-backwards-combining)
2727                            // starter albeit past `composition_passthrough_bound`
2728
2729                            // Fast-track succeeded!
2730                            continue 'fast;
2731                        }
2732
2733                        // We might now be looking at a surrogate.
2734                        // The loop is only broken out of as goto forward
2735                        #[expect(clippy::never_loop)]
2736                        'surrogateloop: loop {
2737                            // The `likely` annotations _below_ exist to make the code _above_
2738                            // go faster!
2739                            let surrogate_base = upcoming32.wrapping_sub(0xD800);
2740                            if likely(surrogate_base > (0xDFFF - 0xD800)) {
2741                                // Not surrogate
2742                                break 'surrogateloop;
2743                            }
2744                            if likely(surrogate_base <= (0xDBFF - 0xD800)) {
2745                                // let iter_backup = code_unit_iter.clone();
2746                                // if let Some(&low) = code_unit_iter.next() {
2747                                if ptr != end {
2748                                    // SAFETY: We just checked that `ptr` has not reached `end`.
2749                                    // `ptr` always advances by one, and we always have a check
2750                                    // per advancement.
2751                                    let low = unsafe { *ptr };
2752                                    if likely(in_inclusive_range16(low, 0xDC00, 0xDFFF)) {
2753                                        // SAFETY: Since `ptr` hadn't reached `end`, yet, advancing
2754                                        // by one points to the same allocation or to immediately
2755                                        // after, which is OK.
2756                                        ptr = unsafe { ptr.add(1) };
2757
2758                                        upcoming32 = (upcoming32 << 10) + u32::from(low)
2759                                            - (((0xD800u32 << 10) - 0x10000u32) + 0xDC00u32);
2760                                        // Successfully-paired surrogate. Read from the trie again.
2761                                        trie_value = {
2762                                            // Semantically, this bit of conditional compilation makes no sense.
2763                                            // The purpose is to keep LLVM seeing the untyped trie case the way
2764                                            // it did before so as not to regress the performance of the untyped
2765                                            // case due to unintuitive optimizer effects. If you care about the
2766                                            // perf of the untyped trie case and have better ideas, please try
2767                                            // something better.
2768                                            #[cfg(not(icu4x_unstable_fast_trie_only))]
2769                                            {composition.decomposition.trie.get32(upcoming32)}
2770                                            #[cfg(icu4x_unstable_fast_trie_only)]
2771                                            {composition.decomposition.trie.get32_supplementary(upcoming32)}
2772                                        };
2773                                        if likely(potential_passthrough_and_cannot_combine_backwards_impl(trie_value)) {
2774                                            // Fast-track succeeded!
2775                                            continue 'fast;
2776                                        }
2777                                        break 'surrogateloop;
2778                                    // } else {
2779                                    //     code_unit_iter = iter_backup;
2780                                    }
2781                                }
2782                            }
2783                            // unpaired surrogate
2784                            upcoming32 = 0xFFFD; // Safe value for `char::from_u32_unchecked` and matches later potential error check.
2785                            // trie_value already holds a decomposition to U+FFFD.
2786                            debug_assert_eq!(trie_value, NON_ROUND_TRIP_MARKER | BACKWARD_COMBINING_MARKER | 0xFFFD);
2787                            break 'surrogateloop;
2788                        }
2789
2790                        // SAFETY: upcoming32 can no longer be a surrogate.
2791                        let upcoming = unsafe { char::from_u32_unchecked(upcoming32) };
2792                        let upcoming_with_trie_value = CharacterAndTrieValue::new(upcoming, trie_value);
2793                        // We need to fall off the fast path.
2794                        composition.decomposition.pending = Some(upcoming_with_trie_value);
2795                        let Some(consumed_so_far_slice) = pending_slice.get(..pending_slice.len() -
2796                            // code_unit_iter.as_slice().len()
2797                            // SAFETY: `ptr` and `end` have been derived from the same allocation
2798                            // and `ptr` is never greater than `end`.
2799                            unsafe { end.offset_from(ptr) as usize }
2800                            - upcoming.len_utf16()) else {
2801                            // If we ever come here, it's a bug, but let's avoid panic code paths in release builds.
2802                            debug_assert!(false);
2803                            // Throw away the results of the fast path.
2804                            break 'fastwrap;
2805                        };
2806                        let mut consumed_so_far = consumed_so_far_slice.chars();
2807                        let Some(c_from_back) = consumed_so_far.next_back() else {
2808                            // If we ever come here, it's a bug, but let's avoid panic code paths in release builds.
2809                            debug_assert!(false);
2810                            // Throw away the results of the fast path.
2811                            break 'fastwrap;
2812                        };
2813                        // TODO: If the previous character was below the passthrough bound,
2814                        // we really need to read from the trie. Otherwise, we could maintain
2815                        // the most-recent trie value. Need to measure what's more expensive:
2816                        // Remembering the trie value on each iteration or re-reading the
2817                        // last one after the fast-track run.
2818                        undecomposed_starter = composition.decomposition.attach_trie_value(c_from_back);
2819                        sink.write_slice(consumed_so_far.as_slice())?;
2820                        break 'fast;
2821                    }
2822                    // End of stream
2823                    sink.write_slice(pending_slice)?;
2824                    return Ok(());
2825                }
2826                // Sync the main iterator
2827                // composition.decomposition.delegate = code_unit_iter.as_slice().chars();
2828                // SAFETY: `ptr` and `end` have been derive from the same allocation
2829                // and `ptr` is never greater than `end`.
2830                composition.decomposition.delegate = unsafe { core::slice::from_raw_parts(ptr, end.offset_from(ptr) as usize) }.chars();
2831                break 'fastwrap;
2832            }
2833        },
2834        text,
2835        sink,
2836        composition,
2837        composition_passthrough_bound,
2838        undecomposed_starter,
2839        pending_slice,
2840        len_utf16,
2841    );
2842}
2843
2844/// A normalizer for performing composing normalization.
2845#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ComposingNormalizer {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "ComposingNormalizer", "decomposing_normalizer",
            &self.decomposing_normalizer, "canonical_compositions",
            &&self.canonical_compositions)
    }
}Debug)]
2846pub struct ComposingNormalizer {
2847    decomposing_normalizer: DecomposingNormalizer,
2848    canonical_compositions: DataPayload<NormalizerNfcV1>,
2849}
2850
2851impl ComposingNormalizer {
2852    /// Constructs a borrowed version of this type for more efficient querying.
2853    pub fn as_borrowed(&self) -> ComposingNormalizerBorrowed<'_> {
2854        ComposingNormalizerBorrowed {
2855            decomposing_normalizer: self.decomposing_normalizer.as_borrowed(),
2856            canonical_compositions: self.canonical_compositions.get(),
2857        }
2858    }
2859
2860    /// NFC constructor using compiled data.
2861    ///
2862    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
2863    ///
2864    /// [📚 Help choosing a constructor](icu_provider::constructors)
2865    #[cfg(feature = "compiled_data")]
2866    pub const fn new_nfc() -> ComposingNormalizerBorrowed<'static> {
2867        ComposingNormalizerBorrowed::new_nfc()
2868    }
2869
2870    icu_provider::gen_buffer_data_constructors!(
2871        () -> error: DataError,
2872        functions: [
2873            new_nfc: skip,
2874            try_new_nfc_with_buffer_provider,
2875            try_new_nfc_unstable,
2876            Self,
2877        ]
2878    );
2879
2880    #[doc = "A version of [`Self::new_nfc`] that uses custom data provided by a [`DataProvider`].\n\n[\u{1f4da} Help choosing a constructor](icu_provider::constructors)\n\n<div class=\"stab unstable\">\u{26a0}\u{fe0f} The bounds on <tt>provider</tt> may change over time, including in SemVer minor releases.</div>"icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_nfc)]
2881    pub fn try_new_nfc_unstable<D>(provider: &D) -> Result<Self, DataError>
2882    where
2883        D: DataProvider<NormalizerNfdDataV1>
2884            + DataProvider<NormalizerNfdTablesV1>
2885            + DataProvider<NormalizerNfcV1>
2886            + ?Sized,
2887    {
2888        let decomposing_normalizer = DecomposingNormalizer::try_new_nfd_unstable(provider)?;
2889
2890        let canonical_compositions: DataPayload<NormalizerNfcV1> =
2891            provider.load(Default::default())?.payload;
2892
2893        Ok(ComposingNormalizer {
2894            decomposing_normalizer,
2895            canonical_compositions,
2896        })
2897    }
2898
2899    /// NFKC constructor using compiled data.
2900    ///
2901    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
2902    ///
2903    /// [📚 Help choosing a constructor](icu_provider::constructors)
2904    #[cfg(feature = "compiled_data")]
2905    pub const fn new_nfkc() -> ComposingNormalizerBorrowed<'static> {
2906        ComposingNormalizerBorrowed::new_nfkc()
2907    }
2908
2909    icu_provider::gen_buffer_data_constructors!(
2910        () -> error: DataError,
2911        functions: [
2912            new_nfkc: skip,
2913            try_new_nfkc_with_buffer_provider,
2914            try_new_nfkc_unstable,
2915            Self,
2916        ]
2917    );
2918
2919    #[doc = "A version of [`Self::new_nfkc`] that uses custom data provided by a [`DataProvider`].\n\n[\u{1f4da} Help choosing a constructor](icu_provider::constructors)\n\n<div class=\"stab unstable\">\u{26a0}\u{fe0f} The bounds on <tt>provider</tt> may change over time, including in SemVer minor releases.</div>"icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_nfkc)]
2920    pub fn try_new_nfkc_unstable<D>(provider: &D) -> Result<Self, DataError>
2921    where
2922        D: DataProvider<NormalizerNfkdDataV1>
2923            + DataProvider<NormalizerNfdTablesV1>
2924            + DataProvider<NormalizerNfkdTablesV1>
2925            + DataProvider<NormalizerNfcV1>
2926            + ?Sized,
2927    {
2928        let decomposing_normalizer = DecomposingNormalizer::try_new_nfkd_unstable(provider)?;
2929
2930        let canonical_compositions: DataPayload<NormalizerNfcV1> =
2931            provider.load(Default::default())?.payload;
2932
2933        Ok(ComposingNormalizer {
2934            decomposing_normalizer,
2935            canonical_compositions,
2936        })
2937    }
2938
2939    #[doc = "A version of [`Self::new_uts46`] that uses custom data provided by a [`DataProvider`].\n\n[\u{1f4da} Help choosing a constructor](icu_provider::constructors)\n\n<div class=\"stab unstable\">\u{26a0}\u{fe0f} The bounds on <tt>provider</tt> may change over time, including in SemVer minor releases.</div>"icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_uts46)]
2940    pub(crate) fn try_new_uts46_unstable<D>(provider: &D) -> Result<Self, DataError>
2941    where
2942        D: DataProvider<NormalizerUts46DataV1>
2943            + DataProvider<NormalizerNfdTablesV1>
2944            + DataProvider<NormalizerNfkdTablesV1>
2945            // UTS 46 tables merged into CompatibilityDecompositionTablesV1
2946            + DataProvider<NormalizerNfcV1>
2947            + ?Sized,
2948    {
2949        let decomposing_normalizer =
2950            DecomposingNormalizer::try_new_uts46_decomposed_unstable(provider)?;
2951
2952        let canonical_compositions: DataPayload<NormalizerNfcV1> =
2953            provider.load(Default::default())?.payload;
2954
2955        Ok(ComposingNormalizer {
2956            decomposing_normalizer,
2957            canonical_compositions,
2958        })
2959    }
2960}
2961
2962#[cfg(feature = "utf16_iter")]
2963struct IsNormalizedSinkUtf16<'a> {
2964    expect: &'a [u16],
2965}
2966
2967#[cfg(feature = "utf16_iter")]
2968impl<'a> IsNormalizedSinkUtf16<'a> {
2969    pub fn new(slice: &'a [u16]) -> Self {
2970        IsNormalizedSinkUtf16 { expect: slice }
2971    }
2972    pub fn remaining_len(&self) -> usize {
2973        self.expect.len()
2974    }
2975}
2976
2977#[cfg(feature = "utf16_iter")]
2978impl write16::Write16 for IsNormalizedSinkUtf16<'_> {
2979    fn write_slice(&mut self, s: &[u16]) -> core::fmt::Result {
2980        // We know that if we get a slice, it's a pass-through,
2981        // so we can compare addresses. Indexing is OK, because
2982        // an indexing failure would be a code bug rather than
2983        // an input or data issue.
2984        #[expect(clippy::indexing_slicing)]
2985        if core::ptr::eq(s.as_ptr(), self.expect.as_ptr()) {
2986            self.expect = &self.expect[s.len()..];
2987            Ok(())
2988        } else {
2989            Err(core::fmt::Error {})
2990        }
2991    }
2992
2993    fn write_char(&mut self, c: char) -> core::fmt::Result {
2994        let mut iter = self.expect.chars();
2995        if iter.next() == Some(c) {
2996            self.expect = iter.as_slice();
2997            Ok(())
2998        } else {
2999            Err(core::fmt::Error {})
3000        }
3001    }
3002}
3003
3004#[cfg(feature = "utf8_iter")]
3005struct IsNormalizedSinkUtf8<'a> {
3006    expect: &'a [u8],
3007}
3008
3009#[cfg(feature = "utf8_iter")]
3010impl<'a> IsNormalizedSinkUtf8<'a> {
3011    pub fn new(slice: &'a [u8]) -> Self {
3012        IsNormalizedSinkUtf8 { expect: slice }
3013    }
3014    pub fn remaining_len(&self) -> usize {
3015        self.expect.len()
3016    }
3017}
3018
3019#[cfg(feature = "utf8_iter")]
3020impl core::fmt::Write for IsNormalizedSinkUtf8<'_> {
3021    fn write_str(&mut self, s: &str) -> core::fmt::Result {
3022        // We know that if we get a slice, it's a pass-through,
3023        // so we can compare addresses. Indexing is OK, because
3024        // an indexing failure would be a code bug rather than
3025        // an input or data issue.
3026        #[expect(clippy::indexing_slicing)]
3027        if core::ptr::eq(s.as_ptr(), self.expect.as_ptr()) {
3028            self.expect = &self.expect[s.len()..];
3029            Ok(())
3030        } else {
3031            Err(core::fmt::Error {})
3032        }
3033    }
3034
3035    fn write_char(&mut self, c: char) -> core::fmt::Result {
3036        let mut iter = self.expect.chars();
3037        if iter.next() == Some(c) {
3038            self.expect = iter.as_slice();
3039            Ok(())
3040        } else {
3041            Err(core::fmt::Error {})
3042        }
3043    }
3044}
3045
3046struct IsNormalizedSinkStr<'a> {
3047    expect: &'a str,
3048}
3049
3050impl<'a> IsNormalizedSinkStr<'a> {
3051    pub fn new(slice: &'a str) -> Self {
3052        IsNormalizedSinkStr { expect: slice }
3053    }
3054    pub fn remaining_len(&self) -> usize {
3055        self.expect.len()
3056    }
3057}
3058
3059impl core::fmt::Write for IsNormalizedSinkStr<'_> {
3060    fn write_str(&mut self, s: &str) -> core::fmt::Result {
3061        // We know that if we get a slice, it's a pass-through,
3062        // so we can compare addresses. Indexing is OK, because
3063        // an indexing failure would be a code bug rather than
3064        // an input or data issue.
3065        if core::ptr::eq(s.as_ptr(), self.expect.as_ptr()) {
3066            self.expect = &self.expect[s.len()..];
3067            Ok(())
3068        } else {
3069            Err(core::fmt::Error {})
3070        }
3071    }
3072
3073    fn write_char(&mut self, c: char) -> core::fmt::Result {
3074        let mut iter = self.expect.chars();
3075        if iter.next() == Some(c) {
3076            self.expect = iter.as_str();
3077            Ok(())
3078        } else {
3079            Err(core::fmt::Error {})
3080        }
3081    }
3082}