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]
451 fn test_super_char() {
452 for base in '\0'..=char::MAX {
453 let sc = SuperChar::from_char(base);
456 assert!(sc.chars().eq([base]));
457 assert_eq!(sc.base_char(), base);
458 assert_eq!(sc.try_as_char(), Some(base));
459 assert!(!sc.has_vs());
460 assert_eq!(sc.vs(), None);
461
462 let mut sc_buf = [255u8; 4];
463 sc.encode_utf8(&mut sc_buf);
464 let mut char_buf = [255u8; 4];
465 base.encode_utf8(&mut char_buf);
466
467 assert_eq!(sc_buf, char_buf);
468 assert_eq!(sc.to_string(), base.to_string());
469
470 let sc_solidus = sc.with_overlay(OverlayChar::Solidus);
473 if let Some(precomposed) = get_precomposed_solidus_overlay(base) {
474 assert_eq!(sc_solidus == sc, precomposed == base);
475 assert!(sc_solidus.chars().eq([precomposed]));
476 assert_eq!(sc_solidus.base_char(), precomposed);
477 assert_eq!(sc_solidus.try_as_char(), Some(precomposed));
478 } else {
479 assert!(sc_solidus != sc);
480 assert!(
481 sc_solidus
482 .chars()
483 .eq([base, symbol::COMBINING_LONG_SOLIDUS_OVERLAY])
484 );
485 assert_eq!(sc_solidus.base_char(), base);
486 assert_eq!(sc_solidus.try_as_char(), None);
487
488 let mut sc_buf_solidus = [255u8; 7];
489 sc_solidus.encode_utf8(&mut sc_buf_solidus);
490 let mut char_buf_solidus = [255u8; 7];
491 let base_utf8_len = base.encode_utf8(&mut char_buf_solidus).len();
492 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
493 .encode_utf8(&mut char_buf_solidus[base_utf8_len..]);
494 assert_eq!(sc_buf_solidus, char_buf_solidus);
495 }
496 assert!(!sc_solidus.has_vs());
497 assert_eq!(sc_solidus.vs(), None);
498 assert_eq!(sc_solidus.with_overlay(OverlayChar::Solidus), sc_solidus);
499
500 let sc_vert = sc.with_overlay(OverlayChar::VerticalLine);
503 assert!(sc_vert != sc);
504 assert!(sc_vert != sc_solidus);
505 assert!(
506 sc_vert
507 .chars()
508 .eq([base, symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY])
509 );
510 assert_eq!(sc_vert.base_char(), base);
511 assert_eq!(sc_vert.try_as_char(), None);
512
513 let mut sc_buf_vert = [255u8; 7];
514 sc_vert.encode_utf8(&mut sc_buf_vert);
515 let mut char_buf_vert = [255u8; 7];
516 let base_utf8_len = base.encode_utf8(&mut char_buf_vert).len();
517 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
518 .encode_utf8(&mut char_buf_vert[base_utf8_len..]);
519 assert_eq!(sc_buf_vert, char_buf_vert);
520
521 assert!(!sc_vert.has_vs());
522 assert_eq!(sc_vert.vs(), None);
523 assert_eq!(sc_vert.with_overlay(OverlayChar::VerticalLine), sc_vert);
524
525 let sc_both = sc_solidus.with_overlay(OverlayChar::VerticalLine);
528 assert_eq!(sc_both, sc_vert.with_overlay(OverlayChar::Solidus));
529 if let Some(precomposed) = get_precomposed_solidus_overlay(base) {
530 assert_eq!(sc_both == sc_vert, precomposed == base);
531 assert!(
532 sc_both
533 .chars()
534 .eq([precomposed, symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY])
535 );
536 assert_eq!(sc_both.base_char(), precomposed);
537 } else {
538 assert!(sc_both != sc_vert);
539 assert!(sc_both.chars().eq([
540 base,
541 symbol::COMBINING_LONG_SOLIDUS_OVERLAY,
542 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
543 ]));
544 assert_eq!(sc_both.base_char(), base);
545
546 let mut sc_buf_both = [255u8; 10];
547 sc_both.encode_utf8(&mut sc_buf_both);
548 let mut char_buf_both = [255u8; 10];
549 let base_utf8_len = base.encode_utf8(&mut char_buf_both).len();
550 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
551 .encode_utf8(&mut char_buf_both[base_utf8_len..]);
552 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
553 .encode_utf8(&mut char_buf_both[(base_utf8_len + 2)..]);
554 assert_eq!(sc_buf_both, char_buf_both);
555 }
556 assert!(sc_both != sc_solidus);
557 assert_eq!(sc_both.try_as_char(), None);
558 assert!(!sc_both.has_vs());
559 assert_eq!(sc_both.vs(), None);
560 assert_eq!(sc_both.with_overlay(OverlayChar::Solidus), sc_both);
561 assert_eq!(sc_both.with_overlay(OverlayChar::VerticalLine), sc_both);
562
563 if !is_precomposed_solidus_overlay_for_debug(base) {
567 for vs in [
568 Vs1, Vs2, Vs3, Vs4, Vs5, Vs6, Vs7, Vs8, Vs9, Vs10, Vs11, Vs12, Vs13, Vs14, Vs15,
569 ] {
570 let sc_vs = SuperChar::from_char_with_vs(base, vs);
571
572 assert!(sc_vs != sc);
573 assert!(sc_vs.chars().eq([base, vs.into()]));
574 assert_eq!(sc_vs.base_char(), base);
575 assert_eq!(sc_vs.try_as_char(), None);
576 assert!(sc_vs.has_vs());
577 assert_eq!(sc_vs.vs(), Some(vs));
578
579 let sc_vs_solidus = sc_vs.with_overlay(OverlayChar::Solidus);
582 assert!(sc_vs_solidus != sc_vs);
583 assert!(sc_vs_solidus.chars().eq([
584 base,
585 vs.into(),
586 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
587 ]));
588 assert_eq!(sc_vs_solidus.base_char(), base);
589 assert_eq!(sc_vs_solidus.try_as_char(), None);
590
591 let mut sc_buf_vs_solidus = [255u8; 9];
592 sc_vs_solidus.encode_utf8(&mut sc_buf_vs_solidus);
593 let mut char_buf_vs_solidus = [255u8; 9];
594 let base_utf8_len = base.encode_utf8(&mut char_buf_vs_solidus).len();
595 char::from(vs).encode_utf8(&mut char_buf_vs_solidus[base_utf8_len..]);
596 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
597 .encode_utf8(&mut char_buf_vs_solidus[(base_utf8_len + 3)..]);
598 assert_eq!(sc_buf_vs_solidus, char_buf_vs_solidus);
599
600 assert!(sc_vs_solidus.has_vs());
601 assert_eq!(sc_vs_solidus.vs(), Some(vs));
602 assert_eq!(
603 sc_vs_solidus.with_overlay(OverlayChar::Solidus),
604 sc_vs_solidus
605 );
606
607 let sc_vs_vert = sc_vs.with_overlay(OverlayChar::VerticalLine);
610 assert!(sc_vs_vert != sc_vs);
611 assert!(sc_vs_vert != sc_vs_solidus);
612 assert!(sc_vs_vert.chars().eq([
613 base,
614 vs.into(),
615 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
616 ]));
617 assert_eq!(sc_vs_vert.base_char(), base);
618 assert_eq!(sc_vs_vert.try_as_char(), None);
619
620 let mut sc_buf_vs_vert = [255u8; 10];
621 sc_vs_vert.encode_utf8(&mut sc_buf_vs_vert);
622 let mut char_buf_vs_vert = [255u8; 10];
623 let base_utf8_len = base.encode_utf8(&mut char_buf_vs_vert).len();
624 char::from(vs).encode_utf8(&mut char_buf_vs_vert[base_utf8_len..]);
625 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
626 .encode_utf8(&mut char_buf_vs_vert[(base_utf8_len + 3)..]);
627 assert_eq!(sc_buf_vs_vert, char_buf_vs_vert);
628
629 assert!(sc_vs_vert.has_vs());
630 assert_eq!(sc_vs_vert.vs(), Some(vs));
631 assert_eq!(
632 sc_vs_vert.with_overlay(OverlayChar::VerticalLine),
633 sc_vs_vert
634 );
635
636 let sc_vs_both = sc_vs_solidus.with_overlay(OverlayChar::VerticalLine);
639 assert_eq!(sc_vs_both, sc_vs_vert.with_overlay(OverlayChar::Solidus));
640
641 assert!(sc_vs_both != sc_vs_solidus);
642 assert!(sc_vs_both != sc_vs_vert);
643 assert!(sc_vs_both.chars().eq([
644 base,
645 vs.into(),
646 symbol::COMBINING_LONG_SOLIDUS_OVERLAY,
647 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
648 ]));
649 assert_eq!(sc_vs_both.base_char(), base);
650
651 let mut sc_buf_vs_both = [255u8; 12];
652 sc_vs_both.encode_utf8(&mut sc_buf_vs_both);
653 let mut char_buf_vs_both = [255u8; 12];
654 let base_utf8_len = base.encode_utf8(&mut char_buf_vs_both).len();
655 char::from(vs).encode_utf8(&mut char_buf_vs_both[base_utf8_len..]);
656 symbol::COMBINING_LONG_SOLIDUS_OVERLAY
657 .encode_utf8(&mut char_buf_vs_both[(base_utf8_len + 3)..]);
658 symbol::COMBINING_LONG_VERTICAL_LINE_OVERLAY
659 .encode_utf8(&mut char_buf_vs_both[(base_utf8_len + 5)..]);
660 assert_eq!(sc_buf_vs_both, char_buf_vs_both);
661
662 assert_eq!(sc_vs_both.try_as_char(), None);
663 assert!(sc_vs_both.has_vs());
664 assert_eq!(sc_vs_both.vs(), Some(vs));
665 assert_eq!(sc_vs_both.with_overlay(OverlayChar::Solidus), sc_vs_both);
666 assert_eq!(
667 sc_vs_both.with_overlay(OverlayChar::VerticalLine),
668 sc_vs_both
669 );
670 }
671 }
672 }
673 }
674}