use std::iter::Sum;
use std::ops::{Add, AddAssign, Range, Sub, SubAssign};
use malloc_size_of_derive::MallocSizeOf;
pub use crate::unicode_block::{UnicodeBlock, UnicodeBlockMethod};
pub fn is_bidi_control(c: char) -> bool {
matches!(c, '\u{202A}'..='\u{202E}' | '\u{2066}'..='\u{2069}' | '\u{200E}' | '\u{200F}' | '\u{061C}')
}
pub fn unicode_plane(codepoint: char) -> u32 {
(codepoint as u32) >> 16
}
pub fn is_cjk(codepoint: char) -> bool {
if let Some(
UnicodeBlock::CJKRadicalsSupplement |
UnicodeBlock::KangxiRadicals |
UnicodeBlock::IdeographicDescriptionCharacters |
UnicodeBlock::CJKSymbolsandPunctuation |
UnicodeBlock::Hiragana |
UnicodeBlock::Katakana |
UnicodeBlock::Bopomofo |
UnicodeBlock::HangulCompatibilityJamo |
UnicodeBlock::Kanbun |
UnicodeBlock::BopomofoExtended |
UnicodeBlock::CJKStrokes |
UnicodeBlock::KatakanaPhoneticExtensions |
UnicodeBlock::EnclosedCJKLettersandMonths |
UnicodeBlock::CJKCompatibility |
UnicodeBlock::CJKUnifiedIdeographsExtensionA |
UnicodeBlock::YijingHexagramSymbols |
UnicodeBlock::CJKUnifiedIdeographs |
UnicodeBlock::CJKCompatibilityIdeographs |
UnicodeBlock::CJKCompatibilityForms |
UnicodeBlock::HalfwidthandFullwidthForms,
) = codepoint.block()
{
return true;
}
unicode_plane(codepoint) == 2 || unicode_plane(codepoint) == 3
}
#[derive(Clone, Copy)]
pub struct RangeAny<T> {
pub start: Option<T>,
pub end: Option<T>,
}
impl<T> RangeAny<T> {
pub fn map<U>(self, f: impl Fn(T) -> U + Copy) -> RangeAny<U> {
let Self { start, end } = self;
RangeAny {
start: start.map(f),
end: end.map(f),
}
}
pub fn intersect(self, other: Self) -> Option<Self>
where
T: Ord,
{
let start = match (self.start, other.start) {
(None, None) => None,
(None, Some(b)) => Some(b),
(Some(a), None) => Some(a),
(Some(a), Some(b)) => Some(a.max(b)),
};
let end = match (self.end, other.end) {
(None, None) => None,
(None, Some(b)) => Some(b),
(Some(a), None) => Some(a),
(Some(a), Some(b)) => Some(a.min(b)),
};
if start
.as_ref()
.is_none_or(|start| end.as_ref().is_none_or(|end| start < end))
{
Some(RangeAny { start, end })
} else {
None
}
}
}
impl<T> From<Range<T>> for RangeAny<T> {
fn from(value: Range<T>) -> Self {
Self {
start: Some(value.start),
end: Some(value.end),
}
}
}
macro_rules! unicode_length_type {
($( #[$doc:meta] )+ $type_name:ident) => {
$( #[$doc] )+
#[derive(Clone, Copy, Debug, Default, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd)]
pub struct $type_name(pub usize);
impl $type_name {
pub fn zero() -> Self {
Self(0)
}
pub fn one() -> Self {
Self(1)
}
pub fn saturating_sub(self, value: Self) -> Self {
Self(self.0.saturating_sub(value.0))
}
}
impl From<u32> for $type_name {
fn from(value: u32) -> Self {
Self(value as usize)
}
}
impl From<isize> for $type_name {
fn from(value: isize) -> Self {
Self(value as usize)
}
}
impl Add for $type_name {
type Output = Self;
fn add(self, other: Self) -> Self {
Self(self.0 + other.0)
}
}
impl AddAssign for $type_name {
fn add_assign(&mut self, other: Self) {
*self = Self(self.0 + other.0)
}
}
impl Sub for $type_name {
type Output = Self;
fn sub(self, value: Self) -> Self {
Self(self.0 - value.0)
}
}
impl SubAssign for $type_name {
fn sub_assign(&mut self, other: Self) {
*self = Self(self.0 - other.0)
}
}
impl Sum for $type_name {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self::zero(), |a, b| Self(a.0 + b.0))
}
}
};
}
unicode_length_type! {
Utf8CodeUnits
}
unicode_length_type! {
Utf16CodeUnits
}
unicode_length_type! {
Utf32CodeUnits
}
unicode_length_type! {
Utf32CodeUnitsOrNodeOffset
}
impl Utf16CodeUnits {
pub fn length_of(string: &str) -> Self {
Self(string.bytes().map(len_utf16_for_utf8_byte).sum())
}
pub fn to_utf32_code_units_in(self, string: &str) -> Utf32CodeUnits {
let mut current_utf16_offset = Utf16CodeUnits(0);
let mut current_utf32_offset = Utf32CodeUnits(0);
for utf8_byte in string.bytes() {
if current_utf16_offset >= self {
break;
}
increment_offsets_for_utf8_byte(
utf8_byte,
&mut current_utf16_offset,
&mut current_utf32_offset,
);
}
current_utf32_offset
}
}
fn len_utf16_for_utf8_byte(byte: u8) -> usize {
if byte < 0b1000_0000 {
1
} else if byte < 0b1100_0000 {
0
} else if byte < 0b1111_0000 {
1
} else {
2
}
}
fn increment_offsets_for_utf8_byte(
utf8_byte: u8,
utf16_offset: &mut Utf16CodeUnits,
utf32_offset: &mut Utf32CodeUnits,
) {
let len_utf16 = len_utf16_for_utf8_byte(utf8_byte);
utf16_offset.0 += len_utf16;
utf32_offset.0 += (len_utf16 != 0) as usize;
}
impl Utf32CodeUnits {
pub fn length_of(string: &str) -> Self {
Self(string.chars().count())
}
pub fn to_utf8_code_units_in(self, string: &str) -> Utf8CodeUnits {
let mut current_utf32_offset = Utf32CodeUnits(0);
for (current_utf8_offset, utf8_byte) in string.bytes().enumerate() {
if (utf8_byte & 0b1100_0000) == 0b1000_0000 {
continue;
}
if current_utf32_offset >= self {
return Utf8CodeUnits(current_utf8_offset);
}
current_utf32_offset.0 += 1;
}
Utf8CodeUnits(string.len())
}
pub fn to_utf16_code_units_in(self, string: &str) -> Utf16CodeUnits {
let mut current_utf32_offset = Utf32CodeUnits(0);
let mut current_utf16_offset = Utf16CodeUnits(0);
for utf8_byte in string.bytes() {
if current_utf32_offset >= self {
break;
}
increment_offsets_for_utf8_byte(
utf8_byte,
&mut current_utf16_offset,
&mut current_utf32_offset,
);
}
current_utf16_offset
}
}
impl Utf32CodeUnitsOrNodeOffset {
pub fn to_utf16_code_units_in(self, string: &str) -> Utf16CodeUnits {
Utf32CodeUnits(self.0).to_utf16_code_units_in(string)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_is_cjk() {
assert_eq!(is_cjk('〇'), true);
assert_eq!(is_cjk('㐀'), true);
assert_eq!(is_cjk('あ'), true);
assert_eq!(is_cjk('ア'), true);
assert_eq!(is_cjk('㆒'), true);
assert_eq!(is_cjk('ㆣ'), true);
assert_eq!(is_cjk('龥'), true);
assert_eq!(is_cjk('𰾑'), true);
assert_eq!(is_cjk('𰻝'), true);
assert_eq!(is_cjk('a'), false);
assert_eq!(is_cjk('🙂'), false);
assert_eq!(is_cjk('©'), false);
}
#[test]
fn test_utf16_length() {
assert_eq!(Utf16CodeUnits::length_of(""), Utf16CodeUnits(0));
assert_eq!(Utf16CodeUnits::length_of("a"), Utf16CodeUnits(1));
assert_eq!(Utf16CodeUnits::length_of("é"), Utf16CodeUnits(1));
assert_eq!(Utf16CodeUnits::length_of("字"), Utf16CodeUnits(1));
assert_eq!(Utf16CodeUnits::length_of("\u{1F4A9}"), Utf16CodeUnits(2));
assert_eq!(
Utf16CodeUnits::length_of("\u{1F4A9}字éa"),
Utf16CodeUnits(5)
);
}
#[test]
fn test_utf16_to_utf32() {
let s = "aé字\u{1F4A9}";
assert_eq!(
Utf16CodeUnits(0).to_utf32_code_units_in(s),
Utf32CodeUnits(0)
);
assert_eq!(
Utf16CodeUnits(1).to_utf32_code_units_in(s),
Utf32CodeUnits(1)
);
assert_eq!(
Utf16CodeUnits(2).to_utf32_code_units_in(s),
Utf32CodeUnits(2)
);
assert_eq!(
Utf16CodeUnits(3).to_utf32_code_units_in(s),
Utf32CodeUnits(3)
);
assert_eq!(
Utf16CodeUnits(4).to_utf32_code_units_in(s),
Utf32CodeUnits(4)
);
assert_eq!(
Utf16CodeUnits(5).to_utf32_code_units_in(s),
Utf32CodeUnits(4)
);
assert_eq!(
Utf16CodeUnits(6).to_utf32_code_units_in(s),
Utf32CodeUnits(4)
);
assert_eq!(
Utf16CodeUnits(7).to_utf32_code_units_in(s),
Utf32CodeUnits(4)
);
}
#[test]
fn test_utf32_to_utf16() {
let string = "aé字\u{1F4A9}";
assert_eq!(
Utf32CodeUnits(0).to_utf16_code_units_in(string),
Utf16CodeUnits(0),
);
assert_eq!(
Utf32CodeUnits(1).to_utf16_code_units_in(string),
Utf16CodeUnits(1),
);
assert_eq!(
Utf32CodeUnits(2).to_utf16_code_units_in(string),
Utf16CodeUnits(2),
);
assert_eq!(
Utf32CodeUnits(3).to_utf16_code_units_in(string),
Utf16CodeUnits(3),
);
assert_eq!(
Utf32CodeUnits(4).to_utf16_code_units_in(string),
Utf16CodeUnits(5),
);
assert_eq!(
Utf32CodeUnits(6).to_utf16_code_units_in(string),
Utf16CodeUnits(5),
);
assert_eq!(
Utf32CodeUnits(1000).to_utf16_code_units_in(string),
Utf16CodeUnits(5),
);
}
}