Skip to main content

math_core_renderer_internal/
super_char.rs

1use std::{
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#[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            // SAFETY: `PRECOMPOSED_SOLIDUS_OVERLAY` contains only valid Unicode characters
298            .map(|idx| unsafe {
299                char::from_u32_unchecked(PRECOMPOSED_SOLIDUS_OVERLAY.0[idx][1].into())
300            })
301    } else {
302        None
303    }
304}
305
306/// Whether this character has a canonical decomposition to a sequence containing U+338.
307/// Used in a debug assertion; should not be used in release because it is slow
308const fn is_precomposed_solidus_overlay_for_debug(c: char) -> bool {
309    // can't use `binary_search` in `const`,
310    // so we do a linear one
311
312    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))] // align to cache line
330struct Align128<T>(T);
331
332// This is a mapping from Unicode codepoints to their precomposed solidus-overlay variant.
333// They are all in the BMP, so we use `u16` instead of `char` to save space.
334// <https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5Cp%7BDecomposition_Mapping%3D%2F.%CC%B8%2F%7D%26%5Cp%7BisNFC%7D>
335const PRECOMPOSED_SOLIDUS_OVERLAY: Align128<[[u16; 2]; 88]> = Align128([
336    [0x003C, 0x226E], // LESS-THAN SIGN -> NOT LESS-THAN
337    [0x003D, 0x2260], // EQUALS SIGN -> NOT EQUAL TO
338    [0x003E, 0x226F], // GREATER-THAN SIGN -> NOT GREATER-THAN
339    [0x2190, 0x219A], // LEFTWARDS ARROW -> LEFTWARDS ARROW WITH STROKE
340    [0x2192, 0x219B], // RIGHTWARDS ARROW -> RIGHTWARDS ARROW WITH STROKE
341    [0x2194, 0x21AE], // LEFT RIGHT ARROW -> LEFT RIGHT ARROW WITH STROKE
342    [0x219A, 0x219A], // LEFTWARDS ARROW WITH STROKE
343    [0x219B, 0x219B], // RIGHTWARDS ARROW WITH STROKE
344    [0x21AE, 0x21AE], // LEFT RIGHT ARROW WITH STROKE
345    [0x21CD, 0x21CD], // LEFTWARDS DOUBLE ARROW WITH STROKE
346    [0x21CE, 0x21CE], // LEFT RIGHT DOUBLE ARROW WITH STROKE
347    [0x21CF, 0x21CF], // RIGHTWARDS DOUBLE ARROW WITH STROKE
348    [0x21D0, 0x21CD], // LEFTWARDS DOUBLE ARROW -> LEFTWARDS DOUBLE ARROW WITH STROKE
349    [0x21D2, 0x21CF], // RIGHTWARDS DOUBLE ARROW -> RIGHTWARDS DOUBLE ARROW WITH STROKE
350    [0x21D4, 0x21CE], // LEFT RIGHT DOUBLE ARROW -> LEFT RIGHT DOUBLE ARROW WITH STROKE
351    [0x2203, 0x2204], // THERE EXISTS -> THERE DOES NOT EXIST
352    [0x2204, 0x2204], // THERE DOES NOT EXIST
353    [0x2208, 0x2209], // ELEMENT OF -> NOT AN ELEMENT OF
354    [0x2209, 0x2209], // NOT AN ELEMENT OF
355    [0x220B, 0x220C], // CONTAINS AS MEMBER -> DOES NOT CONTAIN AS MEMBER
356    [0x220C, 0x220C], // DOES NOT CONTAIN AS MEMBER
357    [0x2223, 0x2224], // DIVIDES -> DOES NOT DIVIDE
358    [0x2224, 0x2224], // DOES NOT DIVIDE
359    [0x2225, 0x2226], // PARALLEL TO -> NOT PARALLEL TO
360    [0x2226, 0x2226], // NOT PARALLEL TO
361    [0x223C, 0x2241], // TILDE OPERATOR -> NOT TILDE
362    [0x2241, 0x2241], // NOT TILDE
363    [0x2243, 0x2244], // ASYMPTOTICALLY EQUAL TO -> NOT ASYMPTOTICALLY EQUAL TO
364    [0x2244, 0x2244], // NOT ASYMPTOTICALLY EQUAL TO
365    [0x2245, 0x2247], // APPROXIMATELY EQUAL TO -> NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO
366    [0x2247, 0x2247], // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO
367    [0x2248, 0x2249], // ALMOST EQUAL TO -> NOT ALMOST EQUAL TO
368    [0x2249, 0x2249], // NOT ALMOST EQUAL TO
369    [0x224D, 0x226D], // EQUIVALENT TO -> NOT EQUIVALENT TO
370    [0x2260, 0x2260], // NOT EQUAL TO
371    [0x2261, 0x2262], // IDENTICAL TO -> NOT IDENTICAL TO
372    [0x2262, 0x2262], // NOT IDENTICAL TO
373    [0x2264, 0x2270], // LESS-THAN OR EQUAL TO -> NEITHER LESS-THAN NOR EQUAL TO
374    [0x2265, 0x2271], // GREATER-THAN OR EQUAL TO -> NEITHER GREATER-THAN NOR EQUAL TO
375    [0x226D, 0x226D], // NOT EQUIVALENT TO
376    [0x226E, 0x226E], // NOT LESS-THAN
377    [0x226F, 0x226F], // NOT GREATER-THAN
378    [0x2270, 0x2270], // NEITHER LESS-THAN NOR EQUAL TO
379    [0x2271, 0x2271], // NEITHER GREATER-THAN NOR EQUAL TO
380    [0x2272, 0x2274], // LESS-THAN OR EQUIVALENT TO -> NEITHER LESS-THAN NOR EQUIVALENT TO
381    [0x2273, 0x2275], // GREATER-THAN OR EQUIVALENT TO -> NEITHER GREATER-THAN NOR EQUIVALENT TO
382    [0x2274, 0x2274], // NEITHER LESS-THAN NOR EQUIVALENT TO
383    [0x2275, 0x2275], // NEITHER GREATER-THAN NOR EQUIVALENT TO
384    [0x2276, 0x2278], // LESS-THAN OR GREATER-THAN -> NEITHER LESS-THAN NOR GREATER-THAN
385    [0x2277, 0x2279], // GREATER-THAN OR LESS-THAN -> NEITHER GREATER-THAN NOR LESS-THAN
386    [0x2278, 0x2278], // NEITHER LESS-THAN NOR GREATER-THAN
387    [0x2279, 0x2279], // NEITHER GREATER-THAN NOR LESS-THAN
388    [0x227A, 0x2280], // PRECEDES -> DOES NOT PRECEDE
389    [0x227B, 0x2281], // SUCCEEDS -> DOES NOT SUCCEED
390    [0x227C, 0x22E0], // PRECEDES OR EQUAL TO -> DOES NOT PRECEDE OR EQUAL
391    [0x227D, 0x22E1], // SUCCEEDS OR EQUAL TO -> DOES NOT SUCCEED OR EQUAL
392    [0x2280, 0x2280], // DOES NOT PRECEDE
393    [0x2281, 0x2281], // DOES NOT SUCCEED
394    [0x2282, 0x2284], // SUBSET OF -> NOT A SUBSET OF
395    [0x2283, 0x2285], // SUPERSET OF -> NOT A SUPERSET OF
396    [0x2284, 0x2284], // NOT A SUBSET OF
397    [0x2285, 0x2285], // NOT A SUPERSET OF
398    [0x2286, 0x2288], // SUBSET OF OR EQUAL TO -> NEITHER A SUBSET OF NOR EQUAL TO
399    [0x2287, 0x2289], // SUPERSET OF OR EQUAL TO -> NEITHER A SUPERSET OF NOR EQUAL TO
400    [0x2288, 0x2288], // NEITHER A SUBSET OF NOR EQUAL TO
401    [0x2289, 0x2289], // NEITHER A SUPERSET OF NOR EQUAL TO
402    [0x2291, 0x22E2], // SQUARE IMAGE OF OR EQUAL TO -> NOT SQUARE IMAGE OF OR EQUAL TO
403    [0x2292, 0x22E3], // SQUARE ORIGINAL OF OR EQUAL TO -> NOT SQUARE ORIGINAL OF OR EQUAL TO
404    [0x22A2, 0x22AC], // RIGHT TACK -> DOES NOT PROVE
405    [0x22A8, 0x22AD], // TRUE -> NOT TRUE
406    [0x22A9, 0x22AE], // FORCES -> DOES NOT FORCE
407    [0x22AB, 0x22AF], // DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE -> NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE
408    [0x22AC, 0x22AC], // DOES NOT PROVE
409    [0x22AD, 0x22AD], // NOT TRUE
410    [0x22AE, 0x22AE], // DOES NOT FORCE
411    [0x22AF, 0x22AF], // NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE
412    [0x22B2, 0x22EA], // NORMAL SUBGROUP OF -> NOT NORMAL SUBGROUP OF
413    [0x22B3, 0x22EB], // CONTAINS AS NORMAL SUBGROUP -> DOES NOT CONTAIN AS NORMAL SUBGROUP
414    [0x22B4, 0x22EC], // NORMAL SUBGROUP OF OR EQUAL TO -> NOT NORMAL SUBGROUP OF OR EQUAL TO
415    [0x22B5, 0x22ED], // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO -> DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL
416    [0x22E0, 0x22E0], // DOES NOT PRECEDE OR EQUAL
417    [0x22E1, 0x22E1], // DOES NOT SUCCEED OR EQUAL
418    [0x22E2, 0x22E2], // NOT SQUARE IMAGE OF OR EQUAL TO
419    [0x22E3, 0x22E3], // NOT SQUARE ORIGINAL OF OR EQUAL TO
420    [0x22EA, 0x22EA], // NOT NORMAL SUBGROUP OF
421    [0x22EB, 0x22EB], // DOES NOT CONTAIN AS NORMAL SUBGROUP
422    [0x22EC, 0x22EC], // NOT NORMAL SUBGROUP OF OR EQUAL TO
423    [0x22ED, 0x22ED], // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL
424]);
425
426#[cfg(test)]
427mod tests {
428    use super::*;
429    use VariationSelector::*;
430
431    #[test]
432    fn solidus_table_sanity_check() {
433        // Check that the table is sorted
434        assert!(PRECOMPOSED_SOLIDUS_OVERLAY.0.is_sorted_by_key(|a| a[0]));
435        // Check that the table has an even number of entries
436        assert!(PRECOMPOSED_SOLIDUS_OVERLAY.0.len().is_multiple_of(2));
437        // Check that exactly half the entries are an idempotent mapping
438        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 every operation on a representative set of [`SuperChar`] values.
450    ///
451    /// `SuperChar`'s behavior depends on the base `char` only through its UTF-8
452    /// length, whether it has a precomposed solidus-overlay form, and (for
453    /// `from_char_with_vs`) whether it *is* such a precomposed form. The
454    /// variation-selector/overlay operations are base-char-independent bit
455    /// twiddling. So rather than exhaustively iterating all ~1.1M code points
456    /// (which takes ~20s in debug builds), we test a curated set that exercises
457    /// each of those cases.
458    #[test]
459    fn test_super_char() {
460        let interesting = [
461            // UTF-8 length boundaries (1/2/3/4 bytes) and surrogate boundaries
462            '\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            // some ordinary characters not in the precomposed table
473            'a',
474            'Z',
475            '0',
476            'α',
477            '∑',
478            '€',
479            '😀',
480        ]
481        .into_iter()
482        // every character participating in the precomposed solidus-overlay table,
483        // covering both the "has a precomposed form" and "is a precomposed form" cases
484        .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            // Test base character alone
494
495            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            // Test with solidus overlay
511
512            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            // Test with vertical line overlay
541
542            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            // Test with both overlays
566
567            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            // Test with variation selector.
604            // Characters with a precomposed solidus overlay shouldn't have a variation selector
605
606            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                    // Test with solidus overlay
620
621                    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                    // Test with vertical line overlay
648
649                    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                    // Test with both overlays
677
678                    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}