docling 1.104.0

DocumentConverter and format backends for docling.rs (a Rust port of docling).
Documentation
//! Glyphs of the Windows symbol fonts as Unicode (#588).
//!
//! `Symbol`, `Wingdings`, `Wingdings 2`, `Wingdings 3` and `Webdings` are
//! *symbol-encoded* fonts: their glyphs sit on the byte codes 0x20–0xFF, and
//! Word stores a character set in one of them either as that byte (`8D`), as
//! the byte raised into the Private Use Area (`U+F08D`, the cmap these fonts
//! actually carry), or as a `w:sym` element naming the font and the code. None
//! of those is text to a reader that lacks the font: python-docx hands docling
//! the PUA code point as it is (an invisible character, or a tofu box) and
//! drops `w:sym` outright, so a Symbol-font bullet, a Wingdings check mark
//! or a formula's Greek letters vanish from the Markdown. Pandoc's DOCX reader
//! maps them, and so does this module — a deliberate divergence from
//! docling, recorded in `docs/MIGRATION.md`.
//!
//! The correspondences: `Symbol` is Adobe's Symbol encoding as the Unicode
//! Consortium publishes it (`MAPPINGS/VENDORS/ADOBE/symbol.txt`; where one
//! code has several Unicode equivalents the first listed is taken, 0x20 is
//! the ordinary space and 0x6D the Greek mu rather than the micro sign), the
//! Wingdings and Webdings families are the Unicode 7.0 encoding of their
//! glyphs (L2/11-052R, the Dingbats / Miscellaneous Symbols and Arrows /
//! Transport and Map Symbols blocks and the circled digits), the same
//! tables Pandoc's `Text.Pandoc.Readers.Docx.Symbols` applies. A code with
//! no glyph in the font (or none Unicode encodes — Symbol's Adobe-private
//! bracket pieces map to Adobe's own PUA codes, as published) is left as it
//! was.

/// One of the symbol-encoded Windows fonts.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum SymbolFont {
    Symbol,
    Wingdings,
    Wingdings2,
    Wingdings3,
    Webdings,
}

impl SymbolFont {
    /// The font a `w:rFonts` / `w:sym w:font` / FFN name denotes, if it is
    /// one of the symbol-encoded ones (case-insensitive, surrounding
    /// whitespace ignored, `Wingdings2` and `Wingdings 2` alike).
    pub(crate) fn from_name(name: &str) -> Option<Self> {
        let n: String = name
            .trim()
            .chars()
            .filter(|c| !c.is_whitespace())
            .flat_map(char::to_lowercase)
            .collect();
        match n.as_str() {
            "symbol" => Some(Self::Symbol),
            "wingdings" => Some(Self::Wingdings),
            "wingdings2" => Some(Self::Wingdings2),
            "wingdings3" => Some(Self::Wingdings3),
            "webdings" => Some(Self::Webdings),
            _ => None,
        }
    }

    fn table(self) -> &'static [u32; 224] {
        match self {
            Self::Symbol => &SYMBOL,
            Self::Wingdings => &WINGDINGS,
            Self::Wingdings2 => &WINGDINGS_2,
            Self::Wingdings3 => &WINGDINGS_3,
            Self::Webdings => &WEBDINGS,
        }
    }

    /// The Unicode character for glyph `code` of this font: the byte code
    /// (0x20–0xFF), or that code in the Private Use Area (`U+F020`–`U+F0FF`,
    /// how the fonts' cmap and Word's `w:sym w:char` spell it). `None` when
    /// the code is outside that range or the font has no glyph there.
    pub(crate) fn glyph(self, code: u32) -> Option<char> {
        let code = match code {
            0xF020..=0xF0FF => code - 0xF000,
            0x20..=0xFF => code,
            _ => return None,
        };
        match self.table()[(code - 0x20) as usize] {
            0 => None,
            u => char::from_u32(u),
        }
    }

    /// `text` as set in this font: every character that is a glyph code
    /// replaced by its Unicode equivalent, the rest kept.
    #[cfg(test)]
    fn map_text(self, text: &str) -> String {
        text.chars()
            .map(|c| self.glyph(c as u32).unwrap_or(c))
            .collect()
    }
}

/// Whether `c` is a code a symbol font would re-interpret — the byte range
/// or its PUA image. Lets a caller skip the mapping for ordinary text cheaply.
pub(crate) fn is_glyph_code(c: char) -> bool {
    matches!(c as u32, 0x20..=0xFF | 0xF020..=0xF0FF)
}

