lmjtfy.git / packages / card / src / lib.rs
lib.rsannotatedlib.rssource388 lines · 16.2 KB · raw

The picture a link unfurls with: the question and Jev's answer, drawn the way the page looks, as a PNG. No I/O.

It is drawn by hand, a pixel at a time, in an 8 by 8 bitmap face scaled up. That is the page's look (dither, window frames, pixel type), and it needs no font engine: a Worker has ten milliseconds to make this, and a megabyte or two less to carry. Sixteen colours, four bits a pixel: the page's four, and what coloured pixel art (pixel) brings.

9#![forbid(unsafe_code)]
11pub mod pixel;
12
13use archive::{Answer, Detail};
14use font8x8::UnicodeFonts;
15
16pub const WIDTH: usize = 600;
17pub const HEIGHT: usize = 315;

The first four palette entries, as the page's --paper, --ink, --pink and --grey. Pixel art adds its own after them.

21const PALETTE: [[u8; 3]; 4] = [[0xfe, 0xfe, 0xfe], [0x1e, 0x1e, 0x1e], [0xf3, 0x86, 0xa1], [0xde, 0xde, 0xde]];

What a four-bit palette holds.

23const COLOURS: usize = 16;
24const PAPER: u8 = 0;
25const INK: u8 = 1;
26const PINK: u8 = 2;
27const GREY: u8 = 3;

What the card is of.

30#[derive(Clone, Copy, Debug, PartialEq)]
31pub enum Card<'a> {

The site itself.

33    Home,

A question, and what Jev said: nothing, if it has not been asked.

35    Question { input: &'a str, said: &'a [Answer] },

A question Jev cannot take (not a question, or nothing it can be asked): the question, and the way to an LLM that can. Nothing on it says Jev answered.

