Files
butcem/scripts/generate-icons.ts
T

91 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Uygulama ikonlarını logodan üretir: npm run icons
*
* Kaynak: public/brand/icon.png (kare, en az 512×512).
* Çıktılar (commit edilir):
* src/app/favicon.ico 16/32/48 px (PNG gömülü ICO)
* src/app/icon.png 192 px → <link rel="icon">
* src/app/apple-icon.png 180 px, saydamsız → iOS ana ekran
* public/icons/icon-192.png PWA manifest
* public/icons/icon-512.png PWA manifest
* public/icons/maskable-512.png PWA manifest (purpose: maskable, %80 güvenli alan)
*
* Logoyu değiştirdikten sonra bu betiği yeniden çalıştırın.
*/
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import sharp from "sharp";
const ROOT = process.cwd();
const SOURCE = path.join(ROOT, "public/brand/icon.png");
/** Saydamlık kabul etmeyen ikonlarda arka plan: marka mavisi (--primary, açık tema). */
const BRAND_BG = "#105bbe";
async function png(size: number, opts: { flatten?: boolean; padding?: number } = {}) {
const inner = Math.round(size * (1 - (opts.padding ?? 0) * 2));
const resized = await sharp(SOURCE).resize(inner, inner, { fit: "contain" }).png().toBuffer();
let img = sharp({
create: {
width: size,
height: size,
channels: 4,
background: opts.flatten || opts.padding ? BRAND_BG : { r: 0, g: 0, b: 0, alpha: 0 },
},
}).composite([{ input: resized, gravity: "center" }]);
if (opts.flatten) img = img.flatten({ background: BRAND_BG });
return img.png({ compressionLevel: 9 }).toBuffer();
}
/** PNG görüntülerinden ICO dosyası oluşturur (Vista+ tüm tarayıcılar PNG gömülü ICO destekler). */
function toIco(images: { size: number; data: Buffer }[]): Buffer {
const header = Buffer.alloc(6);
header.writeUInt16LE(0, 0); // reserved
header.writeUInt16LE(1, 2); // type: icon
header.writeUInt16LE(images.length, 4);
const entries: Buffer[] = [];
let offset = 6 + images.length * 16;
for (const { size, data } of images) {
const e = Buffer.alloc(16);
e.writeUInt8(size >= 256 ? 0 : size, 0);
e.writeUInt8(size >= 256 ? 0 : size, 1);
e.writeUInt8(0, 2); // palette
e.writeUInt8(0, 3); // reserved
e.writeUInt16LE(1, 4); // color planes
e.writeUInt16LE(32, 6); // bpp
e.writeUInt32LE(data.length, 8);
e.writeUInt32LE(offset, 12);
offset += data.length;
entries.push(e);
}
return Buffer.concat([header, ...entries, ...images.map((i) => i.data)]);
}
async function main() {
const meta = await sharp(SOURCE).metadata();
if (!meta.width || !meta.height || meta.width !== meta.height || meta.width < 512) {
throw new Error(`${SOURCE} kare ve en az 512×512 olmalı (şu an ${meta.width}×${meta.height}).`);
}
await mkdir(path.join(ROOT, "public/icons"), { recursive: true });
const out: [string, Buffer][] = [
["src/app/icon.png", await png(192)],
["src/app/apple-icon.png", await png(180, { flatten: true })],
["public/icons/icon-192.png", await png(192)],
["public/icons/icon-512.png", await png(512)],
["public/icons/maskable-512.png", await png(512, { padding: 0.1 })],
[
"src/app/favicon.ico",
toIco(await Promise.all([16, 32, 48].map(async (size) => ({ size, data: await png(size) })))),
],
];
for (const [file, data] of out) {
await writeFile(path.join(ROOT, file), data);
console.log(`✓ ${file} (${data.length} bayt)`);
}
}
main().catch((err: unknown) => {
console.error(err instanceof Error ? err.message : err);
process.exit(1);
});