| 1 | import { readFile, writeFile, mkdir } from 'node:fs/promises'; |
| 2 | import { fileURLToPath } from 'node:url'; |
| 3 | |
| 4 | const root = new URL('./', import.meta.url); |
| 5 | const icons = JSON.parse(await readFile(new URL('icons.json', root), 'utf8')); |
| 6 | const bodies = new Map(); |
| 7 | for (const item of icons) { |
| 8 | const source = await readFile(new URL(`upstream/${item.icon}.svg`, root), 'utf8'); |
| 9 | bodies.set(item.id, source.match(/<svg[^>]*>([\s\S]*?)<\/svg>/)[1].trim()); |
| 10 | } |
| 11 | const glyph = (item, x, y, size) => `<g transform="translate(${x} ${y}) scale(${size / 24})" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${bodies.get(item.id)}</g>`; |
| 12 | // All seven use the same 24-unit Lucide canvas, occupying 70% of the tile. |
| 13 | const tile = (item, x, y, size, radius = 0) => `<rect x="${x}" y="${y}" width="${size}" height="${size}" rx="${radius}" fill="${item.colour}"/>${glyph(item, x + size * .15, y + size * .15, size * .7)}`; |
| 14 | const svg = (width, height, content, title) => `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${title}"><title>${title}</title>${content}</svg>\n`; |
| 15 | await mkdir(new URL('svg/', root), { recursive: true }); |
| 16 | for (const item of icons) { |
| 17 | await writeFile(new URL(`svg/${item.id}.svg`, root), svg(1024, 1024, tile(item, 0, 0, 1024), item.name)); |
| 18 | } |
| 19 | let sheet = `<rect width="1400" height="790" fill="#F6F5F2"/><g font-family="Arial, sans-serif"><text x="64" y="77" font-size="32" fill="#252932" font-weight="700">Oficina</text><text x="64" y="111" font-size="16" fill="#606571">One family. Seven marks. White Lucide symbols on solid colour.</text>`; |
| 20 | for (let i = 0; i < icons.length; i++) { |
| 21 | const item = icons[i]; |
| 22 | const x = 64 + i * 184; |
| 23 | sheet += tile(item, x, 165, 144, 30); |
| 24 | sheet += `<text x="${x}" y="345" font-size="20" font-weight="700" fill="#252932">${item.name}</text><text x="${x}" y="372" font-size="13" fill="#606571">${item.icon}</text><text x="${x}" y="395" font-size="13" fill="#606571">${item.colour}</text>`; |
| 25 | for (const [size, y] of [[48, 450], [32, 525], [16, 588]]) sheet += tile(item, x, y, size, size * .21); |
| 26 | } |
| 27 | sheet += `<text x="64" y="666" font-size="14" fill="#606571">Actual sizes above: 48, 32 and 16 px. Rounded corners are a preview mask only.</text><text x="64" y="733" font-size="14" fill="#606571">Square, opaque masters · Shared padding · 2-unit strokes · No gradients or shadows</text></g>`; |
| 28 | await writeFile(new URL('preview.svg', root), svg(1400, 790, sheet, 'Oficina app icon set')); |
| 29 | console.log('Generated seven SVG masters and preview.svg'); |
| 30 | |
| 31 | if (process.argv.includes('--render')) { |
| 32 | const { chromium } = await import('playwright'); |
| 33 | const browser = await chromium.launch({ headless: true }); |
| 34 | try { |
| 35 | const page = await browser.newPage({ viewport: { width: 1400, height: 790 }, deviceScaleFactor: 1 }); |
| 36 | await page.goto(new URL('preview.svg', root).href); |
| 37 | await page.screenshot({ path: fileURLToPath(new URL('preview.png', root)) }); |
| 38 | await mkdir(new URL('png/', root), { recursive: true }); |
| 39 | for (const item of icons) { |
| 40 | const source = await readFile(new URL(`svg/${item.id}.svg`, root), 'utf8'); |
| 41 | for (const size of [16, 32, 48, 180, 192, 512, 1024]) { |
| 42 | const png = await page.evaluate(async ({ source, size }) => { |
| 43 | const img = new Image(); |
| 44 | img.src = 'data:image/svg+xml;base64,' + btoa(source); |
| 45 | await img.decode(); |
| 46 | const canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas'); |
| 47 | canvas.width = canvas.height = size; |
| 48 | canvas.getContext('2d', { alpha: false }).drawImage(img, 0, 0, size, size); |
| 49 | return canvas.toDataURL('image/png').split(',')[1]; |
| 50 | }, { source, size }); |
| 51 | await writeFile(new URL(`png/${item.id}-${size}.png`, root), Buffer.from(png, 'base64')); |
| 52 | } |
| 53 | } |
| 54 | console.log('Rendered preview.png and seven PNG sizes per app'); |
| 55 | } finally { |
| 56 | await browser.close(); |
| 57 | } |
| 58 | } |
| 59 | |