densa-text 0.2.5

Text for densa: font loading, glyph rasterization and a growable, allocation-free glyph atlas cache
Documentation
//! Font loading, character mapping and metrics of the bundled JetBrains Mono.

use densa_text::{Font, FontError};

fn mono() -> Font {
    Font::bundled_mono().expect("bundled font parses")
}

#[test]
fn maps_ascii_characters_to_glyphs() {
    let font = mono();
    assert!(font.glyph_id('A').is_some());
    assert!(font.glyph_id('0').is_some());
}

#[test]
fn covers_accented_latin() {
    let font = mono();
    for ch in "éüßøåçñčřžłőœŵẃẞ€".chars() {
        assert!(font.glyph_id(ch).is_some(), "missing glyph for {ch:?}");
    }
}

#[test]
fn unmapped_characters_have_no_glyph() {
    // A private-use code point no text font maps.
    assert_eq!(mono().glyph_id('\u{F8FF}'), None);
}

#[test]
fn every_glyph_has_the_same_advance_in_a_monospace_font() {
    let font = mono();
    let advance = |ch| font.advance(font.glyph_id(ch).unwrap(), 16.0);
    let zero = advance('0');
    assert!(
        (zero - 9.6).abs() < 0.01,
        "600/1000 em at 16 px, got {zero}"
    );
    assert_eq!(advance('W'), zero);
    assert_eq!(advance('ẃ'), zero);
}

#[test]
fn line_metrics_scale_with_size() {
    let font = mono();
    let small = font.line_metrics(10.0);
    let large = font.line_metrics(20.0);
    assert!(small.ascent > 0.0 && small.descent < 0.0, "{small:?}");
    assert!((large.ascent - 2.0 * small.ascent).abs() < 0.01);
    assert!(
        large.line_height() > 20.0,
        "line height must exceed the em size"
    );
}

#[test]
fn rejects_data_that_is_not_a_font() {
    let err = Font::new(b"definitely not a font".to_vec()).unwrap_err();
    assert_eq!(err, FontError::Parse);
    assert_eq!(
        err.to_string(),
        "the data is not a readable OpenType/TrueType font"
    );
}

#[test]
fn glyph_extent_bounds_the_rasterized_bitmap_without_rasterizing() {
    let font = Font::bundled_mono().unwrap();
    let w = font.glyph_id('W').unwrap();
    let [width, height] = font.glyph_extent(w, 40.0).unwrap();
    let bitmap = densa_text::rasterize(&font, w, 40.0, 0.0);
    assert!(
        (bitmap.width as f32 - width).abs() <= 2.0,
        "{width} vs {}",
        bitmap.width
    );
    assert!(
        (bitmap.height as f32 - height).abs() <= 2.0,
        "{height} vs {}",
        bitmap.height
    );
    let space = font.glyph_id(' ').unwrap();
    assert_eq!(font.glyph_extent(space, 40.0), Some([0.0, 0.0]));
}

#[test]
fn the_bundled_font_is_one_font_however_often_it_is_asked_for() {
    assert_eq!(mono().id(), mono().id(), "shares cached glyphs");
}

#[test]
fn faces_load_from_shared_bytes_without_copying() {
    let bytes: densa_text::FontBytes = std::sync::Arc::new(densa_text::BUNDLED_MONO);
    let a = Font::from_shared(bytes.clone(), 0).unwrap();
    assert_eq!(a.bytes().as_ptr(), densa_text::BUNDLED_MONO.as_ptr());
    assert_eq!(a.index(), 0);
    assert_ne!(a.id(), Font::from_shared(bytes.clone(), 0).unwrap().id());
    assert_eq!(
        Font::from_shared(bytes, 1).unwrap_err(),
        FontError::Parse,
        "no face 1"
    );
}