1use std::{
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]
291fn get_precomposed_solidus_overlay(c: char) -> Option<char> {
292 if let Ok(bmp) = u16::try_from(c) {
293 PRECOMPOSED_SOLIDUS_OVERLAY
294 .0
295 .binary_search_by_key(&bmp, |&[from, _]| from)
296 .ok()
297 .map(|idx| unsafe {
299 char::from_u32_unchecked(PRECOMPOSED_SOLIDUS_OVERLAY.0[idx][1].into())
300 })
301 } else {
302 None
303 }
304}
305
306const fn is_precomposed_solidus_overlay_for_debug(c: char) -> bool {
309 let cp = c as u32;
313 if cp > u16::MAX as u32 {
314 return false;
315 }
316 let cp = cp as u16;
317
318 let mut i: usize = 0;
319 while i < PRECOMPOSED_SOLIDUS_OVERLAY.0.len() {
320 if cp == PRECOMPOSED_SOLIDUS_OVERLAY.0[i][1] {
321 return true;
322 }
323 i += 1;
324 }
325
326 false
327}
328
329#[repr(align(128))] struct Align128<T>(T);
331
332const PRECOMPOSED_SOLIDUS_OVERLAY: Align128<[[u16; 2]; 88]> = Align128([
336 [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], ]);
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429 use VariationSelector::*;
430
431 #[test]
432 fn solidus_table_sanity_check() {
433 assert!(PRECOMPOSED_SOLIDUS_OVERLAY.0.is_sorted_by_key(|a| a[0]));
435 assert!(PRECOMPOSED_SOLIDUS_OVERLAY.0.len().is_multiple_of(2));
437 assert!(
439 PRECOMPOSED_SOLIDUS_OVERLAY
440 .0
441 .iter()
442 .filter(|[from, to]| from == to)
443 .count()
444 * 2
445 == PRECOMPOSED_SOLIDUS_OVERLAY.0.len()
446 );
447 }
448
449 #[test]
459 fn test_super_char() {
460 let interesting = [
461 '\u{0}',
463 '\u{7F}',
464 '\u{80}',
465 '\u{7FF}',
466 '\u{800}',
467 '\u{D7FF}',
468 '\u{E000}',
469 '\u{FFFF}',
470 '\u{10000}',
471 char::MAX,
472 'a',
474 'Z',
475 '0',
476 'α',
477 '∑',
478 '€',
479 '😀',
480 ]
481 .into_iter()
482 .chain(
485 PRECOMPOSED_SOLIDUS_OVERLAY
486 .0
487 .iter()
488 .flat_map(|&[from, to]| [from, to])
489 .map(|cp| char::from_u32(cp.into()).unwrap()),
490 );
491
492 for base in interesting {
493 let sc = SuperChar::from_char(base);
496 assert!(sc.chars().eq([base]));
497 assert_eq!(sc.base_char(), base);
498 assert_eq!(sc.try_as_char(), Some(base));
499 assert!(!sc.has_vs());
500 assert_eq!(sc.vs(), None);
501
502 let mut sc_buf = [255u8; 4];
503 sc.encode_utf8(&mut sc_buf);
504 let mut char_buf = [255u8; 4];
505 base.encode_utf8(&mut char_buf);
506
507 assert_eq!(sc_buf, char_buf);
508 assert_eq!(sc.to_string(), base.to_string());
509
510 let sc_solidus = sc.with_overlay(OverlayChar::Solidus);
513 if let Some(precomposed) = get_precomposed_solidus_overlay(base) {
514 assert_eq!(sc_solidus == sc, precomposed == base);
515 assert!(sc_solidus.chars().eq([precomposed]));
516 assert_eq!(sc_solidus.base_char(), precomposed);
517 assert_eq!(sc_solidus.try_as_char(), Some(precomposed));
518 } else {
519 assert!(sc_solidus != sc);
520 assert!(
521 sc_solidus
522 .chars()
523 .eq([base, symbol::COMBINING_LONG_SOLIDUS_OVERLAY])
524 );
525 assert_eq!(sc_solidus.base_char(), base);
526 assert_eq!(sc_solidus.try_as_char(), None);
527
528 let mut sc_buf_solidus = [255u8; 7];
529 sc_solidus.encode_utf8(&mut sc_buf_solidus);
530 let mut char_buf_solidus = [255u8; 7];
531 let base_utf8_len = base.encode_utf8(&mut char_buf_solidus).len();
532 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
533 .encode_utf8(&mut char_buf_solidus[base_utf8_len..]);
534 assert_eq!(sc_buf_solidus, char_buf_solidus);
535 }
536 assert!(!sc_solidus.has_vs());
537 assert_eq!(sc_solidus.vs(), None);
538 assert_eq!(sc_solidus.with_overlay(OverlayChar::Solidus), sc_solidus);
539
540 let sc_vert = sc.with_overlay(OverlayChar::VerticalLine);
543 assert!(sc_vert != sc);
544 assert!(sc_vert != sc_solidus);
545 assert!(
546 sc_vert
547 .chars()
548 .eq([base, symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY])
549 );
550 assert_eq!(sc_vert.base_char(), base);
551 assert_eq!(sc_vert.try_as_char(), None);
552
553 let mut sc_buf_vert = [255u8; 7];
554 sc_vert.encode_utf8(&mut sc_buf_vert);
555 let mut char_buf_vert = [255u8; 7];
556 let base_utf8_len = base.encode_utf8(&mut char_buf_vert).len();
557 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
558 .encode_utf8(&mut char_buf_vert[base_utf8_len..]);
559 assert_eq!(sc_buf_vert, char_buf_vert);
560
561 assert!(!sc_vert.has_vs());
562 assert_eq!(sc_vert.vs(), None);
563 assert_eq!(sc_vert.with_overlay(OverlayChar::VerticalLine), sc_vert);
564
565 let sc_both = sc_solidus.with_overlay(OverlayChar::VerticalLine);
568 assert_eq!(sc_both, sc_vert.with_overlay(OverlayChar::Solidus));
569 if let Some(precomposed) = get_precomposed_solidus_overlay(base) {
570 assert_eq!(sc_both == sc_vert, precomposed == base);
571 assert!(
572 sc_both
573 .chars()
574 .eq([precomposed, symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY])
575 );
576 assert_eq!(sc_both.base_char(), precomposed);
577 } else {
578 assert!(sc_both != sc_vert);
579 assert!(sc_both.chars().eq([
580 base,
581 symbol::COMBINING_LONG_SOLIDUS_OVERLAY,
582 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
583 ]));
584 assert_eq!(sc_both.base_char(), base);
585
586 let mut sc_buf_both = [255u8; 10];
587 sc_both.encode_utf8(&mut sc_buf_both);
588 let mut char_buf_both = [255u8; 10];
589 let base_utf8_len = base.encode_utf8(&mut char_buf_both).len();
590 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
591 .encode_utf8(&mut char_buf_both[base_utf8_len..]);
592 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
593 .encode_utf8(&mut char_buf_both[(base_utf8_len + 2)..]);
594 assert_eq!(sc_buf_both, char_buf_both);
595 }
596 assert!(sc_both != sc_solidus);
597 assert_eq!(sc_both.try_as_char(), None);
598 assert!(!sc_both.has_vs());
599 assert_eq!(sc_both.vs(), None);
600 assert_eq!(sc_both.with_overlay(OverlayChar::Solidus), sc_both);
601 assert_eq!(sc_both.with_overlay(OverlayChar::VerticalLine), sc_both);
602
603 if !is_precomposed_solidus_overlay_for_debug(base) {
607 for vs in [
608 Vs1, Vs2, Vs3, Vs4, Vs5, Vs6, Vs7, Vs8, Vs9, Vs10, Vs11, Vs12, Vs13, Vs14, Vs15,
609 ] {
610 let sc_vs = SuperChar::from_char_with_vs(base, vs);
611
612 assert!(sc_vs != sc);
613 assert!(sc_vs.chars().eq([base, vs.into()]));
614 assert_eq!(sc_vs.base_char(), base);
615 assert_eq!(sc_vs.try_as_char(), None);
616 assert!(sc_vs.has_vs());
617 assert_eq!(sc_vs.vs(), Some(vs));
618
619 let sc_vs_solidus = sc_vs.with_overlay(OverlayChar::Solidus);
622 assert!(sc_vs_solidus != sc_vs);
623 assert!(sc_vs_solidus.chars().eq([
624 base,
625 vs.into(),
626 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
627 ]));
628 assert_eq!(sc_vs_solidus.base_char(), base);
629 assert_eq!(sc_vs_solidus.try_as_char(), None);
630
631 let mut sc_buf_vs_solidus = [255u8; 9];
632 sc_vs_solidus.encode_utf8(&mut sc_buf_vs_solidus);
633 let mut char_buf_vs_solidus = [255u8; 9];
634 let base_utf8_len = base.encode_utf8(&mut char_buf_vs_solidus).len();
635 char::from(vs).encode_utf8(&mut char_buf_vs_solidus[base_utf8_len..]);
636 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
637 .encode_utf8(&mut char_buf_vs_solidus[(base_utf8_len + 3)..]);
638 assert_eq!(sc_buf_vs_solidus, char_buf_vs_solidus);
639
640 assert!(sc_vs_solidus.has_vs());
641 assert_eq!(sc_vs_solidus.vs(), Some(vs));
642 assert_eq!(
643 sc_vs_solidus.with_overlay(OverlayChar::Solidus),
644 sc_vs_solidus
645 );
646
647 let sc_vs_vert = sc_vs.with_overlay(OverlayChar::VerticalLine);
650 assert!(sc_vs_vert != sc_vs);
651 assert!(sc_vs_vert != sc_vs_solidus);
652 assert!(sc_vs_vert.chars().eq([
653 base,
654 vs.into(),
655 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
656 ]));
657 assert_eq!(sc_vs_vert.base_char(), base);
658 assert_eq!(sc_vs_vert.try_as_char(), None);
659
660 let mut sc_buf_vs_vert = [255u8; 10];
661 sc_vs_vert.encode_utf8(&mut sc_buf_vs_vert);
662 let mut char_buf_vs_vert = [255u8; 10];
663 let base_utf8_len = base.encode_utf8(&mut char_buf_vs_vert).len();
664 char::from(vs).encode_utf8(&mut char_buf_vs_vert[base_utf8_len..]);
665 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
666 .encode_utf8(&mut char_buf_vs_vert[(base_utf8_len + 3)..]);
667 assert_eq!(sc_buf_vs_vert, char_buf_vs_vert);
668
669 assert!(sc_vs_vert.has_vs());
670 assert_eq!(sc_vs_vert.vs(), Some(vs));
671 assert_eq!(
672 sc_vs_vert.with_overlay(OverlayChar::VerticalLine),
673 sc_vs_vert
674 );
675
676 let sc_vs_both = sc_vs_solidus.with_overlay(OverlayChar::VerticalLine);
679 assert_eq!(sc_vs_both, sc_vs_vert.with_overlay(OverlayChar::Solidus));
680
681 assert!(sc_vs_both != sc_vs_solidus);
682 assert!(sc_vs_both != sc_vs_vert);
683 assert!(sc_vs_both.chars().eq([
684 base,
685 vs.into(),
686 symbol::COMBINING_LONG_SOLIDUS_OVERLAY,
687 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
688 ]));
689 assert_eq!(sc_vs_both.base_char(), base);
690
691 let mut sc_buf_vs_both = [255u8; 12];
692 sc_vs_both.encode_utf8(&mut sc_buf_vs_both);
693 let mut char_buf_vs_both = [255u8; 12];
694 let base_utf8_len = base.encode_utf8(&mut char_buf_vs_both).len();
695 char::from(vs).encode_utf8(&mut char_buf_vs_both[base_utf8_len..]);
696 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
697 .encode_utf8(&mut char_buf_vs_both[(base_utf8_len + 3)..]);
698 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
699 .encode_utf8(&mut char_buf_vs_both[(base_utf8_len + 5)..]);
700 assert_eq!(sc_buf_vs_both, char_buf_vs_both);
701
702 assert_eq!(sc_vs_both.try_as_char(), None);
703 assert!(sc_vs_both.has_vs());
704 assert_eq!(sc_vs_both.vs(), Some(vs));
705 assert_eq!(sc_vs_both.with_overlay(OverlayChar::Solidus), sc_vs_both);
706 assert_eq!(
707 sc_vs_both.with_overlay(OverlayChar::VerticalLine),
708 sc_vs_both
709 );
710 }
711 }
712 }
713 }
714}