// The tables: 224 entries for codes 0x20–0xFF, 0 = no glyph / no Unicode
// equivalent. Generated from the sources named in the module docs.

const SYMBOL: [u32; 224] = [
    0x00020, 0x00021, 0x02200, 0x00023, 0x02203, 0x00025, 0x00026, 0x0220B, // 0x20
    0x00028, 0x00029, 0x02217, 0x0002B, 0x0002C, 0x02212, 0x0002E, 0x0002F, // 0x28
    0x00030, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037, // 0x30
    0x00038, 0x00039, 0x0003A, 0x0003B, 0x0003C, 0x0003D, 0x0003E, 0x0003F, // 0x38
    0x02245, 0x00391, 0x00392, 0x003A7, 0x00394, 0x00395, 0x003A6, 0x00393, // 0x40
    0x00397, 0x00399, 0x003D1, 0x0039A, 0x0039B, 0x0039C, 0x0039D, 0x0039F, // 0x48
    0x003A0, 0x00398, 0x003A1, 0x003A3, 0x003A4, 0x003A5, 0x003C2, 0x003A9, // 0x50
    0x0039E, 0x003A8, 0x00396, 0x0005B, 0x02234, 0x0005D, 0x022A5, 0x0005F, // 0x58
    0x0F8E5, 0x003B1, 0x003B2, 0x003C7, 0x003B4, 0x003B5, 0x003C6, 0x003B3, // 0x60
    0x003B7, 0x003B9, 0x003D5, 0x003BA, 0x003BB, 0x003BC, 0x003BD, 0x003BF, // 0x68
    0x003C0, 0x003B8, 0x003C1, 0x003C3, 0x003C4, 0x003C5, 0x003D6, 0x003C9, // 0x70
    0x003BE, 0x003C8, 0x003B6, 0x0007B, 0x0007C, 0x0007D, 0x0223C, 0x00000, // 0x78
    0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, // 0x80
    0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, // 0x88
    0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, // 0x90
    0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, // 0x98
    0x020AC, 0x003D2, 0x02032, 0x02264, 0x02044, 0x0221E, 0x00192, 0x02663, // 0xA0
    0x02666, 0x02665, 0x02660, 0x02194, 0x02190, 0x02191, 0x02192, 0x02193, // 0xA8
    0x000B0, 0x000B1, 0x02033, 0x02265, 0x000D7, 0x0221D, 0x02202, 0x02022, // 0xB0
    0x000F7, 0x02260, 0x02261, 0x02248, 0x02026, 0x0F8E6, 0x0F8E7, 0x021B5, // 0xB8
    0x02135, 0x02111, 0x0211C, 0x02118, 0x02297, 0x02295, 0x02205, 0x02229, // 0xC0
    0x0222A, 0x02283, 0x02287, 0x02284, 0x02282, 0x02286, 0x02208, 0x02209, // 0xC8
    0x02220, 0x02207, 0x0F6DA, 0x0F6D9, 0x0F6DB, 0x0220F, 0x0221A, 0x022C5, // 0xD0
    0x000AC, 0x02227, 0x02228, 0x021D4, 0x021D0, 0x021D1, 0x021D2, 0x021D3, // 0xD8
    0x025CA, 0x02329, 0x0F8E8, 0x0F8E9, 0x0F8EA, 0x02211, 0x0F8EB, 0x0F8EC, // 0xE0
    0x0F8ED, 0x0F8EE, 0x0F8EF, 0x0F8F0, 0x0F8F1, 0x0F8F2, 0x0F8F3, 0x0F8F4, // 0xE8
    0x00000, 0x0232A, 0x0222B, 0x02320, 0x0F8F5, 0x02321, 0x0F8F6, 0x0F8F7, // 0xF0
    0x0F8F8, 0x0F8F9, 0x0F8FA, 0x0F8FB, 0x0F8FC, 0x0F8FD, 0x0F8FE, 0x00000, // 0xF8
];

