daegun 1.1.6

Your all-in-one text engine. Rust, no_std, zero dependencies.
Documentation
use std::time::{Duration, Instant};

use daegun::daecore::daetype::outline::OutlinePen;
use daegun::daerizer::daecpu::math::{Geometry, Glyph};
use daegun::daerizer::daecpu::rasterize::{metrics_raw, Raster};
use daegun::daecore::daemachine::subpixel::{StripeOrder, SubpixelLayout};
use daegun::daecore::daetype::TableBytes;

const WARMUP: usize = 40;
const ROUNDS: usize = 60;

struct Face {
    map: std::collections::BTreeMap<String, TableBytes>,
    loca: Vec<usize>,
    upm: f32,
}

fn face(rel: &str) -> Face {
    let path = format!("{}/{}", crate::FONTS, rel);
    let bytes = std::fs::read(&path).unwrap_or_else(|e| panic!("fixture missing: {path} ({e})"));
    let map = daegun::daecore::daetype::decoder::extract_ttf_tables(&bytes).expect("parses");
    let head = map.get("head").expect("head");
    let fmt = daegun::daecore::daetype::decoder::read_i16_be(head, 50).expect("locaFmt");
    let upm = f32::from(daegun::daecore::daetype::decoder::read_u16_be(head, 18).expect("upm"));
    let n = daegun::daecore::daetype::decoder::read_u16_be(map.get("maxp").expect("maxp"), 4).expect("n");
    let loca = daegun::daecore::daetype::instancer::parse_loca(&map, fmt, n as usize).expect("loca");
    Face { map, loca, upm }
}

fn gid_for(f: &Face, c: char) -> u16 {
    let cmap = f.map.get("cmap").expect("cmap");
    daegun::daecore::daetype::subsetter::cmap_glyph_id(cmap, c as u32).unwrap_or_else(|| panic!("no {c}"))
}

fn pen_ops(f: &Face, gid: u16) -> usize {
    #[derive(Default)]
    struct Count(usize);
    impl OutlinePen for Count {
        fn move_to(&mut self, _: f32, _: f32) { self.0 += 1; }
        fn line_to(&mut self, _: f32, _: f32) { self.0 += 1; }
        fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) { self.0 += 1; }
        fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) { self.0 += 1; }
        fn close(&mut self) { self.0 += 1; }
    }
    let mut c = Count::default();
    daegun::daecore::daetype::outline::outline_glyf_glyph_with_loca(&f.map, &f.loca, gid, &mut c).ok();
    c.0
}

fn flatten(f: &Face, gid: u16, px: f32) -> Glyph {
    let mut g = Geometry::new(px, f.upm);
    daegun::daecore::daetype::outline::outline_glyf_glyph_with_loca(&f.map, &f.loca, gid, &mut g)
        .expect("the glyph draws");
    let mut out = Glyph::default();
    g.finalize(&mut out);
    out
}

fn median(v: &mut [Duration]) -> Duration {
    v.sort();
    v[v.len() / 2]
}

