rizzma 1.14.1

A Rust reimplementation of the good parts of matplotlib/pyplot, with first-class wasm support.
Documentation
//! Embedded-only font sourcing.
//!
//! [`FontSource`] holds one or more font faces parsed from in-memory byte slices.
//! It performs **no** system font discovery, so it builds and runs unchanged on
//! `wasm32-unknown-unknown`. The default source is DejaVu Sans, vendored from
//! matplotlib's `mpl-data` (see `fonts/LICENSE_DEJAVU`).

use std::borrow::Cow;

use ttf_parser::{Face, GlyphId};

/// Raw bytes of the vendored DejaVu Sans face, embedded at compile time.
///
/// This is the same TTF matplotlib ships as its default sans-serif font.
#[deprecated(since = "1.14.1", note = "use `dejavu_sans_ttf()`")]
pub static DEJAVU_SANS_TTF: &[u8] = include_bytes!("fonts/DejaVuSans.ttf");

/// Raw bytes of the vendored DejaVu Sans face — the same TTF matplotlib
/// ships as its default sans-serif font.
#[must_use]
pub fn dejavu_sans_ttf() -> &'static [u8] {
    #[allow(deprecated)]
    DEJAVU_SANS_TTF
}

/// A set of embedded font faces usable for text measurement.
///
/// Faces are byte buffers — the default face borrowed from the embedded
/// font, registered ones owned; a [`Face`] is parsed on demand from a buffer.
/// No filesystem or system font database is touched, which keeps the type
/// usable under `wasm32`.
///
/// Construct the default with [`FontSource::dejavu_sans`], or build a custom set
/// and add faces with [`FontSource::register_face`].
#[derive(Clone, Default)]
pub struct FontSource {
    /// Font file buffers, in registration order. The first entry is the
    /// primary face used by measurement.
    faces: Vec<Cow<'static, [u8]>>,
}

impl FontSource {
    /// Creates an empty font source with no faces registered.
    ///
    /// Most callers want [`FontSource::dejavu_sans`] instead.
    #[must_use]
    pub fn new() -> Self {
        Self { faces: Vec::new() }
    }

    /// Creates a font source backed by the embedded DejaVu Sans face, which
    /// every such source shares rather than copies.
    #[must_use]
    pub fn dejavu_sans() -> Self {
        Self {
            faces: vec![Cow::Borrowed(dejavu_sans_ttf())],
        }
    }

    /// Registers an additional face from raw TrueType/OpenType bytes.
    ///
    /// The bytes are copied into the source so the caller need not keep them
    /// alive. The newly added face becomes available for measurement; the first
    /// registered face remains the primary one.
    pub fn register_face(&mut self, data: &[u8]) {
        self.faces.push(Cow::Owned(data.to_vec()));
    }

    /// Returns the number of registered faces.
    #[must_use]
    pub fn len(&self) -> usize {
        self.faces.len()
    }

    /// Returns `true` if no faces are registered.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.faces.is_empty()
    }

    /// Returns `true` when the primary face has an outline glyph for `ch`.
    ///
    /// Callers can use this to detect missing codepoints and fall back to an
    /// alternative character before laying out geometry. Returns `false` when no
    /// face is registered or the primary face lacks a glyph for `ch`.
    #[must_use]
    pub fn has_glyph(&self, ch: char) -> bool {
        self.primary_face()
            .and_then(|face| face.glyph_index(ch))
            .is_some()
    }

    /// Parses and returns the primary (first-registered) face, if any.
    ///
    /// Returns `None` when no face is registered or the primary buffer fails to
    /// parse as a valid font.
    #[must_use]
    pub(crate) fn primary_face(&self) -> Option<Face<'_>> {
        let bytes = self.faces.first()?;
        Face::parse(bytes, 0).ok()
    }
}

/// Horizontal kerning adjustment, in font design units, between two adjacent
/// glyphs on a baseline.
///
/// Reads the legacy `kern` table — the same source FreeType consults for its
/// default kerning, which is what matplotlib's Agg text rendering uses. GPOS
/// kerning is intentionally ignored: resolving it correctly needs a shaping
/// engine, and DejaVu Sans (like most core fonts) ships the same pairs in the
/// legacy `kern` table that FreeType reads. Returns `0.0` when the font has no
/// `kern` table or no pair value for `(left, right)`.
///
/// The value is in the same font design units as `glyph_hor_advance`, so callers
/// scale it by `font_size_px / units_per_em` alongside the advance.
#[must_use]
pub(crate) fn horizontal_kerning(face: &Face<'_>, left: GlyphId, right: GlyphId) -> f64 {
    let Some(kern) = face.tables().kern else {
        return 0.0;
    };
    // Sum the horizontal, non-state-machine subtables that cover this pair.
    // DejaVu Sans has a single format-0 horizontal subtable, so this reduces to
    // one lookup in practice, matching FreeType's behavior.
    let mut units: i32 = 0;
    for subtable in kern.subtables {
        if !subtable.horizontal || subtable.variable {
            continue;
        }
        if let Some(value) = subtable.glyphs_kerning(left, right) {
            units += i32::from(value);
        }
    }
    f64::from(units)
}

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

    /// The embedded font inflates to exactly matplotlib's DejaVuSans.ttf
    /// (756 072 bytes, SHA-256
    /// 3fdf69cabf06049ea70a00b5919340e2ce1e6d02b0cc3c4b44fb6801bd1e0d22),
    /// pinned here by its FNV-1a hash.
    #[test]
    fn embedded_font_inflates_to_the_vendored_ttf() {
        let ttf = dejavu_sans_ttf();
        assert_eq!(ttf.len(), 756_072);
        let fnv = ttf.iter().fold(0xcbf2_9ce4_8422_2325_u64, |h, &b| {
            (h ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01b3)
        });
        assert_eq!(fnv, 0xc6bb_b6aa_5af4_6d0b);
    }

    #[test]
    fn default_sources_share_the_inflated_font() {
        let (a, b) = (FontSource::dejavu_sans(), FontSource::dejavu_sans());
        assert!(std::ptr::eq(a.faces[0].as_ref(), b.faces[0].as_ref()));
    }
}