inkferro-core 0.1.0

Layout, text measurement, ANSI render, and frame-diff engine for inkferro — a Rust-backed, byte-for-byte drop-in for the ink terminal UI library.
Documentation
//! Criterion microbenches for `text::string_width` — the width measure fed to
//! Taffy's measure func (layout hot path).
//!
//! Input classes (each a `string_width/<class>` bench id):
//! - `ascii`      — pure printable ASCII (whole-string fast path).
//! - `mixed_cjk`  — ASCII prose with interleaved CJK (per-grapheme path).
//! - `cjk`        — pure CJK ideographs.
//! - `hangul`     — modern L+V(+T) jamo syllable runs.
//! - `emoji_zwj`  — ZWJ sequences, VS16, modifiers, flags.
//! - `ansi`       — SGR-laden styled ASCII (strip + fast path).
//!
//! Inputs are built ONCE outside the timed loop; expected widths are asserted
//! once up front so a behavior drift fails the bench instead of timing
//! different work.

use std::hint::black_box;

use criterion::{Criterion, criterion_group, criterion_main};
use inkferro_core::text::string_width::string_width;

fn inputs() -> Vec<(&'static str, String, usize)> {
    let ascii = "The quick brown fox jumps over the lazy dog 0123456789 !@#$%^&*()".repeat(8);
    let ascii_w = ascii.chars().count();

    let mixed = "Item 中文 listing — value: 42 (見出し) done? yes 漢字 end.".repeat(8);
    let cjk = "中文漢字測試字符串寬度計算引擎".repeat(16);
    let hangul = "\u{1100}\u{1161}\u{11A8}\u{1102}\u{1163}\u{1100}\u{1161}".repeat(24);
    let emoji = "👨\u{200D}👩\u{200D}👧 ✌\u{FE0F} 👍🏽 🇺🇸 😀 ❤\u{200D}🔥 1\u{FE0F}\u{20E3}".repeat(8);
    let ansi = "\x1b[1m\x1b[31mError:\x1b[0m something \x1b[4mfailed\x1b[0m badly. ".repeat(16);

    vec![
        ("ascii", ascii, ascii_w),
        // Expected widths pinned against Node string-width@8.2.1 (oracle probe).
        ("mixed_cjk", mixed, 464),
        ("cjk", cjk, 480),
        ("hangul", hangul, 144),
        ("emoji_zwj", emoji, 160),
        ("ansi", ansi, 496),
    ]
}

fn bench_string_width(c: &mut Criterion) {
    let cases = inputs();

    // Pin expected widths once, outside timing: a drift means the bench would
    // silently time different behavior.
    for (name, input, expected) in &cases {
        assert_eq!(
            string_width(input),
            *expected,
            "string_width/{name} fixture drifted"
        );
    }

    let mut group = c.benchmark_group("string_width");
    for (name, input, _) in &cases {
        group.bench_function(*name, |b| {
            b.iter(|| black_box(string_width(black_box(input))));
        });
    }
    group.finish();
}

criterion_group!(benches, bench_string_width);
criterion_main!(benches);