Skip to main content

math_core_renderer_internal/
super_char.rs

1use core::{
2    fmt::{self, Write},
3    iter::FusedIterator,
4};
5
6#[cfg(feature = "serde")]
7use serde::Serialize;
8
9use crate::symbol;
10
11/// A `SuperChar` is like a `char`, and has the same size as one,
12/// but can additionally encode that the character is followed by
13/// a variation selector in the range U+FE00-U+FE0E,
14/// U+0338 (long solidus overlay), or U+20D2 (long vertical line overlay).
15//
16// # Structure
17//
18// - High 4 bits: 1-15 for VS1-VS15, or 0 for no variation seq
19// - Bit 5 (from MSB): U+0338
20// - Bit 6: U+20D2
21// - Low 21 bits: the base `char`
22//
23// # Safety
24//
25// `self.0 & CHAR_MASK` must always be a valid `char`
26#[derive(Clone, Copy, PartialEq, Eq, Hash)]
27#[repr(transparent)]
28pub struct SuperChar(u32);
29
30/// Mask for the top 4 bits
31const VS_MASK: u32 = 0xF000_0000;
32
33/// Mask for the low 21 bits
34const CHAR_MASK: u32 = 0x001F_FFFF;
35
36/// Mask for U+0338
37const SOLIDUS_BIT: u32 = 0x0800_0000;
38
39/// Mask for U+20D2
40const VERTICAL_LINE_BIT: u32 = 0x0400_0000;
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
43#[non_exhaustive]
44pub enum VariationSelector {
45    /// `'\u{FE00}'`
46    Vs1 = 1,
47    /// `'\u{FE01}'`
48    Vs2 = 2,
49    /// `'\u{FE02}'`
50    Vs3 = 3,
51    /// `'\u{FE03}'`
52    Vs4 = 4,
53    /// `'\u{FE04}'`
54    Vs5 = 5,
55    /// `'\u{FE05}'`
56    Vs6 = 6,
57    /// `'\u{FE06}'`
58    Vs7 = 7,
59    /// `'\u{FE07}'`
60    Vs8 = 8,
61    /// `'\u{FE08}'`
62    Vs9 = 9,
63    /// `'\u{FE09}'`
64    Vs10 = 10,
65    /// `'\u{FE0A}'`
66    Vs11 = 11,
67    /// `'\u{FE0B}'`
68    Vs12 = 12,
69    /// `'\u{FE0C}'`
70    Vs13 = 13,
71    /// `'\u{FE0D}'`
72    Vs14 = 14,
73    /// `'\u{FE0E}'`
74    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    /// `'\u{0338}'`
88    Solidus,
89    /// `'\u{20D2}'`
90    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    /// Characters with canonical decompositions should not take a VS.
112    /// We `debug_assert!` for this with respect to the solidus overlay U+0338.
113    /// (There's no unsoundenss if it happens, it's just not correct Unicode usage)
114    #[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    /// Returns an iterator over the `char`s of this `SuperChar`.
122    #[must_use]
123    #[inline]
124    pub fn chars(self) -> SuperCharChars {
125        SuperCharChars(self.0)
126    }
127
128    /// Whether this `SuperChar` has a variation selector associated
129    #[must_use]
130    #[inline]
131    pub const fn has_vs(self) -> bool {
132        self.0 & VS_MASK != 0
133    }
134
135    /// The variation selector for this `SuperChar`, if it has one.
136    #[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    /// Adds the specified overlay character to this `SuperChar`, returning it as a new `SuperChar`.
162    /// Idempotent if the character is already present.
163    /// For the solidus overlay U+0338, we will use the precomposed form if one exists and
164    /// there is no variation selector set.
165    #[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                    // swap out base char for precomposed form
174                    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    /// Get the base `char` of this `SuperChar`, disregarding variation sequences and overlays.
184    #[must_use]
185    #[inline]
186    pub const fn base_char(self) -> char {
187        // SAFETY: `self.0` field invariant
188        unsafe { char::from_u32_unchecked(self.0 & CHAR_MASK) }
189    }
190
191    /// If this string contains exactly 1 `char`, return it;
192    /// otherwise, return `None`.
193    #[must_use]
194    #[inline]
195    pub const fn try_as_char(self) -> Option<char> {
196        if self.0 & CHAR_MASK == self.0 {
197            // SAFETY: `self.0` field invariant
198            Some(self.base_char())
199        } else {
200            None
201        }
202    }
203
204    /// See [`Self::MAX_LEN_UTF8`] for the number of bytes needed.
205    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        // SAFETY: we encoded valid UTF-8 into this range just above
212        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/// An iterator over the chars of a [`SuperChar`].
255// Invariant: `.0` field either reperesents a valid `SuperChar`,
256// except that character bits may be set to all 1s to represent
257// the base character having been already yielded.
258#[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            // SAFETY: `self.0` field invariant,
271            // and we checked for `CHAR_MASK` above
272            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// The algorithm implementation comes from <https://algorithmica.org/en/eytzinger>.
291#[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    // SAFETY: `PRECOMPOSED_SOLIDUS_OVERLAY_EYTZINGER` contains only valid Unicode characters
316    Some(unsafe { char::from_u32_unchecked(slot[1] as u32) })
317}
318
319/// Whether this character has a canonical decomposition to a sequence containing U+338.
320/// Used in a debug assertion; should not be used in release because it is slow
321const fn is_precomposed_solidus_overlay_for_debug(c: char) -> bool {
322    get_precomposed_solidus_overlay(c).is_some()
323}
324
325#[repr(align(128))] // align to cache line
326struct Align128<T>(T);
327
328// This is a mapping from Unicode codepoints to their precomposed solidus-overlay variant.
329// They are all in the BMP, so we use `u16` instead of `char` to save space.
330const 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
348// The original, sorted version from Unicode.
349// <https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5Cp%7BDecomposition_Mapping%3D%2F.%CC%B8%2F%7D%26%5Cp%7BisNFC%7D>
350const PRECOMPOSED_SOLIDUS_OVERLAY_SORTED: [[u16; 2]; 88] = [
351    [0x003C, 0x226E], // LESS-THAN SIGN -> NOT LESS-THAN
352    [0x003D, 0x2260], // EQUALS SIGN -> NOT EQUAL TO
353    [0x003E, 0x226F], // GREATER-THAN SIGN -> NOT GREATER-THAN
354    [0x2190, 0x219A], // LEFTWARDS ARROW -> LEFTWARDS ARROW WITH STROKE
355    [0x2192, 0x219B], // RIGHTWARDS ARROW -> RIGHTWARDS ARROW WITH STROKE
356    [0x2194, 0x21AE], // LEFT RIGHT ARROW -> LEFT RIGHT ARROW WITH STROKE
357    [0x219A, 0x219A], // LEFTWARDS ARROW WITH STROKE
358    [0x219B, 0x219B], // RIGHTWARDS ARROW WITH STROKE
359    [0x21AE, 0x21AE], // LEFT RIGHT ARROW WITH STROKE
360    [0x21CD, 0x21CD], // LEFTWARDS DOUBLE ARROW WITH STROKE
361    [0x21CE, 0x21CE], // LEFT RIGHT DOUBLE ARROW WITH STROKE
362    [0x21CF, 0x21CF], // RIGHTWARDS DOUBLE ARROW WITH STROKE
363    [0x21D0, 0x21CD], // LEFTWARDS DOUBLE ARROW -> LEFTWARDS DOUBLE ARROW WITH STROKE
364    [0x21D2, 0x21CF], // RIGHTWARDS DOUBLE ARROW -> RIGHTWARDS DOUBLE ARROW WITH STROKE
365    [0x21D4, 0x21CE], // LEFT RIGHT DOUBLE ARROW -> LEFT RIGHT DOUBLE ARROW WITH STROKE
366    [0x2203, 0x2204], // THERE EXISTS -> THERE DOES NOT EXIST
367    [0x2204, 0x2204], // THERE DOES NOT EXIST
368    [0x2208, 0x2209], // ELEMENT OF -> NOT AN ELEMENT OF
369    [0x2209, 0x2209], // NOT AN ELEMENT OF
370    [0x220B, 0x220C], // CONTAINS AS MEMBER -> DOES NOT CONTAIN AS MEMBER
371    [0x220C, 0x220C], // DOES NOT CONTAIN AS MEMBER
372    [0x2223, 0x2224], // DIVIDES -> DOES NOT DIVIDE
373    [0x2224, 0x2224], // DOES NOT DIVIDE
374    [0x2225, 0x2226], // PARALLEL TO -> NOT PARALLEL TO
375    [0x2226, 0x2226], // NOT PARALLEL TO
376    [0x223C, 0x2241], // TILDE OPERATOR -> NOT TILDE
377    [0x2241, 0x2241], // NOT TILDE
378    [0x2243, 0x2244], // ASYMPTOTICALLY EQUAL TO -> NOT ASYMPTOTICALLY EQUAL TO
379    [0x2244, 0x2244], // NOT ASYMPTOTICALLY EQUAL TO
380    [0x2245, 0x2247], // APPROXIMATELY EQUAL TO -> NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO
381    [0x2247, 0x2247], // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO
382    [0x2248, 0x2249], // ALMOST EQUAL TO -> NOT ALMOST EQUAL TO
383    [0x2249, 0x2249], // NOT ALMOST EQUAL TO
384    [0x224D, 0x226D], // EQUIVALENT TO -> NOT EQUIVALENT TO
385    [0x2260, 0x2260], // NOT EQUAL TO
386    [0x2261, 0x2262], // IDENTICAL TO -> NOT IDENTICAL TO
387    [0x2262, 0x2262], // NOT IDENTICAL TO
388    [0x2264, 0x2270], // LESS-THAN OR EQUAL TO -> NEITHER LESS-THAN NOR EQUAL TO
389    [0x2265, 0x2271], // GREATER-THAN OR EQUAL TO -> NEITHER GREATER-THAN NOR EQUAL TO
390    [0x226D, 0x226D], // NOT EQUIVALENT TO
391    [0x226E, 0x226E], // NOT LESS-THAN
392    [0x226F, 0x226F], // NOT GREATER-THAN
393    [0x2270, 0x2270], // NEITHER LESS-THAN NOR EQUAL TO
394    [0x2271, 0x2271], // NEITHER GREATER-THAN NOR EQUAL TO
395    [0x2272, 0x2274], // LESS-THAN OR EQUIVALENT TO -> NEITHER LESS-THAN NOR EQUIVALENT TO
396    [0x2273, 0x2275], // GREATER-THAN OR EQUIVALENT TO -> NEITHER GREATER-THAN NOR EQUIVALENT TO
397    [0x2274, 0x2274], // NEITHER LESS-THAN NOR EQUIVALENT TO
398    [0x2275, 0x2275], // NEITHER GREATER-THAN NOR EQUIVALENT TO
399    [0x2276, 0x2278], // LESS-THAN OR GREATER-THAN -> NEITHER LESS-THAN NOR GREATER-THAN
400    [0x2277, 0x2279], // GREATER-THAN OR LESS-THAN -> NEITHER GREATER-THAN NOR LESS-THAN
401    [0x2278, 0x2278], // NEITHER LESS-THAN NOR GREATER-THAN
402    [0x2279, 0x2279], // NEITHER GREATER-THAN NOR LESS-THAN
403    [0x227A, 0x2280], // PRECEDES -> DOES NOT PRECEDE
404    [0x227B, 0x2281], // SUCCEEDS -> DOES NOT SUCCEED
405    [0x227C, 0x22E0], // PRECEDES OR EQUAL TO -> DOES NOT PRECEDE OR EQUAL
406    [0x227D, 0x22E1], // SUCCEEDS OR EQUAL TO -> DOES NOT SUCCEED OR EQUAL
407    [0x2280, 0x2280], // DOES NOT PRECEDE
408    [0x2281, 0x2281], // DOES NOT SUCCEED
409    [0x2282, 0x2284], // SUBSET OF -> NOT A SUBSET OF
410    [0x2283, 0x2285], // SUPERSET OF -> NOT A SUPERSET OF
411    [0x2284, 0x2284], // NOT A SUBSET OF
412    [0x2285, 0x2285], // NOT A SUPERSET OF
413    [0x2286, 0x2288], // SUBSET OF OR EQUAL TO -> NEITHER A SUBSET OF NOR EQUAL TO
414    [0x2287, 0x2289], // SUPERSET OF OR EQUAL TO -> NEITHER A SUPERSET OF NOR EQUAL TO
415    [0x2288, 0x2288], // NEITHER A SUBSET OF NOR EQUAL TO
416    [0x2289, 0x2289], // NEITHER A SUPERSET OF NOR EQUAL TO
417    [0x2291, 0x22E2], // SQUARE IMAGE OF OR EQUAL TO -> NOT SQUARE IMAGE OF OR EQUAL TO
418    [0x2292, 0x22E3], // SQUARE ORIGINAL OF OR EQUAL TO -> NOT SQUARE ORIGINAL OF OR EQUAL TO
419    [0x22A2, 0x22AC], // RIGHT TACK -> DOES NOT PROVE
420    [0x22A8, 0x22AD], // TRUE -> NOT TRUE
421    [0x22A9, 0x22AE], // FORCES -> DOES NOT FORCE
422    [0x22AB, 0x22AF], // DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE -> NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE
423    [0x22AC, 0x22AC], // DOES NOT PROVE
424    [0x22AD, 0x22AD], // NOT TRUE
425    [0x22AE, 0x22AE], // DOES NOT FORCE
426    [0x22AF, 0x22AF], // NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE
427    [0x22B2, 0x22EA], // NORMAL SUBGROUP OF -> NOT NORMAL SUBGROUP OF
428    [0x22B3, 0x22EB], // CONTAINS AS NORMAL SUBGROUP -> DOES NOT CONTAIN AS NORMAL SUBGROUP
429    [0x22B4, 0x22EC], // NORMAL SUBGROUP OF OR EQUAL TO -> NOT NORMAL SUBGROUP OF OR EQUAL TO
430    [0x22B5, 0x22ED], // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO -> DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL
431    [0x22E0, 0x22E0], // DOES NOT PRECEDE OR EQUAL
432    [0x22E1, 0x22E1], // DOES NOT SUCCEED OR EQUAL
433    [0x22E2, 0x22E2], // NOT SQUARE IMAGE OF OR EQUAL TO
434    [0x22E3, 0x22E3], // NOT SQUARE ORIGINAL OF OR EQUAL TO
435    [0x22EA, 0x22EA], // NOT NORMAL SUBGROUP OF
436    [0x22EB, 0x22EB], // DOES NOT CONTAIN AS NORMAL SUBGROUP
437    [0x22EC, 0x22EC], // NOT NORMAL SUBGROUP OF OR EQUAL TO
438    [0x22ED, 0x22ED], // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL
439];
440
441#[cfg(test)]
442mod tests {
443    use super::*;
444    use VariationSelector::*;
445
446    #[test]
447    fn solidus_table_sanity_check() {
448        // Check that the table is sorted
449        assert!(PRECOMPOSED_SOLIDUS_OVERLAY_SORTED.is_sorted_by_key(|a| a[0]));
450        // Check that the table has an even number of entries
451        assert!(PRECOMPOSED_SOLIDUS_OVERLAY_SORTED.len().is_multiple_of(2));
452        // Check that exactly half the entries are an idempotent mapping
453        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        // Check that the binary search always returns the same result as a scan.
462        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 every operation on a representative set of [`SuperChar`] values.
471    ///
472    /// `SuperChar`'s behavior depends on the base `char` only through its UTF-8
473    /// length, whether it has a precomposed solidus-overlay form, and (for
474    /// `from_char_with_vs`) whether it *is* such a precomposed form. The
475    /// variation-selector/overlay operations are base-char-independent bit
476    /// twiddling. So rather than exhaustively iterating all ~1.1M code points
477    /// (which takes ~20s in debug builds), we test a curated set that exercises
478    /// each of those cases.
479    #[test]
480    fn test_super_char() {
481        let interesting = [
482            // UTF-8 length boundaries (1/2/3/4 bytes) and surrogate boundaries
483            '\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            // some ordinary characters not in the precomposed table
494            'a',
495            'Z',
496            '0',
497            'α',
498            '∑',
499            '€',
500            '😀',
501        ]
502        .into_iter()
503        // every character participating in the precomposed solidus-overlay table,
504        // covering both the "has a precomposed form" and "is a precomposed form" cases
505        .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            // Test base character alone
514
515            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            // Test with solidus overlay
531
532            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            // Test with vertical line overlay
561
562            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            // Test with both overlays
586
587            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            // Test with variation selector.
624            // Characters with a precomposed solidus overlay shouldn't have a variation selector
625
626            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                    // Test with solidus overlay
640
641                    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                    // Test with vertical line overlay
668
669                    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                    // Test with both overlays
697
698                    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}