fn stages(name: &str, f: &Face, gid: u16, px: f32, layout: &SubpixelLayout, gamma: Option<&[u8; 256]>) {
    let glyph = flatten(f, gid, px);
    let scale = px / f.upm;
    let (metrics, ox, oy) = metrics_raw(scale, glyph.bounds, 0.0, 0.0, 0.0);
    let (pad_x, pad_y) = layout.pad();
    let w = metrics.width + pad_x * 2;
    let h = metrics.height + pad_y * 2;
    let (sx, sy) = layout.oversample();
    if w == 0 || h == 0 {
        eprintln!("  {name}: empty box, skipped");
        return;
    }

    #[derive(Default)]
    struct NullPen(usize);
    impl OutlinePen for NullPen {
        fn move_to(&mut self, _: f32, _: f32) { self.0 += 1; }
        fn line_to(&mut self, _: f32, _: f32) { self.0 += 1; }
        fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) { self.0 += 1; }
        fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) { self.0 += 1; }
        fn close(&mut self) { self.0 += 1; }
    }

    let (mut t_dec, mut t_flat, mut t_alloc, mut t_draw, mut t_res) =
        (Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new());
    let mut proof = 0usize;
    for round in 0..WARMUP + ROUNDS {
        let t = Instant::now();
        let mut np = NullPen::default();
        daegun::daecore::daetype::outline::outline_glyf_glyph_with_loca(&f.map, &f.loca, gid, &mut np).ok();
        let e_dec = t.elapsed();
        core::hint::black_box(&np.0);

        let t = Instant::now();
        let g = flatten(f, gid, px);
        let e_flat = t.elapsed();

        let t = Instant::now();
        let mut raster = Raster::new(w * sx as usize, h * sy as usize);
        let e_alloc = t.elapsed();

        let t = Instant::now();
        raster.draw(&g, scale * sx as f32, scale * sy as f32,
                    (ox + pad_x as f32) * sx as f32, (oy + pad_y as f32) * sy as f32);
        let e_draw = t.elapsed();

        let t = Instant::now();
        let bytes = raster.resolve(w, h, layout, gamma);
        let e_res = t.elapsed();

        proof += bytes.len() + g.v_segments.len();
        core::hint::black_box(&bytes);
        if round >= WARMUP {
            t_dec.push(e_dec);
            t_flat.push(e_flat.saturating_sub(e_dec));
            t_alloc.push(e_alloc);
            t_draw.push(e_draw);
            t_res.push(e_res);
        }
    }
    assert!(proof > 0, "{name}: the pipeline produced nothing");

    let dec = median(&mut t_dec);
    let f_ = median(&mut t_flat);
    let a_ = median(&mut t_alloc);
    let d_ = median(&mut t_draw);
    let r_ = median(&mut t_res);
    let total = f_ + a_ + d_ + r_;
    let pct = |d: Duration| d.as_secs_f64() / total.as_secs_f64() * 100.0;
    let us = |d: Duration| d.as_secs_f64() * 1e6;
    eprintln!(
        "  {name:26} {w:>4}x{h:<4} raster {:>7.3}us | flatten {:>6.3} ({:>4.1}%)  alloc {:>6.3} ({:>4.1}%)  draw {:>6.3} ({:>4.1}%)  resolve {:>7.3} ({:>4.1}%)   [decode {:>5.3}us, daetype's]",
        us(total), us(f_), pct(f_), us(a_), pct(a_), us(d_), pct(d_), us(r_), pct(r_), us(dec),
    );
}

#[test]
#[ignore]
fn cpu_pipeline_by_size() {
    let f = face("eb-garamond/EBGaramond.ttf");
    let gray = SubpixelLayout::grayscale();
    eprintln!("cpu_pipeline_by_size – EBGaramond 'B', grayscale, no gamma");
    for gid_char in ['B'] {
        let gid = gid_for(&f, gid_char);
        eprintln!("    ({gid_char} is {} pen operations)", pen_ops(&f, gid));
        for px in [12.0f32, 16.0, 24.0, 32.0, 64.0, 128.0, 256.0] {
            stages(&format!("{gid_char} at {px}px"), &f, gid, px, &gray, None);
        }
    }
}

#[test]
#[ignore]
fn cpu_pipeline_by_glyph() {
    let f = face("eb-garamond/EBGaramond.ttf");
    let gray = SubpixelLayout::grayscale();
    eprintln!("cpu_pipeline_by_glyph – 16px, grayscale, no gamma");
    for c in ['.', 'l', 'o', 'B', 'g', 'W', '@'] {
        let gid = gid_for(&f, c);
        stages(&format!("{c} ({} ops)", pen_ops(&f, gid)), &f, gid, 16.0, &gray, None);
    }
}

#[test]
#[ignore]
fn cpu_pipeline_by_layout() {
    let f = face("eb-garamond/EBGaramond.ttf");
    let gid = gid_for(&f, 'B');
    let lut = daegun::daerizer::daecpu::platform::gamma_lut(2.2);
    eprintln!("cpu_pipeline_by_layout – EBGaramond 'B' at 16px");
    for (name, layout, gamma) in [
        ("grayscale", SubpixelLayout::grayscale(), None),
        ("grayscale + gamma", SubpixelLayout::grayscale(), Some(&lut)),
        ("subpixel RGB", SubpixelLayout::horizontal(StripeOrder::Rgb), None),
        ("subpixel RGB + gamma", SubpixelLayout::horizontal(StripeOrder::Rgb), Some(&lut)),
    ] {
        stages(name, &f, gid, 16.0, &layout, gamma);
    }
    eprintln!("  (the same at 64px)");
    for (name, layout, gamma) in [
        ("grayscale", SubpixelLayout::grayscale(), None),
        ("subpixel RGB", SubpixelLayout::horizontal(StripeOrder::Rgb), None),
    ] {
        stages(name, &f, gid, 64.0, &layout, gamma);
    }
}