const WINGDINGS: [u32; 224] = [
    0x00000, 0x1F589, 0x02702, 0x02701, 0x1F453, 0x1F56D, 0x1F56E, 0x1F56F, // 0x20
    0x1F57F, 0x02706, 0x1F582, 0x1F583, 0x1F4EA, 0x1F4EB, 0x1F4EC, 0x1F4ED, // 0x28
    0x1F4C1, 0x1F4C2, 0x1F4C4, 0x1F5CF, 0x1F5D0, 0x1F5C4, 0x0231B, 0x1F5AE, // 0x30
    0x1F5B0, 0x1F5B2, 0x1F5B3, 0x1F5B4, 0x1F5AB, 0x1F5AC, 0x02707, 0x0270D, // 0x38
    0x1F58E, 0x0270C, 0x1F44C, 0x1F44D, 0x1F44E, 0x0261C, 0x0261E, 0x0261D, // 0x40
    0x0261F, 0x1F590, 0x0263A, 0x1F610, 0x02639, 0x1F4A3, 0x02620, 0x1F3F3, // 0x48
    0x1F3F1, 0x02708, 0x0263C, 0x1F4A7, 0x02744, 0x1F546, 0x0271E, 0x1F548, // 0x50
    0x02720, 0x02721, 0x0262A, 0x0262F, 0x00950, 0x02638, 0x02648, 0x02649, // 0x58
    0x0264A, 0x0264B, 0x0264C, 0x0264D, 0x0264E, 0x0264F, 0x02650, 0x02651, // 0x60
    0x02652, 0x02653, 0x1F670, 0x1F675, 0x025CF, 0x1F53E, 0x025A0, 0x025A1, // 0x68
    0x1F790, 0x02751, 0x02752, 0x02B27, 0x029EB, 0x025C6, 0x02756, 0x02B25, // 0x70
    0x02327, 0x02BB9, 0x02318, 0x1F3F5, 0x1F3F6, 0x1F676, 0x1F677, 0x00000, // 0x78
    0x024EA, 0x02460, 0x02461, 0x02462, 0x02463, 0x02464, 0x02465, 0x02466, // 0x80
    0x02467, 0x02468, 0x02469, 0x024FF, 0x02776, 0x02777, 0x02778, 0x02779, // 0x88
    0x0277A, 0x0277B, 0x0277C, 0x0277D, 0x0277E, 0x0277F, 0x1F662, 0x1F660, // 0x90
    0x1F661, 0x1F663, 0x1F65E, 0x1F65C, 0x1F65D, 0x1F65F, 0x000B7, 0x02022, // 0x98
    0x00000, 0x026AA, 0x1F786, 0x1F788, 0x025C9, 0x025CE, 0x1F53F, 0x025AA, // 0xA0
    0x025FB, 0x1F7C2, 0x02726, 0x02605, 0x02736, 0x02734, 0x02739, 0x02735, // 0xA8
    0x02BD0, 0x02316, 0x027E1, 0x02311, 0x02BD1, 0x0272A, 0x02730, 0x1F550, // 0xB0
    0x1F551, 0x1F552, 0x1F553, 0x1F554, 0x1F555, 0x1F556, 0x1F557, 0x1F558, // 0xB8
    0x1F559, 0x1F55A, 0x1F55B, 0x02BB0, 0x02BB1, 0x02BB2, 0x02BB3, 0x02BB4, // 0xC0
    0x02BB5, 0x02BB6, 0x02BB7, 0x1F66A, 0x1F66B, 0x1F655, 0x1F654, 0x1F657, // 0xC8
    0x1F656, 0x1F650, 0x1F651, 0x1F652, 0x1F653, 0x0232B, 0x02326, 0x02B98, // 0xD0
    0x02B9A, 0x02B99, 0x02B9B, 0x02B88, 0x02B8A, 0x02B89, 0x02B8B, 0x1F868, // 0xD8
    0x1F86A, 0x1F869, 0x1F86B, 0x1F86C, 0x1F86D, 0x1F86F, 0x1F86E, 0x1F878, // 0xE0
    0x1F87A, 0x1F879, 0x1F87B, 0x1F87C, 0x1F87D, 0x1F87F, 0x1F87E, 0x021E6, // 0xE8
    0x021E8, 0x021E7, 0x021E9, 0x02B04, 0x021F3, 0x02B00, 0x02B01, 0x02B03, // 0xF0
    0x02B02, 0x1F8AC, 0x1F8AD, 0x1F5F6, 0x02714, 0x1F5F7, 0x1F5F9, 0x00000, // 0xF8
];

