use std::{
fmt::{self, Write},
iter::FusedIterator,
};
#[cfg(feature = "serde")]
use serde::Serialize;
use crate::symbol;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct SuperChar(u32);
const VS_MASK: u32 = 0xF000_0000;
const CHAR_MASK: u32 = 0x001F_FFFF;
const SOLIDUS_BIT: u32 = 0x0800_0000;
const VERTICAL_LINE_BIT: u32 = 0x0400_0000;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum VariationSelector {
Vs1 = 1,
Vs2 = 2,
Vs3 = 3,
Vs4 = 4,
Vs5 = 5,
Vs6 = 6,
Vs7 = 7,
Vs8 = 8,
Vs9 = 9,
Vs10 = 10,
Vs11 = 11,
Vs12 = 12,
Vs13 = 13,
Vs14 = 14,
Vs15 = 15,
}
impl From<VariationSelector> for char {
#[inline]
fn from(vs: VariationSelector) -> Self {
char::from_u32(0xFDFF + vs as u32).unwrap()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum OverlayChar {
Solidus,
VerticalLine,
}
impl From<OverlayChar> for char {
fn from(oc: OverlayChar) -> Self {
match oc {
OverlayChar::Solidus => symbol::COMBINING_LONG_SOLIDUS_OVERLAY,
OverlayChar::VerticalLine => symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY,
}
}
}
impl SuperChar {
pub const MAX_LEN_UTF8: usize = 12;
#[must_use]
#[inline]
pub const fn from_char(c: char) -> Self {
Self(c as u32)
}
#[must_use]
#[inline]
pub const fn from_char_with_vs(c: char, vs: VariationSelector) -> Self {
debug_assert!(!is_precomposed_solidus_overlay_for_debug(c));
Self(c as u32 | ((vs as u32) << 28))
}
#[must_use]
#[inline]
pub fn chars(self) -> SuperCharChars {
SuperCharChars(self.0)
}
#[must_use]
#[inline]
pub const fn has_vs(self) -> bool {
self.0 & VS_MASK != 0
}
#[must_use]
#[inline]
pub const fn vs(self) -> Option<VariationSelector> {
use VariationSelector::*;
match self.0 >> 28 {
0 => None,
1 => Some(Vs1),
2 => Some(Vs2),
3 => Some(Vs3),
4 => Some(Vs4),
5 => Some(Vs5),
6 => Some(Vs6),
7 => Some(Vs7),
8 => Some(Vs8),
9 => Some(Vs9),
10 => Some(Vs10),
11 => Some(Vs11),
12 => Some(Vs12),
13 => Some(Vs13),
14 => Some(Vs14),
15 => Some(Vs15),
_ => unreachable!(),
}
}
#[inline]
#[must_use]
pub fn with_overlay(self, overlay: OverlayChar) -> Self {
match overlay {
OverlayChar::Solidus => {
if !self.has_vs()
&& let Some(precomposed) = get_precomposed_solidus_overlay(self.base_char())
{
Self(self.0 & !CHAR_MASK | precomposed as u32)
} else {
Self(self.0 | SOLIDUS_BIT)
}
}
OverlayChar::VerticalLine => Self(self.0 | VERTICAL_LINE_BIT),
}
}
#[must_use]
#[inline]
pub const fn base_char(self) -> char {
unsafe { char::from_u32_unchecked(self.0 & CHAR_MASK) }
}
#[must_use]
#[inline]
pub const fn try_as_char(self) -> Option<char> {
if self.0 & CHAR_MASK == self.0 {
Some(self.base_char())
} else {
None
}
}
pub fn encode_utf8(self, dst: &mut [u8]) -> &mut str {
let mut idx: usize = 0;
for c in self.chars() {
let result = c.encode_utf8(&mut dst[idx..]);
idx += result.len();
}
unsafe { str::from_utf8_unchecked_mut(&mut dst[..idx]) }
}
}
impl From<char> for SuperChar {
#[inline]
fn from(c: char) -> Self {
Self::from_char(c)
}
}
impl fmt::Debug for SuperChar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"")?;
for c in self.chars() {
write!(f, "{}", c.escape_debug())?;
}
write!(f, "\"")?;
Ok(())
}
}
impl fmt::Display for SuperChar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for c in self.chars() {
f.write_char(c)?;
}
Ok(())
}
}
#[cfg(feature = "serde")]
impl Serialize for SuperChar {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serde::Serializer::collect_str(serializer, self)
}
}
#[derive(Clone, Debug)]
#[repr(transparent)]
pub struct SuperCharChars(u32);
impl Iterator for SuperCharChars {
type Item = char;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let base_char = self.0 & CHAR_MASK;
if base_char != CHAR_MASK {
self.0 |= CHAR_MASK;
Some(unsafe { char::from_u32_unchecked(base_char) })
} else if let Some(vs) = SuperChar(self.0).vs() {
self.0 &= !VS_MASK;
Some(vs.into())
} else if self.0 & SOLIDUS_BIT != 0 {
self.0 &= !SOLIDUS_BIT;
Some(symbol::COMBINING_LONG_SOLIDUS_OVERLAY)
} else if self.0 & VERTICAL_LINE_BIT != 0 {
self.0 &= !VERTICAL_LINE_BIT;
Some(symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY)
} else {
None
}
}
}
impl FusedIterator for SuperCharChars {}
#[inline]
fn get_precomposed_solidus_overlay(c: char) -> Option<char> {
if let Ok(bmp) = u16::try_from(c) {
PRECOMPOSED_SOLIDUS_OVERLAY
.0
.binary_search_by_key(&bmp, |&[from, _]| from)
.ok()
.map(|idx| unsafe {
char::from_u32_unchecked(PRECOMPOSED_SOLIDUS_OVERLAY.0[idx][1].into())
})
} else {
None
}
}
const fn is_precomposed_solidus_overlay_for_debug(c: char) -> bool {
let cp = c as u32;
if cp > u16::MAX as u32 {
return false;
}
let cp = cp as u16;
let mut i: usize = 0;
while i < PRECOMPOSED_SOLIDUS_OVERLAY.0.len() {
if cp == PRECOMPOSED_SOLIDUS_OVERLAY.0[i][1] {
return true;
}
i += 1;
}
false
}
#[repr(align(128))] struct Align128<T>(T);
const PRECOMPOSED_SOLIDUS_OVERLAY: Align128<[[u16; 2]; 88]> = Align128([
[0x003C, 0x226E], [0x003D, 0x2260], [0x003E, 0x226F], [0x2190, 0x219A], [0x2192, 0x219B], [0x2194, 0x21AE], [0x219A, 0x219A], [0x219B, 0x219B], [0x21AE, 0x21AE], [0x21CD, 0x21CD], [0x21CE, 0x21CE], [0x21CF, 0x21CF], [0x21D0, 0x21CD], [0x21D2, 0x21CF], [0x21D4, 0x21CE], [0x2203, 0x2204], [0x2204, 0x2204], [0x2208, 0x2209], [0x2209, 0x2209], [0x220B, 0x220C], [0x220C, 0x220C], [0x2223, 0x2224], [0x2224, 0x2224], [0x2225, 0x2226], [0x2226, 0x2226], [0x223C, 0x2241], [0x2241, 0x2241], [0x2243, 0x2244], [0x2244, 0x2244], [0x2245, 0x2247], [0x2247, 0x2247], [0x2248, 0x2249], [0x2249, 0x2249], [0x224D, 0x226D], [0x2260, 0x2260], [0x2261, 0x2262], [0x2262, 0x2262], [0x2264, 0x2270], [0x2265, 0x2271], [0x226D, 0x226D], [0x226E, 0x226E], [0x226F, 0x226F], [0x2270, 0x2270], [0x2271, 0x2271], [0x2272, 0x2274], [0x2273, 0x2275], [0x2274, 0x2274], [0x2275, 0x2275], [0x2276, 0x2278], [0x2277, 0x2279], [0x2278, 0x2278], [0x2279, 0x2279], [0x227A, 0x2280], [0x227B, 0x2281], [0x227C, 0x22E0], [0x227D, 0x22E1], [0x2280, 0x2280], [0x2281, 0x2281], [0x2282, 0x2284], [0x2283, 0x2285], [0x2284, 0x2284], [0x2285, 0x2285], [0x2286, 0x2288], [0x2287, 0x2289], [0x2288, 0x2288], [0x2289, 0x2289], [0x2291, 0x22E2], [0x2292, 0x22E3], [0x22A2, 0x22AC], [0x22A8, 0x22AD], [0x22A9, 0x22AE], [0x22AB, 0x22AF], [0x22AC, 0x22AC], [0x22AD, 0x22AD], [0x22AE, 0x22AE], [0x22AF, 0x22AF], [0x22B2, 0x22EA], [0x22B3, 0x22EB], [0x22B4, 0x22EC], [0x22B5, 0x22ED], [0x22E0, 0x22E0], [0x22E1, 0x22E1], [0x22E2, 0x22E2], [0x22E3, 0x22E3], [0x22EA, 0x22EA], [0x22EB, 0x22EB], [0x22EC, 0x22EC], [0x22ED, 0x22ED], ]);
#[cfg(test)]
mod tests {
use super::*;
use VariationSelector::*;
#[test]
fn solidus_table_sanity_check() {
assert!(PRECOMPOSED_SOLIDUS_OVERLAY.0.is_sorted_by_key(|a| a[0]));
assert!(PRECOMPOSED_SOLIDUS_OVERLAY.0.len().is_multiple_of(2));
assert!(
PRECOMPOSED_SOLIDUS_OVERLAY
.0
.iter()
.filter(|[from, to]| from == to)
.count()
* 2
== PRECOMPOSED_SOLIDUS_OVERLAY.0.len()
);
}
#[test]
fn test_super_char() {
for base in '\0'..=char::MAX {
let sc = SuperChar::from_char(base);
assert!(sc.chars().eq([base]));
assert_eq!(sc.base_char(), base);
assert_eq!(sc.try_as_char(), Some(base));
assert!(!sc.has_vs());
assert_eq!(sc.vs(), None);
let mut sc_buf = [255u8; 4];
sc.encode_utf8(&mut sc_buf);
let mut char_buf = [255u8; 4];
base.encode_utf8(&mut char_buf);
assert_eq!(sc_buf, char_buf);
assert_eq!(sc.to_string(), base.to_string());
let sc_solidus = sc.with_overlay(OverlayChar::Solidus);
if let Some(precomposed) = get_precomposed_solidus_overlay(base) {
assert_eq!(sc_solidus == sc, precomposed == base);
assert!(sc_solidus.chars().eq([precomposed]));
assert_eq!(sc_solidus.base_char(), precomposed);
assert_eq!(sc_solidus.try_as_char(), Some(precomposed));
} else {
assert!(sc_solidus != sc);
assert!(
sc_solidus
.chars()
.eq([base, symbol::COMBINING_LONG_SOLIDUS_OVERLAY])
);
assert_eq!(sc_solidus.base_char(), base);
assert_eq!(sc_solidus.try_as_char(), None);
let mut sc_buf_solidus = [255u8; 7];
sc_solidus.encode_utf8(&mut sc_buf_solidus);
let mut char_buf_solidus = [255u8; 7];
let base_utf8_len = base.encode_utf8(&mut char_buf_solidus).len();
symbol::COMBINING_LONG_SOLIDUS_OVERLAY
.encode_utf8(&mut char_buf_solidus[base_utf8_len..]);
assert_eq!(sc_buf_solidus, char_buf_solidus);
}
assert!(!sc_solidus.has_vs());
assert_eq!(sc_solidus.vs(), None);
assert_eq!(sc_solidus.with_overlay(OverlayChar::Solidus), sc_solidus);
let sc_vert = sc.with_overlay(OverlayChar::VerticalLine);
assert!(sc_vert != sc);
assert!(sc_vert != sc_solidus);
assert!(
sc_vert
.chars()
.eq([base, symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY])
);
assert_eq!(sc_vert.base_char(), base);
assert_eq!(sc_vert.try_as_char(), None);
let mut sc_buf_vert = [255u8; 7];
sc_vert.encode_utf8(&mut sc_buf_vert);
let mut char_buf_vert = [255u8; 7];
let base_utf8_len = base.encode_utf8(&mut char_buf_vert).len();
symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
.encode_utf8(&mut char_buf_vert[base_utf8_len..]);
assert_eq!(sc_buf_vert, char_buf_vert);
assert!(!sc_vert.has_vs());
assert_eq!(sc_vert.vs(), None);
assert_eq!(sc_vert.with_overlay(OverlayChar::VerticalLine), sc_vert);
let sc_both = sc_solidus.with_overlay(OverlayChar::VerticalLine);
assert_eq!(sc_both, sc_vert.with_overlay(OverlayChar::Solidus));
if let Some(precomposed) = get_precomposed_solidus_overlay(base) {
assert_eq!(sc_both == sc_vert, precomposed == base);
assert!(
sc_both
.chars()
.eq([precomposed, symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY])
);
assert_eq!(sc_both.base_char(), precomposed);
} else {
assert!(sc_both != sc_vert);
assert!(sc_both.chars().eq([
base,
symbol::COMBINING_LONG_SOLIDUS_OVERLAY,
symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
]));
assert_eq!(sc_both.base_char(), base);
let mut sc_buf_both = [255u8; 10];
sc_both.encode_utf8(&mut sc_buf_both);
let mut char_buf_both = [255u8; 10];
let base_utf8_len = base.encode_utf8(&mut char_buf_both).len();
symbol::COMBINING_LONG_SOLIDUS_OVERLAY
.encode_utf8(&mut char_buf_both[base_utf8_len..]);
symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
.encode_utf8(&mut char_buf_both[(base_utf8_len + 2)..]);
assert_eq!(sc_buf_both, char_buf_both);
}
assert!(sc_both != sc_solidus);
assert_eq!(sc_both.try_as_char(), None);
assert!(!sc_both.has_vs());
assert_eq!(sc_both.vs(), None);
assert_eq!(sc_both.with_overlay(OverlayChar::Solidus), sc_both);
assert_eq!(sc_both.with_overlay(OverlayChar::VerticalLine), sc_both);
if !is_precomposed_solidus_overlay_for_debug(base) {
for vs in [
Vs1, Vs2, Vs3, Vs4, Vs5, Vs6, Vs7, Vs8, Vs9, Vs10, Vs11, Vs12, Vs13, Vs14, Vs15,
] {
let sc_vs = SuperChar::from_char_with_vs(base, vs);
assert!(sc_vs != sc);
assert!(sc_vs.chars().eq([base, vs.into()]));
assert_eq!(sc_vs.base_char(), base);
assert_eq!(sc_vs.try_as_char(), None);
assert!(sc_vs.has_vs());
assert_eq!(sc_vs.vs(), Some(vs));
let sc_vs_solidus = sc_vs.with_overlay(OverlayChar::Solidus);
assert!(sc_vs_solidus != sc_vs);
assert!(sc_vs_solidus.chars().eq([
base,
vs.into(),
symbol::COMBINING_LONG_SOLIDUS_OVERLAY
]));
assert_eq!(sc_vs_solidus.base_char(), base);
assert_eq!(sc_vs_solidus.try_as_char(), None);
let mut sc_buf_vs_solidus = [255u8; 9];
sc_vs_solidus.encode_utf8(&mut sc_buf_vs_solidus);
let mut char_buf_vs_solidus = [255u8; 9];
let base_utf8_len = base.encode_utf8(&mut char_buf_vs_solidus).len();
char::from(vs).encode_utf8(&mut char_buf_vs_solidus[base_utf8_len..]);
symbol::COMBINING_LONG_SOLIDUS_OVERLAY
.encode_utf8(&mut char_buf_vs_solidus[(base_utf8_len + 3)..]);
assert_eq!(sc_buf_vs_solidus, char_buf_vs_solidus);
assert!(sc_vs_solidus.has_vs());
assert_eq!(sc_vs_solidus.vs(), Some(vs));
assert_eq!(
sc_vs_solidus.with_overlay(OverlayChar::Solidus),
sc_vs_solidus
);
let sc_vs_vert = sc_vs.with_overlay(OverlayChar::VerticalLine);
assert!(sc_vs_vert != sc_vs);
assert!(sc_vs_vert != sc_vs_solidus);
assert!(sc_vs_vert.chars().eq([
base,
vs.into(),
symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
]));
assert_eq!(sc_vs_vert.base_char(), base);
assert_eq!(sc_vs_vert.try_as_char(), None);
let mut sc_buf_vs_vert = [255u8; 10];
sc_vs_vert.encode_utf8(&mut sc_buf_vs_vert);
let mut char_buf_vs_vert = [255u8; 10];
let base_utf8_len = base.encode_utf8(&mut char_buf_vs_vert).len();
char::from(vs).encode_utf8(&mut char_buf_vs_vert[base_utf8_len..]);
symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
.encode_utf8(&mut char_buf_vs_vert[(base_utf8_len + 3)..]);
assert_eq!(sc_buf_vs_vert, char_buf_vs_vert);
assert!(sc_vs_vert.has_vs());
assert_eq!(sc_vs_vert.vs(), Some(vs));
assert_eq!(
sc_vs_vert.with_overlay(OverlayChar::VerticalLine),
sc_vs_vert
);
let sc_vs_both = sc_vs_solidus.with_overlay(OverlayChar::VerticalLine);
assert_eq!(sc_vs_both, sc_vs_vert.with_overlay(OverlayChar::Solidus));
assert!(sc_vs_both != sc_vs_solidus);
assert!(sc_vs_both != sc_vs_vert);
assert!(sc_vs_both.chars().eq([
base,
vs.into(),
symbol::COMBINING_LONG_SOLIDUS_OVERLAY,
symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
]));
assert_eq!(sc_vs_both.base_char(), base);
let mut sc_buf_vs_both = [255u8; 12];
sc_vs_both.encode_utf8(&mut sc_buf_vs_both);
let mut char_buf_vs_both = [255u8; 12];
let base_utf8_len = base.encode_utf8(&mut char_buf_vs_both).len();
char::from(vs).encode_utf8(&mut char_buf_vs_both[base_utf8_len..]);
symbol::COMBINING_LONG_SOLIDUS_OVERLAY
.encode_utf8(&mut char_buf_vs_both[(base_utf8_len + 3)..]);
symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
.encode_utf8(&mut char_buf_vs_both[(base_utf8_len + 5)..]);
assert_eq!(sc_buf_vs_both, char_buf_vs_both);
assert_eq!(sc_vs_both.try_as_char(), None);
assert!(sc_vs_both.has_vs());
assert_eq!(sc_vs_both.vs(), Some(vs));
assert_eq!(sc_vs_both.with_overlay(OverlayChar::Solidus), sc_vs_both);
assert_eq!(
sc_vs_both.with_overlay(OverlayChar::VerticalLine),
sc_vs_both
);
}
}
}
}
}