pdfrum-font 0.1.0

PDF font dictionaries, encodings, glyph mapping and outlines
//! Loading the synthesized TrueType fixtures the ladder tests drive.
//!
//! The fixtures live under `tests/fixtures/` and are generated by
//! `tests/fixtures/make.py`; see that directory's `PROVENANCE.md`. They
//! are read from disk rather than `include_bytes!`-ed so a fixture can be
//! regenerated without recompiling the crate.

/// Read one fixture by file name.
///
/// # Panics
///
/// Test-only, and a missing fixture is a broken checkout rather than a
/// runtime condition — so this panics with the path it could not read.
pub(crate) fn load(name: &str) -> Vec<u8> {
    let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures")
        .join(name);
    std::fs::read(&path).unwrap_or_else(|e| panic!("fixture {}: {e}", path.display()))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::glyphs::{CharmapId, Face};
    use std::sync::Arc;

    /// The fixtures and what each declares, mirroring their `PROVENANCE.md`.
    const FIXTURES: [(&str, u32, &[CharmapId]); 12] = [
        ("tt_unicode_31.ttf", 2, &[CharmapId::WINDOWS_UNICODE]),
        ("tt_unicode_03.ttf", 2, &[CharmapId::UNICODE_SYNTHETIC]),
        ("tt_symbol_30.ttf", 6, &[CharmapId::WINDOWS_SYMBOL]),
        ("tt_macroman_10.ttf", 8, &[CharmapId::MAC_ROMAN]),
        (
            "tt_custom_40.ttf",
            2,
            &[CharmapId {
                platform: 4,
                encoding: 0,
            }],
        ),
        (
            "tt_symbol_and_macroman.ttf",
            8,
            &[CharmapId::WINDOWS_SYMBOL, CharmapId::MAC_ROMAN],
        ),
        (
            "tt_unicode_03_and_symbol.ttf",
            6,
            &[CharmapId::UNICODE_SYNTHETIC, CharmapId::WINDOWS_SYMBOL],
        ),
        (
            "tt_unicode_31_and_symbol.ttf",
            6,
            &[CharmapId::WINDOWS_UNICODE, CharmapId::WINDOWS_SYMBOL],
        ),
        ("tt_symbol_empty.ttf", 6, &[CharmapId::WINDOWS_SYMBOL]),
        ("tt_macroman_empty.ttf", 8, &[CharmapId::MAC_ROMAN]),
        (
            "tt_sjis_and_unicode.ttf",
            6,
            &[
                CharmapId::WINDOWS_UNICODE,
                CharmapId {
                    platform: 3,
                    encoding: 2,
                },
            ],
        ),
        ("tt_named_no_cmap.ttf", 3, &[]),
    ];

    #[test]
    fn every_fixture_parses_with_the_charmaps_it_declares() {
        for (name, num_glyphs, charmaps) in FIXTURES {
            let bytes = load(name);
            let face = Face::new(Arc::from(bytes.as_slice()), 0)
                .unwrap_or_else(|| panic!("{name} must parse"));
            assert_eq!(face.units_per_em(), 1000, "{name}");
            assert_eq!(face.num_glyphs(), num_glyphs, "{name}");
            assert_eq!(face.charmaps(), charmaps.to_vec(), "{name}");
            // Every fixture is a `glyf` font.
            assert!(face.is_truetype(), "{name}");
        }
    }

    #[test]
    fn the_named_fixture_has_names_and_no_charmaps() {
        let bytes = load("tt_named_no_cmap.ttf");
        let face = Face::new(Arc::from(bytes.as_slice()), 0).expect("parses");
        assert!(face.charmaps().is_empty());
        assert!(face.has_glyph_names());
        assert_eq!(face.name_index("A"), 1);
        assert_eq!(face.name_index("B"), 2);
        assert_eq!(face.name_index("nonesuch"), 0);
    }

    #[test]
    fn the_mapped_glyphs_draw_something() {
        for (name, gid) in [
            ("tt_unicode_31.ttf", 1u16),
            ("tt_symbol_30.ttf", 5),
            ("tt_macroman_10.ttf", 7),
        ] {
            let bytes = load(name);
            let face = Face::new(Arc::from(bytes.as_slice()), 0).expect("parses");
            let path = face
                .outline(crate::Gid(gid))
                .unwrap_or_else(|| panic!("{name} glyph {gid} must draw"));
            assert!(!path.elements().is_empty(), "{name}");
        }
    }

    #[test]
    fn a_format_zero_subtable_reports_zero_rather_than_nothing_for_a_miss() {
        // Worth pinning because the ladders test `!= 0` rather than presence,
        // so the two subtable formats' different miss behavior is invisible to
        // them — but would not be to a future reader.
        let bytes = load("tt_macroman_10.ttf");
        let face = Face::new(Arc::from(bytes.as_slice()), 0).expect("parses");
        assert_eq!(face.char_index(crate::glyphs::Charmap::Index(0), 0x41), 7);
        assert_eq!(face.char_index(crate::glyphs::Charmap::Index(0), 0x42), 0);
    }
}