1use core::{
2 fmt::{self, Write},
3 iter::FusedIterator,
4};
5
6#[cfg(feature = "serde")]
7use serde::Serialize;
8
9use crate::symbol;
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash)]
27#[repr(transparent)]
28pub struct SuperChar(u32);
29
30const VS_MASK: u32 = 0xF000_0000;
32
33const CHAR_MASK: u32 = 0x001F_FFFF;
35
36const SOLIDUS_BIT: u32 = 0x0800_0000;
38
39const VERTICAL_LINE_BIT: u32 = 0x0400_0000;
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
43#[non_exhaustive]
44pub enum VariationSelector {
45 Vs1 = 1,
47 Vs2 = 2,
49 Vs3 = 3,
51 Vs4 = 4,
53 Vs5 = 5,
55 Vs6 = 6,
57 Vs7 = 7,
59 Vs8 = 8,
61 Vs9 = 9,
63 Vs10 = 10,
65 Vs11 = 11,
67 Vs12 = 12,
69 Vs13 = 13,
71 Vs14 = 14,
73 Vs15 = 15,
75}
76
77impl From<VariationSelector> for char {
78 #[inline]
79 fn from(vs: VariationSelector) -> Self {
80 char::from_u32(0xFDFF + vs as u32).unwrap()
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
85#[non_exhaustive]
86pub enum OverlayChar {
87 Solidus,
89 VerticalLine,
91}
92
93impl From<OverlayChar> for char {
94 fn from(oc: OverlayChar) -> Self {
95 match oc {
96 OverlayChar::Solidus => symbol::COMBINING_LONG_SOLIDUS_OVERLAY,
97 OverlayChar::VerticalLine => symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY,
98 }
99 }
100}
101
102impl SuperChar {
103 pub const MAX_LEN_UTF8: usize = 12;
104
105 #[must_use]
106 #[inline]
107 pub const fn from_char(c: char) -> Self {
108 Self(c as u32)
109 }
110
111 #[must_use]
115 #[inline]
116 pub const fn from_char_with_vs(c: char, vs: VariationSelector) -> Self {
117 debug_assert!(!is_precomposed_solidus_overlay_for_debug(c));
118 Self(c as u32 | ((vs as u32) << 28))
119 }
120
121 #[must_use]
123 #[inline]
124 pub fn chars(self) -> SuperCharChars {
125 SuperCharChars(self.0)
126 }
127
128 #[must_use]
130 #[inline]
131 pub const fn has_vs(self) -> bool {
132 self.0 & VS_MASK != 0
133 }
134
135 #[must_use]
137 #[inline]
138 pub const fn vs(self) -> Option<VariationSelector> {
139 use VariationSelector::*;
140 match self.0 >> 28 {
141 0 => None,
142 1 => Some(Vs1),
143 2 => Some(Vs2),
144 3 => Some(Vs3),
145 4 => Some(Vs4),
146 5 => Some(Vs5),
147 6 => Some(Vs6),
148 7 => Some(Vs7),
149 8 => Some(Vs8),
150 9 => Some(Vs9),
151 10 => Some(Vs10),
152 11 => Some(Vs11),
153 12 => Some(Vs12),
154 13 => Some(Vs13),
155 14 => Some(Vs14),
156 15 => Some(Vs15),
157 _ => unreachable!(),
158 }
159 }
160
161 #[inline]
166 #[must_use]
167 pub fn with_overlay(self, overlay: OverlayChar) -> Self {
168 match overlay {
169 OverlayChar::Solidus => {
170 if !self.has_vs()
171 && let Some(precomposed) = get_precomposed_solidus_overlay(self.base_char())
172 {
173 Self(self.0 & !CHAR_MASK | precomposed as u32)
175 } else {
176 Self(self.0 | SOLIDUS_BIT)
177 }
178 }
179 OverlayChar::VerticalLine => Self(self.0 | VERTICAL_LINE_BIT),
180 }
181 }
182
183 #[must_use]
185 #[inline]
186 pub const fn base_char(self) -> char {
187 unsafe { char::from_u32_unchecked(self.0 & CHAR_MASK) }
189 }
190
191 #[must_use]
194 #[inline]
195 pub const fn try_as_char(self) -> Option<char> {
196 if self.0 & CHAR_MASK == self.0 {
197 Some(self.base_char())
199 } else {
200 None
201 }
202 }
203
204 pub fn encode_utf8(self, dst: &mut [u8]) -> &mut str {
206 let mut idx: usize = 0;
207 for c in self.chars() {
208 let result = c.encode_utf8(&mut dst[idx..]);
209 idx += result.len();
210 }
211 unsafe { str::from_utf8_unchecked_mut(&mut dst[..idx]) }
213 }
214}
215
216impl From<char> for SuperChar {
217 #[inline]
218 fn from(c: char) -> Self {
219 Self::from_char(c)
220 }
221}
222
223impl fmt::Debug for SuperChar {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 write!(f, "\"")?;
226 for c in self.chars() {
227 write!(f, "{}", c.escape_debug())?;
228 }
229 write!(f, "\"")?;
230 Ok(())
231 }
232}
233
234impl fmt::Display for SuperChar {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 for c in self.chars() {
237 f.write_char(c)?;
238 }
239 Ok(())
240 }
241}
242
243#[cfg(feature = "serde")]
244impl Serialize for SuperChar {
245 #[inline]
246 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
247 where
248 S: serde::Serializer,
249 {
250 serde::Serializer::collect_str(serializer, self)
251 }
252}
253
254#[derive(Clone, Debug)]
259#[repr(transparent)]
260pub struct SuperCharChars(u32);
261
262impl Iterator for SuperCharChars {
263 type Item = char;
264
265 #[inline]
266 fn next(&mut self) -> Option<Self::Item> {
267 let base_char = self.0 & CHAR_MASK;
268 if base_char != CHAR_MASK {
269 self.0 |= CHAR_MASK;
270 Some(unsafe { char::from_u32_unchecked(base_char) })
273 } else if let Some(vs) = SuperChar(self.0).vs() {
274 self.0 &= !VS_MASK;
275 Some(vs.into())
276 } else if self.0 & SOLIDUS_BIT != 0 {
277 self.0 &= !SOLIDUS_BIT;
278 Some(symbol::COMBINING_LONG_SOLIDUS_OVERLAY)
279 } else if self.0 & VERTICAL_LINE_BIT != 0 {
280 self.0 &= !VERTICAL_LINE_BIT;
281 Some(symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY)
282 } else {
283 None
284 }
285 }
286}
287
288impl FusedIterator for SuperCharChars {}
289
290#[inline]
292const fn get_precomposed_solidus_overlay(c: char) -> Option<char> {
293 if c as u32 > u16::MAX as u32 {
294 return None;
295 }
296 let bmp = c as u16;
297 let mut k = 1;
298 while k < PRECOMPOSED_SOLIDUS_OVERLAY_EYTZINGER.0.len() {
299 let direction = if PRECOMPOSED_SOLIDUS_OVERLAY_EYTZINGER.0[k][0] < bmp {
300 1
301 } else if PRECOMPOSED_SOLIDUS_OVERLAY_EYTZINGER.0[k][0] == bmp {
302 break;
303 } else {
304 0
305 };
306 k = 2 * k + direction;
307 }
308 if k == 0 || k >= PRECOMPOSED_SOLIDUS_OVERLAY_EYTZINGER.0.len() {
309 return None;
310 }
311 let slot = PRECOMPOSED_SOLIDUS_OVERLAY_EYTZINGER.0[k];
312 if slot[0] != bmp {
313 return None;
314 }
315 Some(unsafe { char::from_u32_unchecked(slot[1] as u32) })
317}
318
319const fn is_precomposed_solidus_overlay_for_debug(c: char) -> bool {
322 get_precomposed_solidus_overlay(c).is_some()
323}
324
325#[repr(align(128))] struct Align128<T>(T);
327
328const PRECOMPOSED_SOLIDUS_OVERLAY_EYTZINGER: Align128<
331 [[u16; 2]; PRECOMPOSED_SOLIDUS_OVERLAY_SORTED.len() + 1],
332> = Align128(
333 const {
334 let mut result = [[0, 0]; PRECOMPOSED_SOLIDUS_OVERLAY_SORTED.len() + 1];
335 const fn eytzingerise(data: &mut [[u16; 2]], mut i: usize, k: usize) -> usize {
336 if k < data.len() {
337 i = eytzingerise(data, i, 2 * k);
338 data[k] = PRECOMPOSED_SOLIDUS_OVERLAY_SORTED[i];
339 i = eytzingerise(data, i + 1, 2 * k + 1);
340 }
341 i
342 }
343 eytzingerise(&mut result, 0, 1);
344 result
345 },
346);
347
348const PRECOMPOSED_SOLIDUS_OVERLAY_SORTED: [[u16; 2]; 88] = [
351 [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], ];
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444 use VariationSelector::*;
445
446 #[test]
447 fn solidus_table_sanity_check() {
448 assert!(PRECOMPOSED_SOLIDUS_OVERLAY_SORTED.is_sorted_by_key(|a| a[0]));
450 assert!(PRECOMPOSED_SOLIDUS_OVERLAY_SORTED.len().is_multiple_of(2));
452 assert!(
454 PRECOMPOSED_SOLIDUS_OVERLAY_SORTED
455 .iter()
456 .filter(|[from, to]| from == to)
457 .count()
458 * 2
459 == PRECOMPOSED_SOLIDUS_OVERLAY_SORTED.len()
460 );
461 for &[key, value] in &PRECOMPOSED_SOLIDUS_OVERLAY_SORTED {
463 assert_eq!(
464 get_precomposed_solidus_overlay(char::try_from(key as u32).unwrap()),
465 Some(char::try_from(value as u32).unwrap())
466 );
467 }
468 }
469
470 #[test]
480 fn test_super_char() {
481 let interesting = [
482 '\u{0}',
484 '\u{7F}',
485 '\u{80}',
486 '\u{7FF}',
487 '\u{800}',
488 '\u{D7FF}',
489 '\u{E000}',
490 '\u{FFFF}',
491 '\u{10000}',
492 char::MAX,
493 'a',
495 'Z',
496 '0',
497 'α',
498 '∑',
499 '€',
500 '😀',
501 ]
502 .into_iter()
503 .chain(
506 PRECOMPOSED_SOLIDUS_OVERLAY_SORTED
507 .iter()
508 .flat_map(|&[from, to]| [from, to])
509 .map(|cp| char::from_u32(cp.into()).unwrap()),
510 );
511
512 for base in interesting {
513 let sc = SuperChar::from_char(base);
516 assert!(sc.chars().eq([base]));
517 assert_eq!(sc.base_char(), base);
518 assert_eq!(sc.try_as_char(), Some(base));
519 assert!(!sc.has_vs());
520 assert_eq!(sc.vs(), None);
521
522 let mut sc_buf = [255u8; 4];
523 sc.encode_utf8(&mut sc_buf);
524 let mut char_buf = [255u8; 4];
525 base.encode_utf8(&mut char_buf);
526
527 assert_eq!(sc_buf, char_buf);
528 assert_eq!(sc.to_string(), base.to_string());
529
530 let sc_solidus = sc.with_overlay(OverlayChar::Solidus);
533 if let Some(precomposed) = get_precomposed_solidus_overlay(base) {
534 assert_eq!(sc_solidus == sc, precomposed == base);
535 assert!(sc_solidus.chars().eq([precomposed]));
536 assert_eq!(sc_solidus.base_char(), precomposed);
537 assert_eq!(sc_solidus.try_as_char(), Some(precomposed));
538 } else {
539 assert!(sc_solidus != sc);
540 assert!(
541 sc_solidus
542 .chars()
543 .eq([base, symbol::COMBINING_LONG_SOLIDUS_OVERLAY])
544 );
545 assert_eq!(sc_solidus.base_char(), base);
546 assert_eq!(sc_solidus.try_as_char(), None);
547
548 let mut sc_buf_solidus = [255u8; 7];
549 sc_solidus.encode_utf8(&mut sc_buf_solidus);
550 let mut char_buf_solidus = [255u8; 7];
551 let base_utf8_len = base.encode_utf8(&mut char_buf_solidus).len();
552 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
553 .encode_utf8(&mut char_buf_solidus[base_utf8_len..]);
554 assert_eq!(sc_buf_solidus, char_buf_solidus);
555 }
556 assert!(!sc_solidus.has_vs());
557 assert_eq!(sc_solidus.vs(), None);
558 assert_eq!(sc_solidus.with_overlay(OverlayChar::Solidus), sc_solidus);
559
560 let sc_vert = sc.with_overlay(OverlayChar::VerticalLine);
563 assert!(sc_vert != sc);
564 assert!(sc_vert != sc_solidus);
565 assert!(
566 sc_vert
567 .chars()
568 .eq([base, symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY])
569 );
570 assert_eq!(sc_vert.base_char(), base);
571 assert_eq!(sc_vert.try_as_char(), None);
572
573 let mut sc_buf_vert = [255u8; 7];
574 sc_vert.encode_utf8(&mut sc_buf_vert);
575 let mut char_buf_vert = [255u8; 7];
576 let base_utf8_len = base.encode_utf8(&mut char_buf_vert).len();
577 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
578 .encode_utf8(&mut char_buf_vert[base_utf8_len..]);
579 assert_eq!(sc_buf_vert, char_buf_vert);
580
581 assert!(!sc_vert.has_vs());
582 assert_eq!(sc_vert.vs(), None);
583 assert_eq!(sc_vert.with_overlay(OverlayChar::VerticalLine), sc_vert);
584
585 let sc_both = sc_solidus.with_overlay(OverlayChar::VerticalLine);
588 assert_eq!(sc_both, sc_vert.with_overlay(OverlayChar::Solidus));
589 if let Some(precomposed) = get_precomposed_solidus_overlay(base) {
590 assert_eq!(sc_both == sc_vert, precomposed == base);
591 assert!(
592 sc_both
593 .chars()
594 .eq([precomposed, symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY])
595 );
596 assert_eq!(sc_both.base_char(), precomposed);
597 } else {
598 assert!(sc_both != sc_vert);
599 assert!(sc_both.chars().eq([
600 base,
601 symbol::COMBINING_LONG_SOLIDUS_OVERLAY,
602 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
603 ]));
604 assert_eq!(sc_both.base_char(), base);
605
606 let mut sc_buf_both = [255u8; 10];
607 sc_both.encode_utf8(&mut sc_buf_both);
608 let mut char_buf_both = [255u8; 10];
609 let base_utf8_len = base.encode_utf8(&mut char_buf_both).len();
610 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
611 .encode_utf8(&mut char_buf_both[base_utf8_len..]);
612 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
613 .encode_utf8(&mut char_buf_both[(base_utf8_len + 2)..]);
614 assert_eq!(sc_buf_both, char_buf_both);
615 }
616 assert!(sc_both != sc_solidus);
617 assert_eq!(sc_both.try_as_char(), None);
618 assert!(!sc_both.has_vs());
619 assert_eq!(sc_both.vs(), None);
620 assert_eq!(sc_both.with_overlay(OverlayChar::Solidus), sc_both);
621 assert_eq!(sc_both.with_overlay(OverlayChar::VerticalLine), sc_both);
622
623 if !is_precomposed_solidus_overlay_for_debug(base) {
627 for vs in [
628 Vs1, Vs2, Vs3, Vs4, Vs5, Vs6, Vs7, Vs8, Vs9, Vs10, Vs11, Vs12, Vs13, Vs14, Vs15,
629 ] {
630 let sc_vs = SuperChar::from_char_with_vs(base, vs);
631
632 assert!(sc_vs != sc);
633 assert!(sc_vs.chars().eq([base, vs.into()]));
634 assert_eq!(sc_vs.base_char(), base);
635 assert_eq!(sc_vs.try_as_char(), None);
636 assert!(sc_vs.has_vs());
637 assert_eq!(sc_vs.vs(), Some(vs));
638
639 let sc_vs_solidus = sc_vs.with_overlay(OverlayChar::Solidus);
642 assert!(sc_vs_solidus != sc_vs);
643 assert!(sc_vs_solidus.chars().eq([
644 base,
645 vs.into(),
646 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
647 ]));
648 assert_eq!(sc_vs_solidus.base_char(), base);
649 assert_eq!(sc_vs_solidus.try_as_char(), None);
650
651 let mut sc_buf_vs_solidus = [255u8; 9];
652 sc_vs_solidus.encode_utf8(&mut sc_buf_vs_solidus);
653 let mut char_buf_vs_solidus = [255u8; 9];
654 let base_utf8_len = base.encode_utf8(&mut char_buf_vs_solidus).len();
655 char::from(vs).encode_utf8(&mut char_buf_vs_solidus[base_utf8_len..]);
656 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
657 .encode_utf8(&mut char_buf_vs_solidus[(base_utf8_len + 3)..]);
658 assert_eq!(sc_buf_vs_solidus, char_buf_vs_solidus);
659
660 assert!(sc_vs_solidus.has_vs());
661 assert_eq!(sc_vs_solidus.vs(), Some(vs));
662 assert_eq!(
663 sc_vs_solidus.with_overlay(OverlayChar::Solidus),
664 sc_vs_solidus
665 );
666
667 let sc_vs_vert = sc_vs.with_overlay(OverlayChar::VerticalLine);
670 assert!(sc_vs_vert != sc_vs);
671 assert!(sc_vs_vert != sc_vs_solidus);
672 assert!(sc_vs_vert.chars().eq([
673 base,
674 vs.into(),
675 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
676 ]));
677 assert_eq!(sc_vs_vert.base_char(), base);
678 assert_eq!(sc_vs_vert.try_as_char(), None);
679
680 let mut sc_buf_vs_vert = [255u8; 10];
681 sc_vs_vert.encode_utf8(&mut sc_buf_vs_vert);
682 let mut char_buf_vs_vert = [255u8; 10];
683 let base_utf8_len = base.encode_utf8(&mut char_buf_vs_vert).len();
684 char::from(vs).encode_utf8(&mut char_buf_vs_vert[base_utf8_len..]);
685 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
686 .encode_utf8(&mut char_buf_vs_vert[(base_utf8_len + 3)..]);
687 assert_eq!(sc_buf_vs_vert, char_buf_vs_vert);
688
689 assert!(sc_vs_vert.has_vs());
690 assert_eq!(sc_vs_vert.vs(), Some(vs));
691 assert_eq!(
692 sc_vs_vert.with_overlay(OverlayChar::VerticalLine),
693 sc_vs_vert
694 );
695
696 let sc_vs_both = sc_vs_solidus.with_overlay(OverlayChar::VerticalLine);
699 assert_eq!(sc_vs_both, sc_vs_vert.with_overlay(OverlayChar::Solidus));
700
701 assert!(sc_vs_both != sc_vs_solidus);
702 assert!(sc_vs_both != sc_vs_vert);
703 assert!(sc_vs_both.chars().eq([
704 base,
705 vs.into(),
706 symbol::COMBINING_LONG_SOLIDUS_OVERLAY,
707 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
708 ]));
709 assert_eq!(sc_vs_both.base_char(), base);
710
711 let mut sc_buf_vs_both = [255u8; 12];
712 sc_vs_both.encode_utf8(&mut sc_buf_vs_both);
713 let mut char_buf_vs_both = [255u8; 12];
714 let base_utf8_len = base.encode_utf8(&mut char_buf_vs_both).len();
715 char::from(vs).encode_utf8(&mut char_buf_vs_both[base_utf8_len..]);
716 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
717 .encode_utf8(&mut char_buf_vs_both[(base_utf8_len + 3)..]);
718 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
719 .encode_utf8(&mut char_buf_vs_both[(base_utf8_len + 5)..]);
720 assert_eq!(sc_buf_vs_both, char_buf_vs_both);
721
722 assert_eq!(sc_vs_both.try_as_char(), None);
723 assert!(sc_vs_both.has_vs());
724 assert_eq!(sc_vs_both.vs(), Some(vs));
725 assert_eq!(sc_vs_both.with_overlay(OverlayChar::Solidus), sc_vs_both);
726 assert_eq!(
727 sc_vs_both.with_overlay(OverlayChar::VerticalLine),
728 sc_vs_both
729 );
730 }
731 }
732 }
733 }
734}