use crate::props::Script;
use crate::provider::*;
#[cfg(feature = "alloc")]
use core::iter::FromIterator;
use core::ops::RangeInclusive;
#[cfg(feature = "alloc")]
use icu_collections::codepointinvlist::CodePointInversionList;
use icu_provider::prelude::*;
use zerovec::{ule::AsULE, ZeroSlice};
#[cfg(feature = "harfbuzz_traits")]
pub use crate::harfbuzz::{HarfbuzzScriptData, HarfbuzzScriptDataBorrowed};
const SCRIPT_VAL_LENGTH: u16 = 10;
const SCRIPT_X_SCRIPT_VAL: u16 = (1 << SCRIPT_VAL_LENGTH) - 1;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "datagen", derive(databake::Bake))]
#[cfg_attr(feature = "datagen", databake(path = icu_properties::script))]
#[repr(transparent)]
#[doc(hidden)]
#[allow(clippy::exhaustive_structs)] pub struct ScriptWithExt(pub u16);
#[allow(missing_docs)] #[allow(non_upper_case_globals)]
#[doc(hidden)] impl ScriptWithExt {
pub const Unknown: ScriptWithExt = ScriptWithExt(0);
}
impl AsULE for ScriptWithExt {
type ULE = <u16 as AsULE>::ULE;
#[inline]
fn to_unaligned(self) -> Self::ULE {
Script(self.0).to_unaligned()
}
#[inline]
fn from_unaligned(unaligned: Self::ULE) -> Self {
ScriptWithExt(Script::from_unaligned(unaligned).0)
}
}
#[doc(hidden)] impl ScriptWithExt {
pub fn is_common(&self) -> bool {
self.0 >> SCRIPT_VAL_LENGTH == 1
}
pub fn is_inherited(&self) -> bool {
self.0 >> SCRIPT_VAL_LENGTH == 2
}
pub fn is_other(&self) -> bool {
self.0 >> SCRIPT_VAL_LENGTH == 3
}
pub fn has_extensions(&self) -> bool {
let high_order_bits = self.0 >> SCRIPT_VAL_LENGTH;
high_order_bits > 0
}
}
impl From<ScriptWithExt> for u32 {
fn from(swe: ScriptWithExt) -> Self {
swe.0 as u32
}
}
impl From<ScriptWithExt> for Script {
fn from(swe: ScriptWithExt) -> Self {
Script(swe.0)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct ScriptExtensionsSet<'a> {
values: &'a ZeroSlice<Script>,
}
impl<'a> ScriptExtensionsSet<'a> {
pub fn contains(&self, x: &Script) -> bool {
ZeroSlice::binary_search(self.values, x).is_ok()
}
pub fn iter(&self) -> impl DoubleEndedIterator<Item = Script> + 'a {
ZeroSlice::iter(self.values)
}
#[doc(hidden)] pub fn array_len(&self) -> usize {
self.values.len()
}
#[doc(hidden)] pub fn array_get(&self, index: usize) -> Option<Script> {
self.values.get(index)
}
}
#[derive(Debug)]
pub struct ScriptWithExtensions {
data: DataPayload<PropertyScriptWithExtensionsV1>,
}
#[derive(Clone, Copy, Debug)]
pub struct ScriptWithExtensionsBorrowed<'a> {
data: &'a ScriptWithExtensionsProperty<'a>,
}
impl ScriptWithExtensions {
#[cfg(feature = "compiled_data")]
#[expect(clippy::new_ret_no_self)]
pub fn new() -> ScriptWithExtensionsBorrowed<'static> {
ScriptWithExtensionsBorrowed::new()
}
icu_provider::gen_buffer_data_constructors!(
() -> result: Result<ScriptWithExtensions, DataError>,
functions: [
new: skip,
try_new_with_buffer_provider,
try_new_unstable,
Self,
]
);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
pub fn try_new_unstable(
provider: &(impl DataProvider<PropertyScriptWithExtensionsV1> + ?Sized),
) -> Result<Self, DataError> {
Ok(ScriptWithExtensions::from_data(
provider.load(Default::default())?.payload,
))
}
#[inline]
pub fn as_borrowed(&self) -> ScriptWithExtensionsBorrowed<'_> {
ScriptWithExtensionsBorrowed {
data: self.data.get(),
}
}
pub(crate) fn from_data(data: DataPayload<PropertyScriptWithExtensionsV1>) -> Self {
Self { data }
}
}
impl<'a> ScriptWithExtensionsBorrowed<'a> {
pub fn get_script_val(self, ch: char) -> Script {
self.get_script_val32(ch as u32)
}
pub fn get_script_val32(self, code_point: u32) -> Script {
let sc_with_ext = self.data.trie.get32(code_point);
if sc_with_ext.is_other() {
let ext_idx = sc_with_ext.0 & SCRIPT_X_SCRIPT_VAL;
let scx_val = self.data.extensions.get(ext_idx as usize);
let scx_first_sc = scx_val.and_then(|scx| scx.get(0));
let default_sc_val = Script::Unknown;
scx_first_sc.unwrap_or(default_sc_val)
} else if sc_with_ext.is_common() {
Script::Common
} else if sc_with_ext.is_inherited() {
Script::Inherited
} else {
let script_val = sc_with_ext.0;
Script(script_val)
}
}
fn get_scx_val_using_trie_val(
self,
sc_with_ext_ule: &'a <ScriptWithExt as AsULE>::ULE,
) -> &'a ZeroSlice<Script> {
let sc_with_ext = ScriptWithExt::from_unaligned(*sc_with_ext_ule);
if sc_with_ext.is_other() {
let ext_idx = sc_with_ext.0 & SCRIPT_X_SCRIPT_VAL;
let ext_subarray = self.data.extensions.get(ext_idx as usize);
let scx_slice = ext_subarray
.and_then(|zslice| zslice.as_ule_slice().get(1..))
.unwrap_or_default();
ZeroSlice::from_ule_slice(scx_slice)
} else if sc_with_ext.is_common() || sc_with_ext.is_inherited() {
let ext_idx = sc_with_ext.0 & SCRIPT_X_SCRIPT_VAL;
let scx_val = self.data.extensions.get(ext_idx as usize);
scx_val.unwrap_or_default()
} else {
let script_ule_slice = core::slice::from_ref(sc_with_ext_ule);
ZeroSlice::from_ule_slice(script_ule_slice)
}
}
pub fn get_script_extensions_val(self, ch: char) -> ScriptExtensionsSet<'a> {
self.get_script_extensions_val32(ch as u32)
}
pub fn get_script_extensions_val32(self, code_point: u32) -> ScriptExtensionsSet<'a> {
let sc_with_ext_ule = self.data.trie.get32_ule(code_point);
ScriptExtensionsSet {
values: match sc_with_ext_ule {
Some(ule_ref) => self.get_scx_val_using_trie_val(ule_ref),
None => ZeroSlice::from_ule_slice(&[]),
},
}
}
pub fn has_script(self, ch: char, script: Script) -> bool {
self.has_script32(ch as u32, script)
}
pub fn has_script32(self, code_point: u32, script: Script) -> bool {
let sc_with_ext_ule = if let Some(scwe_ule) = self.data.trie.get32_ule(code_point) {
scwe_ule
} else {
return false;
};
let sc_with_ext = <ScriptWithExt as AsULE>::from_unaligned(*sc_with_ext_ule);
if !sc_with_ext.has_extensions() {
let script_val = sc_with_ext.0;
script == Script(script_val)
} else {
let scx_val = self.get_scx_val_using_trie_val(sc_with_ext_ule);
let script_find = scx_val.iter().find(|&sc| sc == script);
script_find.is_some()
}
}
pub fn get_script_extensions_ranges(
self,
script: Script,
) -> impl Iterator<Item = RangeInclusive<u32>> + 'a {
self.data
.trie
.iter_ranges_mapped(move |value| {
let sc_with_ext = ScriptWithExt(value.0);
if sc_with_ext.has_extensions() {
self.get_scx_val_using_trie_val(&sc_with_ext.to_unaligned())
.iter()
.any(|sc| sc == script)
} else {
script == sc_with_ext.into()
}
})
.filter(|v| v.value)
.map(|v| v.range)
}
#[cfg(feature = "alloc")]
pub fn get_script_extensions_set(self, script: Script) -> CodePointInversionList<'a> {
CodePointInversionList::from_iter(self.get_script_extensions_ranges(script))
}
}
#[cfg(feature = "compiled_data")]
impl Default for ScriptWithExtensionsBorrowed<'static> {
fn default() -> Self {
Self::new()
}
}
impl ScriptWithExtensionsBorrowed<'static> {
#[cfg(feature = "compiled_data")]
pub fn new() -> Self {
Self {
data: Baked::SINGLETON_PROPERTY_SCRIPT_WITH_EXTENSIONS_V1,
}
}
pub const fn static_to_owned(self) -> ScriptWithExtensions {
ScriptWithExtensions {
data: DataPayload::from_static_ref(self.data),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scx_regression_6041() {
let scripts = ScriptWithExtensions::new()
.get_script_extensions_val('\u{2bc}')
.iter()
.collect::<Vec<_>>();
assert_eq!(
scripts,
[
Script::Bengali,
Script::Cyrillic,
Script::Devanagari,
Script::Latin,
Script::Thai,
Script::Lisu,
Script::Toto
]
);
}
}