agentmux_launcher/
splash_text.rs

1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Software text blitter for the splash footer — shared by the Linux software
5//! buffers (`splash_linux`) and the Win32 layered-window DIB (`splash.rs`).
6//! macOS uses native `NSTextField` instead, so it does not use this module.
7//!
8//! Composites grayscale glyph coverage (`splash_font`) into a **pre-multiplied**
9//! 4-bytes-per-pixel buffer (the same format `render_frame` / the DIB produce):
10//! `dst = dst*(1-a) + color*a`, with `a = coverage * window_alpha`, so the footer
11//! fades in/out with the rest of the card.
12
13use crate::splash_font::{COVERAGE, FIRST, GLYPH_H, GLYPH_W, LAST};
14
15/// Pixel width of `s` rendered in the monospace glyph cell.
16pub fn text_width(s: &str) -> i32 {
17    s.chars().count() as i32 * GLYPH_W as i32
18}
19
20#[inline]
21fn glyph_index(ch: char) -> usize {
22    let code = ch as u32;
23    if code >= FIRST as u32 && code <= LAST as u32 {
24        (code - FIRST as u32) as usize
25    } else {
26        ('?' as u32 - FIRST as u32) as usize // unknown glyph → '?'
27    }
28}
29
30/// Blit `text` into `buf` (pre-multiplied, `buf_w`×`buf_h`, 4 bpp) with top-left
31/// at (`x0`, `y0`). `color` is straight RGB; `window_alpha` scales the whole draw
32/// (fade); `bgr` selects byte order (`true` → B,G,R,A as Wayland ARGB8888-on-LE /
33/// X11 ARGB / Win32 BGRA expect, `false` → R,G,B,A).
34pub fn draw_text(
35    buf: &mut [u8],
36    buf_w: i32,
37    buf_h: i32,
38    x0: i32,
39    y0: i32,
40    text: &str,
41    color: [u8; 3],
42    window_alpha: f32,
43    bgr: bool,
44) {
45    let (cr, cg, cb) = (color[0] as f32, color[1] as f32, color[2] as f32);
46    let (o0, o2) = if bgr { (cb, cr) } else { (cr, cb) };
47    let mut pen_x = x0;
48    for ch in text.chars() {
49        let base = glyph_index(ch) * GLYPH_H * GLYPH_W;
50        for gy in 0..GLYPH_H as i32 {
51            let py = y0 + gy;
52            if py < 0 || py >= buf_h {
53                continue;
54            }
55            for gx in 0..GLYPH_W as i32 {
56                let px = pen_x + gx;
57                if px < 0 || px >= buf_w {
58                    continue;
59                }
60                let cov = COVERAGE[base + gy as usize * GLYPH_W + gx as usize] as f32 / 255.0;
61                if cov <= 0.0 {
62                    continue;
63                }
64                let a = cov * window_alpha;
65                let di = ((py * buf_w + px) * 4) as usize;
66                // Pre-multiplied over-composite.
67                buf[di] = (buf[di] as f32 * (1.0 - a) + o0 * a) as u8;
68                buf[di + 1] = (buf[di + 1] as f32 * (1.0 - a) + cg * a) as u8;
69                buf[di + 2] = (buf[di + 2] as f32 * (1.0 - a) + o2 * a) as u8;
70                buf[di + 3] = (buf[di + 3] as f32 * (1.0 - a) + 255.0 * a) as u8;
71            }
72        }
73        pen_x += GLYPH_W as i32;
74    }
75}
76
77/// Horizontally-centered [`draw_text`] at row `y0`.
78pub fn draw_text_centered(
79    buf: &mut [u8],
80    buf_w: i32,
81    buf_h: i32,
82    y0: i32,
83    text: &str,
84    color: [u8; 3],
85    window_alpha: f32,
86    bgr: bool,
87) {
88    let x0 = (buf_w - text_width(text)) / 2;
89    draw_text(buf, buf_w, buf_h, x0, y0, text, color, window_alpha, bgr);
90}