const WINGDINGS_2: [u32; 224] = [
    0x00000, 0x1F58A, 0x1F58B, 0x1F58C, 0x1F58D, 0x02704, 0x02700, 0x1F57E, // 0x20
    0x1F57D, 0x1F5C5, 0x1F5C6, 0x1F5C7, 0x1F5C8, 0x1F5C9, 0x1F5CA, 0x1F5CB, // 0x28
    0x1F5CC, 0x1F5CD, 0x1F4CB, 0x1F5D1, 0x1F5D4, 0x1F5B5, 0x1F5B6, 0x1F5B7, // 0x30
    0x1F5B8, 0x1F5AD, 0x1F5AF, 0x1F5B1, 0x1F592, 0x1F593, 0x1F598, 0x1F599, // 0x38
    0x1F59A, 0x1F59B, 0x1F448, 0x1F449, 0x1F59C, 0x1F59D, 0x1F59E, 0x1F59F, // 0x40
    0x1F5A0, 0x1F5A1, 0x1F446, 0x1F447, 0x1F5A2, 0x1F5A3, 0x1F591, 0x1F5F4, // 0x48
    0x02713, 0x1F5F5, 0x02611, 0x02612, 0x02612, 0x02BBE, 0x02BBF, 0x029B8, // 0x50
    0x029B8, 0x1F671, 0x1F674, 0x1F672, 0x1F673, 0x0203D, 0x1F679, 0x1F67A, // 0x58
    0x1F67B, 0x1F666, 0x1F664, 0x1F665, 0x1F667, 0x1F65A, 0x1F658, 0x1F659, // 0x60
    0x1F65B, 0x024EA, 0x02460, 0x02461, 0x02462, 0x02463, 0x02464, 0x02465, // 0x68
    0x02466, 0x02467, 0x02468, 0x02469, 0x024FF, 0x02776, 0x02777, 0x02778, // 0x70
    0x02779, 0x0277A, 0x0277B, 0x0277C, 0x0277D, 0x0277E, 0x0277F, 0x00000, // 0x78
    0x02609, 0x1F315, 0x0263D, 0x0263E, 0x02E3F, 0x0271D, 0x1F547, 0x1F55C, // 0x80
    0x1F55D, 0x1F55E, 0x1F55F, 0x1F560, 0x1F561, 0x1F562, 0x1F563, 0x1F564, // 0x88
    0x1F565, 0x1F566, 0x1F567, 0x1F668, 0x1F669, 0x02022, 0x025CF, 0x026AB, // 0x90
    0x02B24, 0x1F785, 0x1F786, 0x1F787, 0x1F788, 0x1F78A, 0x029BF, 0x025FE, // 0x98
    0x00000, 0x025FC, 0x02B1B, 0x02B1C, 0x1F791, 0x1F792, 0x1F793, 0x1F794, // 0xA0
    0x025A3, 0x1F795, 0x1F796, 0x1F797, 0x02B29, 0x02B25, 0x025C6, 0x025C7, // 0xA8
    0x1F79A, 0x025C8, 0x1F79B, 0x1F79C, 0x1F79D, 0x02B2A, 0x02B27, 0x029EB, // 0xB0
    0x025CA, 0x1F7A0, 0x025D6, 0x025D7, 0x02BCA, 0x02BCB, 0x025FC, 0x02B25, // 0xB8
    0x02B1F, 0x02BC2, 0x02B23, 0x02B22, 0x02BC3, 0x02BC4, 0x1F7A1, 0x1F7A2, // 0xC0
    0x1F7A3, 0x1F7A4, 0x1F7A5, 0x1F7A6, 0x1F7A7, 0x1F7A8, 0x1F7A9, 0x1F7AA, // 0xC8
    0x1F7AB, 0x1F7AC, 0x1F7AD, 0x1F7AE, 0x1F7AF, 0x1F7B0, 0x1F7B1, 0x1F7B2, // 0xD0
    0x1F7B3, 0x1F7B4, 0x1F7B5, 0x1F7B6, 0x1F7B7, 0x1F7B8, 0x1F7B9, 0x1F7BA, // 0xD8
    0x1F7BB, 0x1F7BC, 0x1F7BD, 0x1F7BE, 0x1F7BF, 0x1F7C0, 0x1F7C2, 0x1F7C4, // 0xE0
    0x02726, 0x1F7C9, 0x02605, 0x02736, 0x1F7CB, 0x02737, 0x1F7CF, 0x1F7D2, // 0xE8
    0x02739, 0x1F7C3, 0x1F7C7, 0x0272F, 0x1F7CD, 0x1F7D4, 0x02BCC, 0x02BCD, // 0xF0
    0x0203B, 0x02042, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, // 0xF8
];

