/** * Renders the panel in each candidate typeface, at 1 bit, plus a magnified * crop of the week strip — the smallest text on the screen and the only part * whose legibility is in question. * * npm run screen:fonts * * Choosing a face for a 1-bit panel is done by looking at it thresholded. A * specimen sheet shows anti-aliased outlines the panel will never render. */ import { mkdir, writeFile } from 'node:fs/promises'; import path from 'node:path'; import sharp from 'sharp'; import type { ScreenPayload } from '../lib/screen/contract'; import { encodeScreen } from '../lib/screen/encode'; import type { SansFamily } from '../lib/screen/fonts'; import type { WeekPlacement } from '../lib/screen/layout'; import { renderScreenSvg } from '../lib/screen/render'; const PAYLOAD: ScreenPayload = { schema: 1, generated_at: '2026-09-22T12:00:00+02:00', shop_name: 'ITA ITO', logo_url: '', today: { date_fr: 'Mercredi 23 septembre', date_en: 'Wednesday 23 September', status: 'OPEN', status_fr: 'OUVERT', status_en: 'OPEN', hours_text: '11:00 – 18:00', next_change_fr: 'Ferme à 18:00', next_change_en: 'Closes at 6:00 pm', is_exception: false, }, week: [ { label_fr: 'Lun', label_en: 'Mon', hours_text: '', is_today: false, is_closed: true, is_exception: false }, { label_fr: 'Mar', label_en: 'Tue', hours_text: '11:00 – 18:00', is_today: false, is_closed: false, is_exception: false }, { label_fr: 'Mer', label_en: 'Wed', hours_text: '11:00 – 18:00', is_today: true, is_closed: false, is_exception: false }, { label_fr: 'Jeu', label_en: 'Thu', hours_text: '11:00 – 13:00 · 14:00 – 18:00', is_today: false, is_closed: false, is_exception: false }, { label_fr: 'Ven', label_en: 'Fri', hours_text: '14:00 – 18:00', is_today: false, is_closed: false, is_exception: true }, { label_fr: 'Sam', label_en: 'Sat', hours_text: '10:00 – 17:00', is_today: false, is_closed: false, is_exception: false }, { label_fr: 'Dim', label_en: 'Sun', hours_text: '', is_today: false, is_closed: true, is_exception: false }, ], banner: { kind: 'MESSAGE', text_fr: 'Fermeture exceptionnelle jeudi matin', text_en: 'Exceptionally closed on Thursday morning', }, }; /** * The worst case the data model allows: three slots in a day, none of them on * a round hour, so nothing can be shortened. If a layout survives this it * survives anything the shop can enter. */ const CROWDED: ScreenPayload = { ...PAYLOAD, today: { ...PAYLOAD.today, hours_text: '09:30 – 12:00 · 13:30 – 16:00 · 17:00 – 19:30', next_change_fr: 'Ferme à 12:00', next_change_en: 'Closes at 12:00 pm', }, week: PAYLOAD.week.map((day, index) => index === 2 ? { ...day, hours_text: '09:30 – 12:00 · 13:30 – 16:00 · 17:00 – 19:30' } : index === 3 ? { ...day, hours_text: '10:30 – 18:30' } : day, ), }; const VARIANTS: { key: string; label: string; family: SansFamily; placement: WeekPlacement; payload?: ScreenPayload; }[] = [ { key: 'bas', label: 'Bande en bas (actuelle)', family: 'inter', placement: 'bottom' }, { key: 'droite', label: 'Bande à droite', family: 'inter', placement: 'right' }, { key: 'gauche', label: 'Bande à gauche', family: 'inter', placement: 'left' }, { key: 'limite-droite', label: 'Pire cas, bande à droite', family: 'inter', placement: 'right', payload: CROWDED }, { key: 'limite-bas', label: 'Pire cas, bande en bas', family: 'inter', placement: 'bottom', payload: CROWDED }, ]; async function main(): Promise { const directory = path.join(process.cwd(), '.preview', 'fonts'); await mkdir(directory, { recursive: true }); for (const { key, label, family, placement, payload } of VARIANTS) { const png = encodeScreen( await renderScreenSvg(payload ?? PAYLOAD, { family, placement }), 'png', ); await writeFile(path.join(directory, `${key}.png`), png); // Magnified with nearest-neighbour on the week area: the point is to see // the pixels the panel will actually set, not a smoothed impression. const crop = placement === 'bottom' ? { left: 0, top: 284, width: 800, height: 116 } : placement === 'right' ? { left: 800 - 232, top: 0, width: 232, height: 480 } : { left: 0, top: 0, width: 232, height: 480 }; await sharp(png) .extract(crop) .resize({ width: crop.width * 2, kernel: 'nearest' }) .toFile(path.join(directory, `${key}-zoom.png`)); console.log(`${label.padEnd(26)} -> .preview/fonts/${key}.png (+ -zoom.png)`); } } main().catch((error: unknown) => { console.error(error); process.exitCode = 1; });