Skip to main content

icu_properties/
script.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//! Data and APIs for supporting `Script_Extensions` property
6//! values in an efficient structure.
7
8use crate::props::Script;
9use crate::provider::*;
10
11#[cfg(feature = "alloc")]
12use core::iter::FromIterator;
13use core::ops::RangeInclusive;
14#[cfg(feature = "alloc")]
15use icu_collections::codepointinvlist::CodePointInversionList;
16use icu_collections::codepointtrie::TrieValue;
17use icu_provider::prelude::*;
18use zerovec::{ZeroSlice, ule::AsULE};
19
20#[cfg(feature = "harfbuzz_traits")]
21pub use crate::harfbuzz::{HarfbuzzScriptData, HarfbuzzScriptDataBorrowed};
22
23/// The number of bits at the low-end of a `ScriptWithExt` value used for
24/// storing the `Script` value (or `extensions` index).
25const SCRIPT_VAL_LENGTH: u16 = 10;
26
27/// The bit mask necessary to retrieve the `Script` value (or `extensions` index)
28/// from a `ScriptWithExt` value.
29const SCRIPT_X_SCRIPT_VAL: u16 = (1 << SCRIPT_VAL_LENGTH) - 1;
30
31/// An internal-use only pseudo-property that represents the values stored in
32/// the trie of the special data structure [`ScriptWithExtensionsProperty`].
33///
34/// Note: The will assume a 12-bit layout. The 2 higher order bits in positions
35/// 11..10 will indicate how to deduce the Script value and Script_Extensions,
36/// and the lower 10 bits 9..0 indicate either the Script value or the index
37/// into the `extensions` structure.
38#[derive(#[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::marker::Copy for ScriptWithExt { }Copy, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::clone::Clone for ScriptWithExt {
    #[inline]
    fn clone(&self) -> ScriptWithExt {
        let _: ::core::clone::AssertParamIsClone<u16>;
        *self
    }
}Clone, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::fmt::Debug for ScriptWithExt {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f, "ScriptWithExt",
            &&self.0)
    }
}Debug, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::Eq for ScriptWithExt {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<u16>;
    }
}Eq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::PartialEq for ScriptWithExt {
    #[inline]
    fn eq(&self, other: &ScriptWithExt) -> bool { self.0 == other.0 }
}PartialEq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40#[cfg_attr(feature = "datagen", derive(databake::Bake))]
41#[cfg_attr(feature = "datagen", databake(path = icu_properties::script))]
42#[repr(transparent)]
43#[doc(hidden)]
44// `ScriptWithExt` not intended as public-facing but for `ScriptWithExtensionsProperty` constructor
45#[allow(clippy::exhaustive_structs)] // this type is stable
46pub struct ScriptWithExt(pub u16);
47
48#[allow(missing_docs)] // These constants don't need individual documentation.
49#[allow(non_upper_case_globals)]
50#[doc(hidden)] // `ScriptWithExt` not intended as public-facing but for `ScriptWithExtensionsProperty` constructor
51impl ScriptWithExt {
52    pub const Unknown: ScriptWithExt = Self::single(Script::Unknown);
53
54    pub const fn single(script: Script) -> Self {
55        Self(script.0 & SCRIPT_X_SCRIPT_VAL)
56    }
57
58    pub const fn new(script: Script, extensions: u16) -> Self {
59        match script {
60            Script::Common => Self(1 << SCRIPT_VAL_LENGTH | extensions & SCRIPT_X_SCRIPT_VAL),
61            Script::Inherited => Self(2 << SCRIPT_VAL_LENGTH | extensions & SCRIPT_X_SCRIPT_VAL),
62            _script => Self(3 << SCRIPT_VAL_LENGTH | extensions & SCRIPT_X_SCRIPT_VAL),
63        }
64    }
65}
66
67impl AsULE for ScriptWithExt {
68    type ULE = <u16 as AsULE>::ULE;
69
70    #[inline]
71    fn to_unaligned(self) -> Self::ULE {
72        Script(self.0).to_unaligned()
73    }
74
75    #[inline]
76    fn from_unaligned(unaligned: Self::ULE) -> Self {
77        ScriptWithExt(Script::from_unaligned(unaligned).0)
78    }
79}
80
81#[doc(hidden)] // `ScriptWithExt` not intended as public-facing but for `ScriptWithExtensionsProperty` constructor
82impl ScriptWithExt {
83    /// Returns whether the [`ScriptWithExt`] value has `Script_Extensions` and
84    /// also indicates a Script value of [`Script::Common`].
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// use icu::properties::script::ScriptWithExt;
90    ///
91    /// assert!(ScriptWithExt(0x04FF).is_common());
92    /// assert!(ScriptWithExt(0x0400).is_common());
93    ///
94    /// assert!(!ScriptWithExt(0x08FF).is_common());
95    /// assert!(!ScriptWithExt(0x0800).is_common());
96    ///
97    /// assert!(!ScriptWithExt(0x0CFF).is_common());
98    /// assert!(!ScriptWithExt(0x0C00).is_common());
99    ///
100    /// assert!(!ScriptWithExt(0xFF).is_common());
101    /// assert!(!ScriptWithExt(0x0).is_common());
102    /// ```
103    pub fn is_common(&self) -> bool {
104        self.0 >> SCRIPT_VAL_LENGTH == 1
105    }
106
107    /// Returns whether the [`ScriptWithExt`] value has `Script_Extensions` and
108    /// also indicates a Script value of [`Script::Inherited`].
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use icu::properties::script::ScriptWithExt;
114    ///
115    /// assert!(!ScriptWithExt(0x04FF).is_inherited());
116    /// assert!(!ScriptWithExt(0x0400).is_inherited());
117    ///
118    /// assert!(ScriptWithExt(0x08FF).is_inherited());
119    /// assert!(ScriptWithExt(0x0800).is_inherited());
120    ///
121    /// assert!(!ScriptWithExt(0x0CFF).is_inherited());
122    /// assert!(!ScriptWithExt(0x0C00).is_inherited());
123    ///
124    /// assert!(!ScriptWithExt(0xFF).is_inherited());
125    /// assert!(!ScriptWithExt(0x0).is_inherited());
126    /// ```
127    pub fn is_inherited(&self) -> bool {
128        self.0 >> SCRIPT_VAL_LENGTH == 2
129    }
130
131    /// Returns whether the [`ScriptWithExt`] value has `Script_Extensions` and
132    /// also indicates that the Script value is neither [`Script::Common`] nor
133    /// [`Script::Inherited`].
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// use icu::properties::script::ScriptWithExt;
139    ///
140    /// assert!(!ScriptWithExt(0x04FF).is_other());
141    /// assert!(!ScriptWithExt(0x0400).is_other());
142    ///
143    /// assert!(!ScriptWithExt(0x08FF).is_other());
144    /// assert!(!ScriptWithExt(0x0800).is_other());
145    ///
146    /// assert!(ScriptWithExt(0x0CFF).is_other());
147    /// assert!(ScriptWithExt(0x0C00).is_other());
148    ///
149    /// assert!(!ScriptWithExt(0xFF).is_other());
150    /// assert!(!ScriptWithExt(0x0).is_other());
151    /// ```
152    pub fn is_other(&self) -> bool {
153        self.0 >> SCRIPT_VAL_LENGTH == 3
154    }
155
156    /// Returns whether the [`ScriptWithExt`] value has `Script_Extensions`.
157    ///
158    /// # Examples
159    ///
160    /// ```
161    /// use icu::properties::script::ScriptWithExt;
162    ///
163    /// assert!(ScriptWithExt(0x04FF).has_extensions());
164    /// assert!(ScriptWithExt(0x0400).has_extensions());
165    ///
166    /// assert!(ScriptWithExt(0x08FF).has_extensions());
167    /// assert!(ScriptWithExt(0x0800).has_extensions());
168    ///
169    /// assert!(ScriptWithExt(0x0CFF).has_extensions());
170    /// assert!(ScriptWithExt(0x0C00).has_extensions());
171    ///
172    /// assert!(!ScriptWithExt(0xFF).has_extensions());
173    /// assert!(!ScriptWithExt(0x0).has_extensions());
174    /// ```
175    pub fn has_extensions(&self) -> bool {
176        let high_order_bits = self.0 >> SCRIPT_VAL_LENGTH;
177        high_order_bits > 0
178    }
179}
180
181impl From<ScriptWithExt> for u32 {
182    fn from(swe: ScriptWithExt) -> Self {
183        swe.0 as u32
184    }
185}
186
187impl From<ScriptWithExt> for Script {
188    fn from(swe: ScriptWithExt) -> Self {
189        Script(swe.0)
190    }
191}
192
193/// A struct that wraps a [`Script`] array, such as in the return value for
194/// [`get_script_extensions_val()`](ScriptWithExtensionsBorrowed::get_script_extensions_val).
195#[derive(#[automatically_derived]
impl<'a> ::core::marker::Copy for ScriptExtensionsSet<'a> { }Copy, #[automatically_derived]
impl<'a> ::core::clone::Clone for ScriptExtensionsSet<'a> {
    #[inline]
    fn clone(&self) -> ScriptExtensionsSet<'a> {
        let _: ::core::clone::AssertParamIsClone<&'a ZeroSlice<Script>>;
        *self
    }
}Clone, #[automatically_derived]
impl<'a> ::core::fmt::Debug for ScriptExtensionsSet<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f,
            "ScriptExtensionsSet", "values", &&self.values)
    }
}Debug, #[automatically_derived]
impl<'a> ::core::cmp::Eq for ScriptExtensionsSet<'a> {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<&'a ZeroSlice<Script>>;
    }
}Eq, #[automatically_derived]
impl<'a> ::core::cmp::PartialEq for ScriptExtensionsSet<'a> {
    #[inline]
    fn eq(&self, other: &ScriptExtensionsSet<'a>) -> bool {
        self.values == other.values
    }
}PartialEq)]
196pub struct ScriptExtensionsSet<'a> {
197    values: &'a ZeroSlice<Script>,
198}
199
200impl<'a> ScriptExtensionsSet<'a> {
201    /// Returns whether this set contains the given script.
202    ///
203    /// # Example
204    ///
205    /// ```
206    /// use icu::properties::props::Script;
207    /// use icu::properties::script::ScriptWithExtensions;
208    /// let swe = ScriptWithExtensions::new();
209    ///
210    /// assert!(
211    ///     swe.get_script_extensions_val('\u{11303}') // GRANTHA SIGN VISARGA
212    ///         .contains(&Script::Grantha)
213    /// );
214    /// ```
215    pub fn contains(&self, x: &Script) -> bool {
216        ZeroSlice::binary_search_by(self.values, |y| y.to_u32().cmp(&x.to_u32())).is_ok()
217    }
218
219    /// Gets an iterator over the elements.
220    ///
221    /// # Example
222    ///
223    /// ```
224    /// use icu::properties::props::Script;
225    /// use icu::properties::script::ScriptWithExtensions;
226    /// let swe = ScriptWithExtensions::new();
227    ///
228    /// assert_eq!(
229    ///     swe.get_script_extensions_val('௫') // U+0BEB TAMIL DIGIT FIVE
230    ///         .iter()
231    ///         .collect::<Vec<_>>(),
232    ///     [Script::Tamil, Script::Grantha]
233    /// );
234    /// ```
235    pub fn iter(&self) -> impl DoubleEndedIterator<Item = Script> + 'a + use<'a> {
236        ZeroSlice::iter(self.values)
237    }
238
239    /// For accessing this set as an array instead of an iterator
240    #[doc(hidden)] // used by FFI code
241    pub fn array_len(&self) -> usize {
242        self.values.len()
243    }
244    /// For accessing this set as an array instead of an iterator
245    #[doc(hidden)] // used by FFI code
246    pub fn array_get(&self, index: usize) -> Option<Script> {
247        self.values.get(index)
248    }
249}
250
251/// A struct that represents the data for the Script and `Script_Extensions` properties.
252///
253/// ✨ *Enabled with the `compiled_data` Cargo feature.*
254///
255/// [📚 Help choosing a constructor](icu_provider::constructors)
256///
257/// Most useful methods are on [`ScriptWithExtensionsBorrowed`] obtained by calling [`ScriptWithExtensions::as_borrowed()`]
258///
259/// # Examples
260///
261/// ```
262/// use icu::properties::script::ScriptWithExtensions;
263/// use icu::properties::props::Script;
264/// let swe = ScriptWithExtensions::new();
265///
266/// // get the `Script` property value
267/// assert_eq!(swe.get_script_val('ـ'), Script::Common); // U+0640 ARABIC TATWEEL
268/// assert_eq!(swe.get_script_val('\u{0650}'), Script::Inherited); // U+0650 ARABIC KASRA
269/// assert_eq!(swe.get_script_val('٠'), Script::Arabic); // // U+0660 ARABIC-INDIC DIGIT ZERO
270/// assert_eq!(swe.get_script_val('ﷲ'), Script::Arabic); // U+FDF2 ARABIC LIGATURE ALLAH ISOLATED FORM
271///
272/// // get the `Script_Extensions` property value
273/// assert_eq!(
274///     swe.get_script_extensions_val('ـ') // U+0640 ARABIC TATWEEL
275///         .iter().collect::<Vec<_>>(),
276///     [Script::Arabic, Script::Syriac, Script::Mandaic, Script::Manichaean,
277///          Script::PsalterPahlavi, Script::Adlam, Script::HanifiRohingya, Script::Sogdian,
278///          Script::OldUyghur]
279/// );
280/// assert_eq!(
281///     swe.get_script_extensions_val('🥳') // U+1F973 FACE WITH PARTY HORN AND PARTY HAT
282///         .iter().collect::<Vec<_>>(),
283///     [Script::Common]
284/// );
285/// assert_eq!(
286///     swe.get_script_extensions_val('\u{200D}') // ZERO WIDTH JOINER
287///         .iter().collect::<Vec<_>>(),
288///     [Script::Inherited]
289/// );
290/// assert_eq!(
291///     swe.get_script_extensions_val('௫') // U+0BEB TAMIL DIGIT FIVE
292///         .iter().collect::<Vec<_>>(),
293///     [Script::Tamil, Script::Grantha]
294/// );
295///
296/// // check containment of a `Script` value in the `Script_Extensions` value
297/// // U+0650 ARABIC KASRA
298/// assert!(!swe.has_script('\u{0650}', Script::Inherited)); // main Script value
299/// assert!(swe.has_script('\u{0650}', Script::Arabic));
300/// assert!(swe.has_script('\u{0650}', Script::Syriac));
301/// assert!(!swe.has_script('\u{0650}', Script::Thaana));
302///
303/// // get a `CodePointInversionList` for when `Script` value is contained in `Script_Extensions` value
304/// let syriac = swe.get_script_extensions_set(Script::Syriac);
305/// assert!(syriac.contains('\u{0650}')); // ARABIC KASRA
306/// assert!(!syriac.contains('٠')); // ARABIC-INDIC DIGIT ZERO
307/// assert!(!syriac.contains('ﷲ')); // ARABIC LIGATURE ALLAH ISOLATED FORM
308/// assert!(syriac.contains('܀')); // SYRIAC END OF PARAGRAPH
309/// assert!(syriac.contains('\u{074A}')); // SYRIAC BARREKH
310/// ```
311#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ScriptWithExtensions {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f,
            "ScriptWithExtensions", "data", &&self.data)
    }
}Debug)]
312pub struct ScriptWithExtensions {
313    data: DataPayload<PropertyScriptWithExtensionsV1>,
314}
315
316/// A borrowed wrapper around script extension data, returned by
317/// [`ScriptWithExtensions::as_borrowed()`]. More efficient to query.
318#[derive(#[automatically_derived]
impl<'a> ::core::clone::Clone for ScriptWithExtensionsBorrowed<'a> {
    #[inline]
    fn clone(&self) -> ScriptWithExtensionsBorrowed<'a> {
        let _:
                ::core::clone::AssertParamIsClone<&'a ScriptWithExtensionsProperty<'a>>;
        *self
    }
}Clone, #[automatically_derived]
impl<'a> ::core::marker::Copy for ScriptWithExtensionsBorrowed<'a> { }Copy, #[automatically_derived]
impl<'a> ::core::fmt::Debug for ScriptWithExtensionsBorrowed<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f,
            "ScriptWithExtensionsBorrowed", "data", &&self.data)
    }
}Debug)]
319pub struct ScriptWithExtensionsBorrowed<'a> {
320    data: &'a ScriptWithExtensionsProperty<'a>,
321}
322
323impl ScriptWithExtensions {
324    /// Creates a new instance of `ScriptWithExtensionsBorrowed` using compiled data.
325    ///
326    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
327    ///
328    /// [📚 Help choosing a constructor](icu_provider::constructors)
329    #[cfg(feature = "compiled_data")]
330    #[expect(clippy::new_ret_no_self)]
331    pub fn new() -> ScriptWithExtensionsBorrowed<'static> {
332        ScriptWithExtensionsBorrowed::new()
333    }
334
335    icu_provider::gen_buffer_data_constructors!(
336        () -> result: Result<ScriptWithExtensions, DataError>,
337        functions: [
338            new: skip,
339            try_new_with_buffer_provider,
340            try_new_unstable,
341            Self,
342        ]
343    );
344
345    #[doc = "A version of [`Self::new`] 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)]
346    pub fn try_new_unstable(
347        provider: &(impl DataProvider<PropertyScriptWithExtensionsV1> + ?Sized),
348    ) -> Result<Self, DataError> {
349        Ok(ScriptWithExtensions::from_data(
350            provider.load(Default::default())?.payload,
351        ))
352    }
353
354    /// Construct a borrowed version of this type that can be queried.
355    ///
356    /// This avoids a potential small underlying cost per API call (ex: `contains()`) by consolidating it
357    /// up front.
358    #[inline]
359    pub fn as_borrowed(&self) -> ScriptWithExtensionsBorrowed<'_> {
360        ScriptWithExtensionsBorrowed {
361            data: self.data.get(),
362        }
363    }
364
365    /// Construct a new one from loaded data
366    ///
367    /// Typically it is preferable to use getters like [`load_script_with_extensions_unstable()`] instead
368    pub(crate) fn from_data(data: DataPayload<PropertyScriptWithExtensionsV1>) -> Self {
369        Self { data }
370    }
371}
372
373impl<'a> ScriptWithExtensionsBorrowed<'a> {
374    /// Returns the `Script` property value for this code point.
375    ///
376    /// # Examples
377    ///
378    /// ```
379    /// use icu::properties::script::ScriptWithExtensions;
380    /// use icu::properties::props::Script;
381    ///
382    /// let swe = ScriptWithExtensions::new();
383    ///
384    /// // U+0640 ARABIC TATWEEL
385    /// assert_eq!(swe.get_script_val('ـ'), Script::Common); // main Script value
386    /// assert_ne!(swe.get_script_val('ـ'), Script::Arabic);
387    /// assert_ne!(swe.get_script_val('ـ'), Script::Syriac);
388    /// assert_ne!(swe.get_script_val('ـ'), Script::Thaana);
389    ///
390    /// // U+0650 ARABIC KASRA
391    /// assert_eq!(swe.get_script_val('\u{0650}'), Script::Inherited); // main Script value
392    /// assert_ne!(swe.get_script_val('\u{0650}'), Script::Arabic);
393    /// assert_ne!(swe.get_script_val('\u{0650}'), Script::Syriac);
394    /// assert_ne!(swe.get_script_val('\u{0650}'), Script::Thaana);
395    ///
396    /// // U+0660 ARABIC-INDIC DIGIT ZERO
397    /// assert_ne!(swe.get_script_val('٠'), Script::Common);
398    /// assert_eq!(swe.get_script_val('٠'), Script::Arabic); // main Script value
399    /// assert_ne!(swe.get_script_val('٠'), Script::Syriac);
400    /// assert_ne!(swe.get_script_val('٠'), Script::Thaana);
401    ///
402    /// // U+FDF2 ARABIC LIGATURE ALLAH ISOLATED FORM
403    /// assert_ne!(swe.get_script_val('ﷲ'), Script::Common);
404    /// assert_eq!(swe.get_script_val('ﷲ'), Script::Arabic); // main Script value
405    /// assert_ne!(swe.get_script_val('ﷲ'), Script::Syriac);
406    /// assert_ne!(swe.get_script_val('ﷲ'), Script::Thaana);
407    /// ```
408    pub fn get_script_val(self, ch: char) -> Script {
409        self.get_script_val32(ch as u32)
410    }
411
412    /// See [`Self::get_script_val`].
413    pub fn get_script_val32(self, code_point: u32) -> Script {
414        let sc_with_ext = self.data.trie.get32(code_point);
415
416        if sc_with_ext.is_other() {
417            let ext_idx = sc_with_ext.0 & SCRIPT_X_SCRIPT_VAL;
418            let scx_val = self.data.extensions.get(ext_idx as usize);
419            let scx_first_sc = scx_val.and_then(|scx| scx.get(0));
420
421            let default_sc_val = Script::Unknown;
422
423            scx_first_sc.unwrap_or(default_sc_val)
424        } else if sc_with_ext.is_common() {
425            Script::Common
426        } else if sc_with_ext.is_inherited() {
427            Script::Inherited
428        } else {
429            let script_val = sc_with_ext.0;
430            Script(script_val)
431        }
432    }
433    // Returns the Script_Extensions value for a code_point when the trie value
434    // is already known.
435    // This private helper method exists to prevent code duplication in callers like
436    // `get_script_extensions_val`, `get_script_extensions_set`, and `has_script`.
437    fn get_scx_val_using_trie_val(
438        self,
439        sc_with_ext_ule: &'a <ScriptWithExt as AsULE>::ULE,
440    ) -> &'a ZeroSlice<Script> {
441        let sc_with_ext = ScriptWithExt::from_unaligned(*sc_with_ext_ule);
442        if sc_with_ext.is_other() {
443            let ext_idx = sc_with_ext.0 & SCRIPT_X_SCRIPT_VAL;
444            let ext_subarray = self.data.extensions.get(ext_idx as usize);
445            // In the OTHER case, where the 2 higher-order bits of the
446            // `ScriptWithExt` value in the trie doesn't indicate the Script value,
447            // the Script value is copied/inserted into the first position of the
448            // `extensions` array. So we must remove it to return the actual scx array val.
449            let scx_slice = ext_subarray
450                .and_then(|zslice| zslice.as_ule_slice().get(1..))
451                .unwrap_or_default();
452            ZeroSlice::from_ule_slice(scx_slice)
453        } else if sc_with_ext.is_common() || sc_with_ext.is_inherited() {
454            let ext_idx = sc_with_ext.0 & SCRIPT_X_SCRIPT_VAL;
455            let scx_val = self.data.extensions.get(ext_idx as usize);
456            scx_val.unwrap_or_default()
457        } else {
458            // Note: `Script` and `ScriptWithExt` are both represented as the same
459            // u16 value when the `ScriptWithExt` has no higher-order bits set.
460            let script_ule_slice = core::slice::from_ref(sc_with_ext_ule);
461            ZeroSlice::from_ule_slice(script_ule_slice)
462        }
463    }
464    /// Return the `Script_Extensions` property value for this code point.
465    ///
466    /// If `code_point` has `Script_Extensions`, then return the Script codes in
467    /// the `Script_Extensions`. In this case, the [`Script`] property value
468    /// (normally `Common` or `Inherited`) is not included in the [`ScriptExtensionsSet`].
469    ///
470    /// If `c` does not have `Script_Extensions`, then the one [`Script`] code is put
471    /// into the [`ScriptExtensionsSet`] and also returned.
472    ///
473    /// If `c` is not a valid code point, then return an empty [`ScriptExtensionsSet`].
474    ///
475    /// # Examples
476    ///
477    /// ```
478    /// use icu::properties::script::ScriptWithExtensions;
479    /// use icu::properties::props::Script;
480    ///
481    /// let swe = ScriptWithExtensions::new();
482    ///
483    /// assert_eq!(
484    ///     swe.get_script_extensions_val('𐓐') // U+104D0 OSAGE CAPITAL LETTER KHA
485    ///         .iter()
486    ///         .collect::<Vec<_>>(),
487    ///     [Script::Osage]
488    /// );
489    /// assert_eq!(
490    ///     swe.get_script_extensions_val('🥳') // U+1F973 FACE WITH PARTY HORN AND PARTY HAT
491    ///         .iter()
492    ///         .collect::<Vec<_>>(),
493    ///     [Script::Common]
494    /// );
495    /// assert_eq!(
496    ///     swe.get_script_extensions_val('\u{200D}') // ZERO WIDTH JOINER
497    ///         .iter()
498    ///         .collect::<Vec<_>>(),
499    ///     [Script::Inherited]
500    /// );
501    /// assert_eq!(
502    ///     swe.get_script_extensions_val('௫') // U+0BEB TAMIL DIGIT FIVE
503    ///         .iter()
504    ///         .collect::<Vec<_>>(),
505    ///     [Script::Tamil, Script::Grantha]
506    /// );
507    /// ```
508    pub fn get_script_extensions_val(self, ch: char) -> ScriptExtensionsSet<'a> {
509        self.get_script_extensions_val32(ch as u32)
510    }
511
512    /// See [`Self::get_script_extensions_val`].
513    pub fn get_script_extensions_val32(self, code_point: u32) -> ScriptExtensionsSet<'a> {
514        let sc_with_ext_ule = self.data.trie.get32_ule(code_point);
515
516        ScriptExtensionsSet {
517            values: match sc_with_ext_ule {
518                Some(ule_ref) => self.get_scx_val_using_trie_val(ule_ref),
519                None => ZeroSlice::from_ule_slice(&[]),
520            },
521        }
522    }
523
524    /// Returns whether `script` is contained in the `Script_Extensions`
525    /// property value if the `code_point` has `Script_Extensions`, otherwise
526    /// if the code point does not have `Script_Extensions` then returns
527    /// whether the Script property value matches.
528    ///
529    /// Some characters are commonly used in multiple scripts. For more information,
530    /// see UAX #24: <https://www.unicode.org/reports/tr24/>.
531    ///
532    /// # Examples
533    ///
534    /// ```
535    /// use icu::properties::script::ScriptWithExtensions;
536    /// use icu::properties::props::Script;
537    ///
538    /// let swe = ScriptWithExtensions::new();
539    ///
540    /// // U+0650 ARABIC KASRA
541    /// assert!(!swe.has_script('\u{0650}', Script::Inherited)); // main Script value
542    /// assert!(swe.has_script('\u{0650}', Script::Arabic));
543    /// assert!(swe.has_script('\u{0650}', Script::Syriac));
544    /// assert!(!swe.has_script('\u{0650}', Script::Thaana));
545    ///
546    /// // U+0660 ARABIC-INDIC DIGIT ZERO
547    /// assert!(!swe.has_script('٠', Script::Common)); // main Script value
548    /// assert!(swe.has_script('٠', Script::Arabic));
549    /// assert!(!swe.has_script('٠', Script::Syriac));
550    /// assert!(swe.has_script('٠', Script::Thaana));
551    ///
552    /// // U+FDF2 ARABIC LIGATURE ALLAH ISOLATED FORM
553    /// assert!(!swe.has_script('ﷲ', Script::Common));
554    /// assert!(swe.has_script('ﷲ', Script::Arabic)); // main Script value
555    /// assert!(!swe.has_script('ﷲ', Script::Syriac));
556    /// assert!(swe.has_script('ﷲ', Script::Thaana));
557    /// ```
558    pub fn has_script(self, ch: char, script: Script) -> bool {
559        self.has_script32(ch as u32, script)
560    }
561
562    /// See [`Self::has_script`].
563    pub fn has_script32(self, code_point: u32, script: Script) -> bool {
564        let sc_with_ext_ule = if let Some(scwe_ule) = self.data.trie.get32_ule(code_point) {
565            scwe_ule
566        } else {
567            return false;
568        };
569        let sc_with_ext = <ScriptWithExt as AsULE>::from_unaligned(*sc_with_ext_ule);
570
571        if !sc_with_ext.has_extensions() {
572            let script_val = sc_with_ext.0;
573            script == Script(script_val)
574        } else {
575            let scx_val = self.get_scx_val_using_trie_val(sc_with_ext_ule);
576            let script_find = scx_val.iter().find(|&sc| sc == script);
577            script_find.is_some()
578        }
579    }
580
581    /// Returns all of the matching `CodePointMapRange`s for the given [`Script`]
582    /// in which `has_script` will return true for all of the contained code points.
583    ///
584    /// # Examples
585    ///
586    /// ```
587    /// use icu::properties::props::Script;
588    /// use icu::properties::script::ScriptWithExtensions;
589    ///
590    /// let swe = ScriptWithExtensions::new();
591    ///
592    /// let syriac_script_extensions_ranges =
593    ///     swe.get_script_extensions_ranges(Script::Syriac);
594    ///
595    /// let exp_ranges = [
596    ///     0x0303..=0x0304, // COMBINING TILDE..COMBINING MACRON
597    ///     0x0307..=0x0308, // COMBINING DOT ABOVE..COMBINING DIAERESIS
598    ///     0x030A..=0x030A, // COMBINING RING ABOVE
599    ///     0x0323..=0x0325, // COMBINING DOT BELOW..COMBINING RING BELOW
600    ///     0x032D..=0x032E, // COMBINING CIRCUMFLEX ACCENT BELOW..COMBINING BREVE BELOW
601    ///     0x0330..=0x0331, // COMBINING TILDE BELOW..COMBINING MACRON BELOW
602    ///     0x060C..=0x060C, // ARABIC COMMA
603    ///     0x061B..=0x061C, // ARABIC SEMICOLON, ARABIC LETTER MARK
604    ///     0x061F..=0x061F, // ARABIC QUESTION MARK
605    ///     0x0640..=0x0640, // ARABIC TATWEEL
606    ///     0x064B..=0x0655, // ARABIC FATHATAN..ARABIC HAMZA BELOW
607    ///     0x0670..=0x0670, // ARABIC LETTER SUPERSCRIPT ALEF
608    ///     0x0700..=0x070D, // Syriac block begins at U+0700
609    ///     0x070F..=0x074A, // Syriac block
610    ///     0x074D..=0x074F, // Syriac block ends at U+074F
611    ///     0x0860..=0x086A, // Syriac Supplement block is U+0860..=U+086F
612    ///     0x1DF8..=0x1DF8, // COMBINING DOT ABOVE LEFT
613    ///     0x1DFA..=0x1DFA, // COMBINING DOT BELOW LEFT
614    /// ];
615    ///
616    /// assert_eq!(
617    ///     syriac_script_extensions_ranges.collect::<Vec<_>>(),
618    ///     exp_ranges
619    /// );
620    /// ```
621    pub fn get_script_extensions_ranges(
622        self,
623        script: Script,
624    ) -> impl Iterator<Item = RangeInclusive<u32>> + 'a {
625        self.data
626            .trie
627            .iter_ranges_mapped(move |value| {
628                let sc_with_ext = ScriptWithExt(value.0);
629                if sc_with_ext.has_extensions() {
630                    self.get_scx_val_using_trie_val(&sc_with_ext.to_unaligned())
631                        .iter()
632                        .any(|sc| sc == script)
633                } else {
634                    script == sc_with_ext.into()
635                }
636            })
637            .filter(|v| v.value)
638            .map(|v| v.range)
639    }
640
641    /// Returns a [`CodePointInversionList`] for the given [`Script`] which represents all
642    /// code points for which `has_script` will return true.
643    ///
644    /// ✨ *Enabled with the `alloc` Cargo feature.*
645    ///
646    /// # Examples
647    ///
648    /// ```
649    /// use icu::properties::script::ScriptWithExtensions;
650    /// use icu::properties::props::Script;
651    ///
652    /// let swe = ScriptWithExtensions::new();
653    ///
654    /// let syriac = swe.get_script_extensions_set(Script::Syriac);
655    ///
656    /// assert!(!syriac.contains('؞')); // ARABIC TRIPLE DOT PUNCTUATION MARK
657    /// assert!(syriac.contains('؟')); // ARABIC QUESTION MARK
658    /// assert!(!syriac.contains('ؠ')); // ARABIC LETTER KASHMIRI YEH
659    ///
660    /// assert!(syriac.contains('܀')); // SYRIAC END OF PARAGRAPH
661    /// assert!(syriac.contains('\u{074A}')); // SYRIAC BARREKH
662    /// assert!(!syriac.contains('\u{074B}')); // unassigned
663    /// assert!(syriac.contains('ݏ')); // SYRIAC LETTER SOGDIAN FE
664    /// assert!(!syriac.contains('ݐ')); // ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW
665    ///
666    /// assert!(syriac.contains('\u{1DF8}')); // COMBINING DOT ABOVE LEFT
667    /// assert!(!syriac.contains('\u{1DF9}')); // COMBINING WIDE INVERTED BRIDGE BELOW
668    /// assert!(syriac.contains('\u{1DFA}')); // COMBINING DOT BELOW LEFT
669    /// assert!(!syriac.contains('\u{1DFB}')); // COMBINING DELETION MARK
670    /// ```
671    #[cfg(feature = "alloc")]
672    pub fn get_script_extensions_set(self, script: Script) -> CodePointInversionList<'a> {
673        CodePointInversionList::from_iter(self.get_script_extensions_ranges(script))
674    }
675}
676
677#[cfg(feature = "compiled_data")]
678impl Default for ScriptWithExtensionsBorrowed<'static> {
679    fn default() -> Self {
680        Self::new()
681    }
682}
683
684impl ScriptWithExtensionsBorrowed<'static> {
685    /// Creates a new instance of `ScriptWithExtensionsBorrowed` using compiled data.
686    ///
687    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
688    ///
689    /// [📚 Help choosing a constructor](icu_provider::constructors)
690    #[cfg(feature = "compiled_data")]
691    pub fn new() -> Self {
692        Self {
693            data: Baked::SINGLETON_PROPERTY_SCRIPT_WITH_EXTENSIONS_V1,
694        }
695    }
696
697    /// Cheaply converts a [`ScriptWithExtensionsBorrowed<'static>`] into a [`ScriptWithExtensions`].
698    ///
699    /// Note: Due to branching and indirection, using [`ScriptWithExtensions`] might inhibit some
700    /// compile-time optimizations that are possible with [`ScriptWithExtensionsBorrowed`].
701    pub const fn static_to_owned(self) -> ScriptWithExtensions {
702        ScriptWithExtensions {
703            data: DataPayload::from_static_ref(self.data),
704        }
705    }
706}
707
708#[cfg(test)]
709mod tests {
710    use super::*;
711    #[test]
712    /// Regression test for <https://github.com/unicode-org/icu4x/issues/6041>
713    fn test_scx_regression_6041() {
714        let scripts = ScriptWithExtensions::new()
715            .get_script_extensions_val('\u{2bc}')
716            .iter()
717            .collect::<Vec<_>>();
718        assert_eq!(
719            scripts,
720            [
721                Script::Bengali,
722                Script::Cyrillic,
723                Script::Devanagari,
724                Script::Latin,
725                Script::Thai,
726                Script::Lisu,
727                Script::Toto
728            ]
729        );
730    }
731
732    #[test]
733    fn test_high_discriminant() {
734        let swe = ScriptWithExtensions::new();
735        assert!(!swe.has_script32(0x0640, Script(0xAFFE)));
736    }
737}