diff --git a/engine.spec b/engine.spec index fbce929..aff2a8d 100644 --- a/engine.spec +++ b/engine.spec @@ -124,5 +124,7 @@ exe = EXE( upx_exclude=[], runtime_tmpdir=None, console=False, # True khi debug (xem log truc tiep), False cho production - icon='src-tauri/icons/icon.ico', + # Icon exe = favicon cua app (app/templates/favicon.svg -> render PNG -> ICO, + # sinh boi tools/gen_favicon_ico.py). Cung nguon voi icon hien thi trong app. + icon='src-tauri/icons/favicon.ico', ) diff --git a/src-tauri/icons/favicon.ico b/src-tauri/icons/favicon.ico new file mode 100644 index 0000000..f7e1874 Binary files /dev/null and b/src-tauri/icons/favicon.ico differ diff --git a/tools/gen_favicon_ico.js b/tools/gen_favicon_ico.js new file mode 100644 index 0000000..616926e --- /dev/null +++ b/tools/gen_favicon_ico.js @@ -0,0 +1,55 @@ +// Sinh src-tauri/icons/favicon.ico tu app/templates/favicon.svg (icon exe). +// Dung: node tools/gen_favicon_ico.js +// (Can @resvg/resvg-js — npm install @resvg/resvg-js trong thu muc lam viec, +// hoac chay trong thu muc da cai. Output: src-tauri/icons/favicon.ico.) +// ICO chua cac size 16/24/32/48/64/128 — Windows dung cho exe icon. +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.resolve(__dirname, '..'); +const SVG = path.join(ROOT, 'app', 'templates', 'favicon.svg'); +const OUT = path.join(ROOT, 'src-tauri', 'icons', 'favicon.ico'); + +let Resvg; +try { + ({ Resvg } = require('@resvg/resvg-js')); +} catch (e) { + console.error('Thieu @resvg/resvg-js. Chay: npm install @resvg/resvg-js'); + process.exit(1); +} + +const svg = fs.readFileSync(SVG, 'utf8'); +const SIZES = [16, 24, 32, 48, 64, 128]; + +function buildIco(images) { + const header = Buffer.alloc(6); + header.writeUInt16LE(0, 0); + header.writeUInt16LE(1, 2); + header.writeUInt16LE(images.length, 4); + const entries = []; + const datas = []; + let offset = 6 + 16 * images.length; + for (const { size, data } of images) { + const entry = Buffer.alloc(16); + const dim = size >= 256 ? 0 : size; + entry.writeUInt8(dim, 0); + entry.writeUInt8(dim, 1); + entry.writeUInt8(0, 2); + entry.writeUInt8(0, 3); + entry.writeUInt16LE(1, 4); + entry.writeUInt16LE(32, 6); + entry.writeUInt32LE(data.length, 8); + entry.writeUInt32LE(offset, 12); + entries.push(entry); + datas.push(data); + offset += data.length; + } + return Buffer.concat([header, ...entries, ...datas]); +} + +const pngs = SIZES.map(s => ({ + size: s, + data: new Resvg(svg, { fitTo: { mode: 'width', value: s }, background: 'rgba(0,0,0,0)' }).render().asPng(), +})); +fs.writeFileSync(OUT, buildIco(pngs)); +console.log('favicon.ico ->', OUT, fs.statSync(OUT).size, 'bytes');