const WINGDINGS_3: [u32; 224] = [
    0x00000, 0x02B60, 0x02B62, 0x02B61, 0x02B63, 0x02B66, 0x02B67, 0x02B69, // 0x20
    0x02B68, 0x02B70, 0x02B72, 0x02B71, 0x02B73, 0x02B76, 0x02B78, 0x02B7B, // 0x28
    0x02B7D, 0x02B64, 0x02B65, 0x02B6A, 0x02B6C, 0x02B6B, 0x02B6D, 0x02B4D, // 0x30
    0x02BA0, 0x02BA1, 0x02BA2, 0x02BA3, 0x02BA4, 0x02BA5, 0x02BA6, 0x02BA7, // 0x38
    0x02B90, 0x02B91, 0x02B92, 0x02B93, 0x02B80, 0x02B83, 0x02B7E, 0x02B7F, // 0x40
    0x02B84, 0x02B86, 0x02B85, 0x02B87, 0x02B8F, 0x02B8D, 0x02B8E, 0x02B8C, // 0x48
    0x02B6E, 0x02B6F, 0x0238B, 0x02324, 0x02303, 0x02325, 0x023B5, 0x0237D, // 0x50
    0x021EA, 0x02BB8, 0x1F8A0, 0x1F8A1, 0x1F8A2, 0x1F8A3, 0x1F8A4, 0x1F8A5, // 0x58
    0x1F8A6, 0x1F8A7, 0x1F8A8, 0x1F8A9, 0x1F8AA, 0x1F8AB, 0x02190, 0x02192, // 0x60
    0x02191, 0x02193, 0x02196, 0x02197, 0x02199, 0x02198, 0x1F858, 0x1F859, // 0x68
    0x025B2, 0x025BC, 0x025B3, 0x025BD, 0x025C4, 0x025BA, 0x025C1, 0x025B7, // 0x70
    0x025E3, 0x025E2, 0x025E4, 0x025E5, 0x1F780, 0x1F782, 0x1F781, 0x00000, // 0x78
    0x1F783, 0x025B2, 0x025BC, 0x025C0, 0x025B6, 0x02B9C, 0x02B9E, 0x02B9D, // 0x80
    0x02B9F, 0x1F810, 0x1F812, 0x1F811, 0x1F813, 0x1F814, 0x1F816, 0x1F815, // 0x88
    0x1F817, 0x1F818, 0x1F81A, 0x1F819, 0x1F81B, 0x1F81C, 0x1F81E, 0x1F81D, // 0x90
    0x1F81F, 0x1F800, 0x1F802, 0x1F801, 0x1F803, 0x1F804, 0x1F806, 0x1F805, // 0x98
    0x00000, 0x1F808, 0x1F80A, 0x1F809, 0x1F80B, 0x1F820, 0x1F822, 0x1F824, // 0xA0
    0x1F826, 0x1F828, 0x1F82A, 0x1F82C, 0x1F89C, 0x1F89D, 0x1F89E, 0x1F89F, // 0xA8
    0x1F82E, 0x1F830, 0x1F832, 0x1F834, 0x1F836, 0x1F838, 0x1F83A, 0x1F839, // 0xB0
    0x1F83B, 0x1F898, 0x1F89A, 0x1F899, 0x1F89B, 0x1F83C, 0x1F83E, 0x1F83D, // 0xB8
    0x1F83F, 0x1F840, 0x1F842, 0x1F841, 0x1F843, 0x1F844, 0x1F846, 0x1F845, // 0xC0
    0x1F847, 0x02BA8, 0x02BA9, 0x02BAA, 0x02BAB, 0x02BAC, 0x02BAD, 0x02BAE, // 0xC8
    0x02BAF, 0x1F860, 0x1F862, 0x1F861, 0x1F863, 0x1F864, 0x1F865, 0x1F867, // 0xD0
    0x1F866, 0x1F870, 0x1F872, 0x1F871, 0x1F873, 0x1F874, 0x1F875, 0x1F877, // 0xD8
    0x1F876, 0x1F880, 0x1F882, 0x1F881, 0x1F883, 0x1F884, 0x1F885, 0x1F887, // 0xE0
    0x1F886, 0x1F890, 0x1F892, 0x1F891, 0x1F893, 0x1F894, 0x1F896, 0x1F895, // 0xE8
    0x1F897, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, // 0xF0
    0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, // 0xF8
];

