#!/usr/bin/env node /** * balanced-fit probe: find the font size and explicit line breaks that give a * headline N balanced lines (no runt first line, no widow last line) inside a * fixed-width box — without relying on CSS `text-wrap: balance` support. * * Usage: node fit-title-probe.mjs * Prints a table for the sample titles so the values can be baked into the renderer. */ import { execSync } from 'node:child_process'; import { writeFileSync, mkdirSync } from 'node:fs'; import { join } from 'node:path'; const CHROME = process.env.CHROME_PATH || 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'; const OUT = 'C:/Users/hoelee/Desktop/blog-og-image-redesign-brief/samples-v2'; mkdirSync(OUT, { recursive: true }); const TITLES = [ ['en', 'Running Production Infrastructure Solo: 162 Containers Across Three Hosts'], ['en', 'Docker Reported Exit 0. The Kernel Had Already OOM-Killed It.'], ['zh', '独自运维 162 个生产容器:两起故障改变了我验证数字的方式'], ['zh', 'Docker 报 Exit 0,内核早已 OOM 杀死了它'], ]; const probe = `
`; const html = probe.replace('TITLES_JSON', JSON.stringify(TITLES)); const p = join(OUT, 'fit-probe.html'); writeFileSync(p, html, 'utf8'); const dom = execSync( `"${CHROME}" --headless --dump-dom --virtual-time-budget=3000 --disable-gpu "file:///${p.replace(/\\/g, '/')}"`, { stdio: 'pipe', maxBuffer: 32 * 1024 * 1024 } ).toString('utf8'); const m = dom.match(/RESULT(.*?)<\/title>/s); if (!m) { console.error('probe did not return a result'); process.exit(1); } const rows = JSON.parse(m[1].replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')); for (const r of rows) { console.log(`\n[${r.lang}] ${r.t}`); if (!r.out) { console.log(' NO FIT'); continue; } console.log(` px=${r.out.px} lines=${r.out.lines.length} widths=[${r.out.widths.map(x => Math.round(x)).join(', ')}]`); r.out.lines.forEach((l) => console.log(` | ${l}`)); }