39    Declined { input: &'a str },

A repository's code: its name (lmjtfy.git), the clone command for url (with submodules when it has some), what it is, and the latest commit if it could be read.

43    Code { name: &'a str, url: &'a str, recurse: bool, about: &'a str, branch: &'a str, latest: Option<&'a Commit> },
44}

The latest commit on the code's main branch, as the card and the page show it.

48#[derive(Clone, Debug, PartialEq)]
49pub struct Commit {

The full hash.

51    pub sha: String,

The message's first line.

53    pub subject: String,

When it was committed, as YYYY-MM-DD.

55    pub date: String,

How many commits main has, if GitHub said.

57    pub count: Option<u64>,
58}
60impl Commit {
61    pub fn short(&self) -> &str {
62        self.sha.get(..7).unwrap_or(&self.sha)
63    }

4d93353 · 2026-10-02 · 140 commits.

66    pub fn line(&self) -> String {
67        let mut parts = vec![self.short().to_owned(), self.date.clone()];
68        parts.extend(self.count.map(|count| format!("{count} commits")));
69        parts.join(" · ")
70    }
71}
73struct Canvas {

Palette indices.

75    pixels: Vec<u8>,
76    palette: Vec<[u8; 3]>,
77}

A 4 by 4 ordered dither: how dark a field has to be before each cell of the pattern takes ink.

81const BAYER: [[u8; 4]; 4] = [[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]];
83impl Canvas {
84    fn new() -> Self {
85        Canvas { pixels: vec![PAPER; WIDTH * HEIGHT], palette: PALETTE.to_vec() }
86    }

The palette index of rgb, added if it is new. A seventeenth colour is drawn in ink: the cards here have seven or so.

90    fn colour(&mut self, rgb: [u8; 3]) -> u8 {
91        match self.palette.iter().position(|seen| *seen == rgb) {
92            Some(at) => at as u8,
93            None if self.palette.len() < COLOURS => {
94                self.palette.push(rgb);
95                (self.palette.len() - 1) as u8
96            }
97            None => INK,
98        }
99    }

icon with its top left at x, y, each of its pixels scale across.

102    fn art(&mut self, x: usize, y: usize, icon: &pixel::Icon, scale: usize) {
103        for cell in &icon.cells {
104            let colour = self.colour(cell.rgb);
105            self.fill(x + cell.x * scale, y + cell.y * scale, scale, scale, colour);
106        }
107    }
109    fn fill(&mut self, x: usize, y: usize, w: usize, h: usize, colour: u8) {
110        for row in y..(y + h).min(HEIGHT) {
111            for column in x..(x + w).min(WIDTH) {
112                self.pixels[row * WIDTH + column] = colour;
113            }
114        }
115    }
116
117    fn frame(&mut self, x: usize, y: usize, w: usize, h: usize, colour: u8) {
118        self.fill(x, y, w, 1, colour);
119        self.fill(x, y + h - 1, w, 1, colour);
120        self.fill(x, y, 1, h, colour);
121        self.fill(x + w - 1, y, 1, h, colour);
122    }

The page's dot field: dense at the top left and bottom right corners, thinning to nothing across the middle. Dots are 2 by 2.

126    fn dither(&mut self, x0: usize, y0: usize, w: usize, h: usize, colour: u8) {
127        for y in (y0..y0 + h).step_by(2) {
128            for x in (x0..x0 + w).step_by(2) {
129                let across = (x - x0) as f64 / w as f64;
130                let down = (y - y0) as f64 / h as f64;
131                let from_corner = (across + down).min(2.0 - across - down);
132                let dark = (1.0 - from_corner * 1.6).max(0.0) * 16.0;
133                if f64::from(BAYER[(y / 2) % 4][(x / 2) % 4]) < dark {
134                    self.fill(x, y, 2, 2, colour);
135                }
136            }
137        }
138    }

One line of text, each font pixel scale across. Returns its width.

141    fn text(&mut self, x: usize, y: usize, text: &str, scale: usize, colour: u8) -> usize {
142        for (index, ch) in text.chars().enumerate() {
143            let glyph = glyph(ch);
144            for (row, bits) in glyph.iter().enumerate() {
145                for column in 0..8 {
146                    if bits >> column & 1 == 1 {
147                        self.fill(x + (index * 8 + column) * scale, y + row * scale, scale, scale, colour);
148                    }
149                }
150            }
151        }
152        width(text, scale)
153    }
154}

A character's 8 rows, bit 0 leftmost. One the face lacks is drawn as ?.

157fn glyph(ch: char) -> [u8; 8] {
158    font8x8::BASIC_FONTS
159        .get(ch)
160        .or_else(|| font8x8::LATIN_FONTS.get(ch))
161        .or_else(|| font8x8::BASIC_FONTS.get('?'))
162        .unwrap_or([0; 8])
163}
165fn width(text: &str, scale: usize) -> usize {
166    text.chars().count() * 8 * scale
167}

text cut to chars, the cut marked.

170fn cut(text: &str, chars: usize) -> String {
171    if text.chars().count() <= chars {
172        return text.to_owned();
173    }
174    let kept: String = text.chars().take(chars.saturating_sub(3)).collect();
175    format!("{}...", kept.trim_end())
176}

text broken at spaces into at most lines lines of at most chars, the last cut if there is more. A word longer than a line is cut.

180fn wrap(text: &str, chars: usize, lines: usize) -> Vec<String> {
181    let mut wrapped: Vec<String> = Vec::new();
182    let mut words = text.split_whitespace().peekable();
183    while let Some(word) = words.next() {
184        let fits = wrapped.last().is_some_and(|line| line.chars().count() + 1 + word.chars().count() <= chars);
185        if fits {
186            let line = wrapped.last_mut().expect("a line was seen to fit");
187            line.push(' ');
188            line.push_str(word);
189        } else if wrapped.len() < lines {
190            wrapped.push(cut(word, chars));
191        } else {
192            // No room for another line: the last one says there was more.
193            let last = wrapped.last_mut().expect("lines is at least one");
194            *last = cut(&format!("{last} {word}"), chars);
195            if words.peek().is_some() && !last.ends_with("...") {
196                *last = cut(&format!("{last} ..."), chars);
197            }
198            break;
199        }
200    }
201    wrapped
202}

The largest scale, at most most, at which text is no wider than room.

205fn fit(text: &str, room: usize, most: usize) -> usize {
206    (room / (text.chars().count().max(1) * 8)).clamp(1, most)
207}

The logos of the LLMs the card points to, as pixel reads them.

210const LOGOS: [&str; 3] = [pixel::CLAUDE, pixel::CHATGPT, pixel::GEMINI];
212const MARGIN: usize = 20;
213const BAR: usize = 28;
214const BAND: usize = 40;
215
216fn draw(card: Card<'_>) -> Canvas {
217    let mut canvas = Canvas::new();
218    canvas.dither(0, 0, WIDTH, HEIGHT - BAND, GREY);
219    // The pink band along the bottom, with its own darker dots.
220    canvas.fill(0, HEIGHT - BAND, WIDTH, BAND, PINK);
221    canvas.dither(0, HEIGHT - BAND, WIDTH, BAND, INK);
222    let site = "LET ME JEV THAT FOR YOU";
223    let site_x = (WIDTH - width(site, 2)) / 2;
224    canvas.fill(site_x - 8, HEIGHT - BAND + 8, width(site, 2) + 16, 24, PINK);
225    canvas.text(site_x, HEIGHT - BAND + 12, site, 2, INK);
226
227    // The window.
228    let (x, y) = (MARGIN, MARGIN);
229    let (w, h) = (WIDTH - 2 * MARGIN, HEIGHT - BAND - 2 * MARGIN);
230    canvas.fill(x, y, w, h, PAPER);
231    canvas.frame(x, y, w, h, INK);
232    canvas.fill(x, y, w, BAR, INK);
233    let title = if let Card::Code { name, .. } = card { name } else { "Jev" };
234    canvas.text(x + 10, y + 6, title, 2, PAPER);
235    let inner = x + 16;
236    let room = w - 32;
237    let top = y + BAR + 14;
238
239    match card {
240        Card::Home => {
241            let scale = 5;
242            for (line, words) in ["Let Me Jev", "That For You"].iter().enumerate() {
243                canvas.text(inner + (room - width(words, scale)) / 2, top + 22 + line * 52, words, scale, INK);
244            }
245            let ask = "Ask it anything. It says yes, no, which or how much.";
246            canvas.text(inner + (room - width(ask, 1)) / 2, top + 140, ask, 1, INK);
247        }
248        Card::Question { input, said } => {
249            // The question, small, up to two lines.
250            let label = canvas.text(inner, top, "Q:", 2, INK) + 12;
251            let lines = wrap(input, (room - label) / 16, 2);
252            for (line, words) in lines.iter().enumerate() {
253                canvas.text(inner + label, top + line * 20, words, 2, INK);
254            }
255            let mut at = top + lines.len().max(1) * 20 + 10;
256            if said.is_empty() {
257                canvas.text(inner, at + 30, "Let me Jev that for you.", 2, INK);
258                return canvas;
259            }
260            // The answer, as large as fits the width, up to 48 high.
261            let headlines: Vec<&str> = said.iter().map(|answer| answer.headline.as_str()).collect();
262            let headline = cut(&headlines.join(" "), 66);
263            let scale = fit(&headline, room, 6);
264            canvas.text(inner, at, &headline, scale, INK);
265            at += 8 * scale + 8;
266            // Its type and numbers, then its distribution: half of what Jev says.
267            let notes: Vec<String> = said.iter().filter_map(Answer::note).collect();
268            if !notes.is_empty() {
269                canvas.text(inner, at, &cut(&notes.join(" / "), room / 8), 1, INK);
270                at += 18;
271            }
272            if let [answer] = said {
273                detail(&mut canvas, inner, at, room, &answer.detail);
274            }
275        }
276        Card::Declined { input } => {
277            let label = canvas.text(inner, top, "Q:", 2, INK) + 12;
278            let lines = wrap(input, (room - label) / 16, 2);
279            for (line, words) in lines.iter().enumerate() {
280                canvas.text(inner + label, top + line * 20, words, 2, INK);
281            }
282            // The name as the site spells it, in a badge like its own card's.
283            let name = "LMMPTFY";
284            let scale = 3;
285            let badge = (width(name, scale) + 24, 8 * scale + 12);
286            let badge_at = (inner + (room - badge.0) / 2, top + 62);
287            canvas.fill(badge_at.0, badge_at.1, badge.0, badge.1, PINK);
288            canvas.frame(badge_at.0, badge_at.1, badge.0, badge.1, INK);
289            canvas.text(badge_at.0 + 12, badge_at.1 + 6, name, scale, INK);
290            // The three logos, seven card pixels to an icon pixel.
291            let (scale, gap) = (7, 40);
292            let logos = LOGOS.map(|svg| pixel::Icon::parse(svg).expect("the vendored icons are readable"));
293            let side = logos.iter().map(|icon| icon.size * scale).max().unwrap_or_default();
294            let row = logos.len() * side + (logos.len() - 1) * gap;
295            for (at, icon) in logos.iter().enumerate() {
296                canvas.art(inner + (room - row) / 2 + at * (side + gap), badge_at.1 + badge.1 + 18, icon, scale);
297            }
298        }
299        Card::Code { url, recurse, about, branch, latest, .. } => {
300            canvas.text(inner, top + 4, "Get the code", 4, INK);
301            // The command, white on black, as a terminal shows it.
302            let (box_y, box_h) = (top + 50, 44);
303            canvas.fill(inner, box_y, room, box_h, INK);
304            canvas.text(inner + 10, box_y + 10, if recurse { "$ git clone --recurse-submodules" } else { "$ git clone" }, 1, PAPER);
305            canvas.text(inner + 10, box_y + 26, &cut(&format!("  {url}"), (room - 20) / 8), 1, PAPER);
306            let mut at = box_y + box_h + 14;
307            if let Some(latest) = latest {
308                canvas.text(inner, at, &cut(&format!("{branch} @ {}", latest.line()), room / 8), 1, INK);
309                canvas.text(inner, at + 14, &cut(&latest.subject, room / 8), 1, INK);
310                at += 34;
311            }
312            canvas.text(inner, at, &cut(about, room / 8), 1, INK);
313        }
314    }
315    canvas
316}

A line with a square on it at p: the page's slider.

319fn slider(canvas: &mut Canvas, x: usize, y: usize, w: usize, p: f64) {
320    canvas.fill(x, y + 4, w, 1, INK);
321    let thumb = x + ((w - 9) as f64 * p.clamp(0.0, 1.0)).round() as usize;
322    canvas.fill(thumb, y, 9, 9, INK);
323}

An answer's numbers, drawn: where a yes-or-no fell between no and yes, the likeliest options of a pick, every level of a how-much.

327fn detail(canvas: &mut Canvas, x: usize, y: usize, room: usize, detail: &Detail) {
328    match detail {
329        Detail::Unknown => {}
330        Detail::Noul { p_yes: p } | Detail::Chance { p } => {
331            let (low, high) = if matches!(detail, Detail::Noul { .. }) { ("no", "yes") } else { ("0%", "100%") };
332            canvas.text(x, y, low, 1, INK);
333            canvas.text(x + room - width(high, 1), y, high, 1, INK);
334            slider(canvas, x, y + 12, room, *p);
335        }
336        Detail::Choice { options, .. } => {
337            for (row, (label, p)) in options.iter().take(3).enumerate() {
338                let at = y + row * 22;
339                let percent = format!("{:.0}%", p * 100.0);
340                canvas.text(x, at, &cut(label, (room - 48) / 8), 1, INK);
341                canvas.text(x + room - width(&percent, 1), at, &percent, 1, INK);
342                slider(canvas, x, at + 10, room, *p);
343            }
344        }
345        Detail::Score { score, levels, .. } => {
346            // One column a level, as tall as it is likely beside the
347            // likeliest; the nearest in ink.
348            let most = levels.iter().map(|(_, p)| *p).fold(0.0, f64::max).max(f64::EPSILON);
349            let count = levels.len().max(1);
350            let nearest = (score.round().max(0.0) as usize).min(count - 1);
351            let (gap, tall) = (6, 34);
352            let column = (room - gap * (count - 1)) / count;
353            for (level, (_, p)) in levels.iter().enumerate() {
354                let height = ((tall as f64) * (p / most).clamp(0.0, 1.0)).round() as usize;
355                let left = x + level * (column + gap);
356                canvas.fill(left, y + tall - height.max(1), column, height.max(1), if level == nearest { INK } else { GREY });
357                canvas.frame(left, y + tall - height.max(1), column, height.max(1), INK);
358            }
359            canvas.fill(x, y + tall, room, 1, INK);
360            if let (Some((lowest, _)), Some((highest, _))) = (levels.first(), levels.last()) {
361                let half = room / 16 - 1;
362                let highest = cut(highest, half);
363                canvas.text(x, y + tall + 5, &cut(lowest, half), 1, INK);
364                canvas.text(x + room - width(&highest, 1), y + tall + 5, &highest, 1, INK);
365            }
366        }
367    }
368}

The card as a PNG.

371pub fn png(card: Card<'_>) -> Vec<u8> {
372    let canvas = draw(card);
373    // Four bits a pixel, two pixels a byte, the first in the high bits.
374    let packed: Vec<u8> = canvas.pixels.chunks(2).map(|two| two[0] << 4 | two.get(1).copied().unwrap_or(0)).collect();
375    let mut out = Vec::new();
376    let mut encoder = png::Encoder::new(&mut out, WIDTH as u32, HEIGHT as u32);
377    encoder.set_color(png::ColorType::Indexed);
378    encoder.set_depth(png::BitDepth::Four);
379    encoder.set_palette(canvas.palette.concat());
380    encoder.set_compression(png::Compression::Fast);
381    let mut writer = encoder.write_header().expect("the header is valid for these fixed settings");
382    writer.write_image_data(&packed).expect("the data is the size the header says");
383    writer.finish().expect("writing to a Vec cannot fail");
384    out
385}
387#[cfg(test)]
388mod tests;