const WEBDINGS: [u32; 224] = [
    0x00000, 0x1F577, 0x1F578, 0x1F572, 0x1F576, 0x1F3C6, 0x1F396, 0x1F587, // 0x20
    0x1F5E8, 0x1F5E9, 0x1F5F0, 0x1F5F1, 0x1F336, 0x1F397, 0x1F67E, 0x1F67C, // 0x28
    0x1F5D5, 0x1F5D6, 0x1F5D7, 0x023F4, 0x023F5, 0x023F6, 0x023F7, 0x023EA, // 0x30
    0x023E9, 0x023EE, 0x023ED, 0x023F8, 0x023F9, 0x023FA, 0x1F5DA, 0x1F5F3, // 0x38
    0x1F6E0, 0x1F3D7, 0x1F3D8, 0x1F3D9, 0x1F3DA, 0x1F3DC, 0x1F3ED, 0x1F3DB, // 0x40
    0x1F3E0, 0x1F3D6, 0x1F3DD, 0x1F6E3, 0x1F50D, 0x1F3D4, 0x1F441, 0x1F442, // 0x48
    0x1F3DE, 0x1F3D5, 0x1F6E4, 0x1F3DF, 0x1F6F3, 0x1F56C, 0x1F56B, 0x1F568, // 0x50
    0x1F508, 0x1F394, 0x1F395, 0x1F5EC, 0x1F67D, 0x1F5ED, 0x1F5EA, 0x1F5EB, // 0x58
    0x02B94, 0x02714, 0x1F6B2, 0x025A1, 0x1F6E1, 0x1F4E6, 0x1F6F1, 0x025A0, // 0x60
    0x1F691, 0x1F6C8, 0x1F6E9, 0x1F6F0, 0x1F7C8, 0x1F574, 0x026AB, 0x1F6E5, // 0x68
    0x1F694, 0x1F5D8, 0x1F5D9, 0x02753, 0x1F6F2, 0x1F687, 0x1F68D, 0x026F3, // 0x70
    0x1F6C7, 0x02296, 0x1F6AD, 0x1F5EE, 0x0007C, 0x1F5EF, 0x1F5F2, 0x00000, // 0x78
    0x1F6B9, 0x1F6BA, 0x1F6C9, 0x1F6CA, 0x1F6BC, 0x1F47D, 0x1F3CB, 0x026F7, // 0x80
    0x1F3C2, 0x1F3CC, 0x1F3CA, 0x1F3C4, 0x1F3CD, 0x1F3CE, 0x1F698, 0x1F5E0, // 0x88
    0x1F6E2, 0x1F4B0, 0x1F3F7, 0x1F4B3, 0x1F46A, 0x1F5E1, 0x1F5E2, 0x1F5E3, // 0x90
    0x0272F, 0x1F584, 0x1F585, 0x1F583, 0x1F586, 0x1F5B9, 0x1F5BA, 0x1F5BB, // 0x98
    0x00000, 0x1F570, 0x1F5BD, 0x1F5BE, 0x1F4CB, 0x1F5D2, 0x1F5D3, 0x1F4D6, // 0xA0
    0x1F4DA, 0x1F5DE, 0x1F5DF, 0x1F5C3, 0x1F5C2, 0x1F5BC, 0x1F3AD, 0x1F39C, // 0xA8
    0x1F398, 0x1F399, 0x1F3A7, 0x1F4BF, 0x1F39E, 0x1F4F7, 0x1F39F, 0x1F3AC, // 0xB0
    0x1F4FD, 0x1F4F9, 0x1F4FE, 0x1F4FB, 0x1F39A, 0x1F39B, 0x1F4FA, 0x1F4BB, // 0xB8
    0x1F5A5, 0x1F5A6, 0x1F5A7, 0x1F579, 0x1F3AE, 0x1F57B, 0x1F57C, 0x1F4DF, // 0xC0
    0x1F581, 0x1F580, 0x1F5A8, 0x1F5A9, 0x1F5BF, 0x1F5AA, 0x1F5DC, 0x1F512, // 0xC8
    0x1F513, 0x1F5DD, 0x1F4E5, 0x1F4E4, 0x1F573, 0x1F323, 0x1F324, 0x1F325, // 0xD0
    0x1F326, 0x02601, 0x1F327, 0x1F328, 0x1F329, 0x1F32A, 0x1F32C, 0x1F32B, // 0xD8
    0x1F31C, 0x1F321, 0x1F6CB, 0x1F6CF, 0x1F37D, 0x1F378, 0x1F6CE, 0x1F6CD, // 0xE0
    0x024C5, 0x0267F, 0x1F6C6, 0x1F588, 0x1F393, 0x1F5E4, 0x1F5E5, 0x1F5E6, // 0xE8
    0x1F5E7, 0x1F6EA, 0x1F43F, 0x1F426, 0x1F41F, 0x1F415, 0x1F408, 0x1F66C, // 0xF0
    0x1F66E, 0x1F66D, 0x1F66F, 0x1F5FA, 0x1F30D, 0x1F30F, 0x1F30E, 0x1F54A, // 0xF8
];

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn names_resolve_case_and_space_insensitively() {
        assert_eq!(SymbolFont::from_name("Symbol"), Some(SymbolFont::Symbol));
        assert_eq!(
            SymbolFont::from_name(" wingdings "),
            Some(SymbolFont::Wingdings)
        );
        assert_eq!(
            SymbolFont::from_name("Wingdings 2"),
            Some(SymbolFont::Wingdings2)
        );
        assert_eq!(
            SymbolFont::from_name("Wingdings3"),
            Some(SymbolFont::Wingdings3)
        );
        assert_eq!(
            SymbolFont::from_name("WEBDINGS"),
            Some(SymbolFont::Webdings)
        );
        assert_eq!(SymbolFont::from_name("Arial"), None);
        assert_eq!(SymbolFont::from_name("Symbola"), None);
    }

    /// The reporter's glyphs (#588), byte code and PUA spelling alike, as
    /// Pandoc 3.12 renders them.
    #[test]
    fn glyphs_match_pandoc() {
        assert_eq!(SymbolFont::Symbol.glyph(0xF0B7), Some('\u{2022}')); // •
        assert_eq!(SymbolFont::Symbol.glyph(0xB7), Some('\u{2022}'));
        assert_eq!(SymbolFont::Wingdings.glyph(0xF0A7), Some('\u{25AA}')); // ▪
        assert_eq!(SymbolFont::Wingdings.glyph(0xF0E8), Some('\u{1F87A}')); // 🡺
        assert_eq!(SymbolFont::Wingdings.glyph(0x8D), Some('\u{2777}')); // ❷
        assert_eq!(SymbolFont::Wingdings.glyph(0xF06C), Some('\u{25CF}')); // ●
        assert_eq!(SymbolFont::Wingdings.glyph(0xF0FC), Some('\u{2714}')); // ✔
        assert_eq!(SymbolFont::Symbol.glyph(0xF061), Some('α'));
        assert_eq!(SymbolFont::Symbol.map_text("a b p"), "α β π");
        assert_eq!(
            SymbolFont::Symbol.map_text("\u{f061}\u{f062}\u{f070}"),
            "αβπ"
        );
        // Greek letters, not their compatibility look-alikes.
        assert_eq!(SymbolFont::Symbol.glyph(0x6D), Some('μ'));
        assert_eq!(SymbolFont::Symbol.glyph(0x44), Some('Δ'));
        assert_eq!(SymbolFont::Symbol.glyph(0x20), Some(' '));
    }

    #[test]
    fn codes_without_a_glyph_are_left_alone() {
        assert_eq!(
            SymbolFont::Wingdings.glyph(0x20),
            None,
            "Wingdings has no glyph on the space"
        );
        assert_eq!(SymbolFont::Symbol.glyph(0x7F), None);
        assert_eq!(SymbolFont::Symbol.glyph(0x1F), None);
        assert_eq!(SymbolFont::Symbol.glyph(0x100), None);
        assert_eq!(SymbolFont::Symbol.glyph(0xF100), None);
        // Every byte code of a Wingdings run is a glyph (`\u{e9}`, 0xE9, is an
        // arrow there); a character outside the byte range stays.
        let glyph_x = SymbolFont::Wingdings.glyph(0x78).unwrap();
        assert_eq!(
            SymbolFont::Wingdings.map_text("x\u{20AC}\u{e01}"),
            format!("{glyph_x}\u{20AC}\u{e01}")
        );
    }
}