1
//! Font registration for the plotters backends.
2
//!
3
//! The charts are drawn by binaries that ship as *static musl* executables in a
4
//! `FROM scratch` image, where fontconfig cannot be reached at all: a static
5
//! binary has no dynamic loader, so plotters' `ttf` backend panicked inside
6
//! `yeslogic-fontconfig-sys` ("Dynamic loading not supported") before it could
7
//! even look for a face. That panic escaped [`crate::svg::render_svg`]'s
8
//! fallback, which only catches `Err`.
9
//!
10
//! So the face is compiled in and registered with plotters' pure-Rust
11
//! `ab_glyph` backend instead. No host fonts, no fontconfig, no `dlopen`.
12

            
13
use std::sync::OnceLock;
14

            
15
use plotters::style::{FontStyle, register_font};
16

            
17
/// The family name every chart asks for (see `draw.rs`).
18
const FAMILY: &str = "sans-serif";
19

            
20
/// DejaVu Sans covers Latin, Greek, Cyrillic and common symbols — not CJK. Chart
21
/// labels are user text (account, category and tag names), so a label outside
22
/// that coverage rasterises as missing-glyph boxes in the native PNG/kitty
23
/// output. The browser still falls back for SVG, since it shapes the `<text>`
24
/// nodes itself. Vendoring a CJK face would add tens of megabytes to every
25
/// binary; revisit if such labels are actually in use.
26
static DEJAVU_SANS: &[u8] = include_bytes!("../fonts/DejaVuSans.ttf");
27

            
28
static REGISTERED: OnceLock<bool> = OnceLock::new();
29

            
30
/// Register the embedded face, once per process. Returns whether every style
31
/// was accepted; a `false` here means text drawing will fail and callers fall
32
/// back to their degraded rendering.
33
///
34
/// Every style maps to the same regular face: only the regular one is vendored,
35
/// and a missing style is a drawing error rather than a heavier stroke, so bold
36
/// text renders as regular by design.
37
14
pub(crate) fn ensure_registered() -> bool {
38
14
    *REGISTERED.get_or_init(|| {
39
1
        [
40
1
            FontStyle::Normal,
41
1
            FontStyle::Bold,
42
1
            FontStyle::Italic,
43
1
            FontStyle::Oblique,
44
1
        ]
45
1
        .into_iter()
46
4
        .all(|style| register_font(FAMILY, style, DEJAVU_SANS).is_ok())
47
1
    })
48
14
}
49

            
50
#[cfg(test)]
51
mod tests {
52
    use super::*;
53

            
54
    #[test]
55
1
    fn embedded_face_registers_for_every_style() {
56
1
        assert!(
57
1
            ensure_registered(),
58
            "embedded font rejected by plotters — charts would degrade to title-only"
59
        );
60
1
    }
61

            
62
    #[test]
63
1
    fn registration_is_idempotent() {
64
1
        assert!(ensure_registered());
65
1
        assert!(ensure_registered());
66
1
    }
67
}