116 lines
4.5 KiB
JavaScript
116 lines
4.5 KiB
JavaScript
/**
|
|
* 生成 YCode 应用图标资产。
|
|
*
|
|
* 输入:build/icon.svg(唯一矢量源)
|
|
* 输出:build/icon.ico、build/icon.png、resources/icon.png、build/icon-preview.png
|
|
*
|
|
* 图案是深红旗面 + 按国旗制式排布的五颗金星(一大四小,3:1),
|
|
* 星群坐标由 scripts/star-layout.mjs 算出,这里只负责栅格化与打包。
|
|
*
|
|
* 用法:node scripts/gen-icon.mjs
|
|
*/
|
|
import { Resvg } from '@resvg/resvg-js'
|
|
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
import { dirname, join } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
const buildDir = join(root, 'build')
|
|
const resourcesDir = join(root, 'resources')
|
|
|
|
const masterSvg = readFileSync(join(buildDir, 'icon.svg'), 'utf8')
|
|
|
|
const ICO_SIZES = [16, 24, 32, 48, 64, 128, 256]
|
|
|
|
function render(svg, size) {
|
|
return new Resvg(svg, { fitTo: { mode: 'width', value: size } }).render().asPng()
|
|
}
|
|
|
|
function pick() {
|
|
return masterSvg
|
|
}
|
|
|
|
/** 组装 Windows ICO:每个尺寸内嵌一张 PNG(Vista 及以后支持)。 */
|
|
function buildIco(entries) {
|
|
const header = Buffer.alloc(6)
|
|
header.writeUInt16LE(0, 0)
|
|
header.writeUInt16LE(1, 2)
|
|
header.writeUInt16LE(entries.length, 4)
|
|
|
|
const dirEntries = []
|
|
let offset = 6 + 16 * entries.length
|
|
for (const { size, png } of entries) {
|
|
const entry = Buffer.alloc(16)
|
|
entry.writeUInt8(size >= 256 ? 0 : size, 0)
|
|
entry.writeUInt8(size >= 256 ? 0 : size, 1)
|
|
entry.writeUInt8(0, 2)
|
|
entry.writeUInt8(0, 3)
|
|
entry.writeUInt16LE(1, 4)
|
|
entry.writeUInt16LE(32, 6)
|
|
entry.writeUInt32LE(png.length, 8)
|
|
entry.writeUInt32LE(offset, 12)
|
|
offset += png.length
|
|
dirEntries.push(entry)
|
|
}
|
|
|
|
return Buffer.concat([header, ...dirEntries, ...entries.map((e) => e.png)])
|
|
}
|
|
|
|
mkdirSync(resourcesDir, { recursive: true })
|
|
|
|
const icoEntries = ICO_SIZES.map((size) => ({ size, png: render(pick(), size) }))
|
|
writeFileSync(join(buildDir, 'icon.ico'), buildIco(icoEntries))
|
|
|
|
writeFileSync(join(buildDir, 'icon.png'), render(masterSvg, 1024))
|
|
writeFileSync(join(resourcesDir, 'icon.png'), render(masterSvg, 512))
|
|
// 托盘:Windows 通知区域按 16/20/24/32 取用,给 32px 让系统自行缩放
|
|
writeFileSync(join(resourcesDir, 'tray.png'), render(masterSvg, 32))
|
|
|
|
// 备用图:常用尺寸各出一张 PNG,供托盘、商店页、文档等场景直接取用
|
|
const PNG_SIZES = [16, 20, 24, 32, 40, 48, 64, 96, 128, 180, 256, 512, 1024]
|
|
const sizesDir = join(buildDir, 'sizes')
|
|
mkdirSync(sizesDir, { recursive: true })
|
|
for (const size of PNG_SIZES) {
|
|
writeFileSync(join(sizesDir, `icon-${size}.png`), render(pick(), size))
|
|
}
|
|
|
|
// 对照表:浅色与深色背景下,各尺寸的真实观感
|
|
const sampleSizes = [128, 64, 48, 32, 16]
|
|
function tile(size) {
|
|
return render(pick(), size).toString('base64')
|
|
}
|
|
const rows = [
|
|
{ label: '浅色任务栏', bg: '#FFFFFF', fg: '#475467' },
|
|
{ label: '深色任务栏', bg: '#202124', fg: '#D0D5DD' }
|
|
]
|
|
let y = 78
|
|
const rowSvg = rows
|
|
.map((row) => {
|
|
let x = 150
|
|
const imgs = sampleSizes
|
|
.map((size) => {
|
|
const markup = `<image x="${x}" y="${y + (128 - size) / 2}" width="${size}" height="${size}" href="data:image/png;base64,${tile(size)}"/>`
|
|
x += size + 28
|
|
return markup
|
|
})
|
|
.join('')
|
|
const label = `<text x="40" y="${y + 68}" font-size="13" fill="${row.fg}" font-family="system-ui, 'Microsoft YaHei', sans-serif">${row.label}</text>`
|
|
const block = `<rect x="20" y="${y}" width="640" height="128" rx="10" fill="${row.bg}"/>${label}${imgs}`
|
|
y += 152
|
|
return block
|
|
})
|
|
.join('')
|
|
|
|
const previewSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 680 ${y + 10}" width="680" height="${y + 10}">
|
|
<rect width="680" height="${y + 10}" fill="#F7F8FA"/>
|
|
<text x="20" y="34" font-size="15" font-weight="500" fill="#1F2328" font-family="system-ui, 'Microsoft YaHei', sans-serif">YCode 图标:128 / 64 / 48 / 32 / 16 px 实际尺寸对照</text>
|
|
<text x="20" y="56" font-size="12" fill="#667085" font-family="system-ui, 'Microsoft YaHei', sans-serif">五颗星按国旗制式:一大四小(3:1),四颗小星各有一角尖对准大星中心;旗面为深红渐变+柔和光泽</text>
|
|
${rowSvg}
|
|
</svg>`
|
|
|
|
writeFileSync(join(buildDir, 'icon-preview.png'), new Resvg(previewSvg, { fitTo: { mode: 'width', value: 1360 } }).render().asPng())
|
|
|
|
console.log('icon.ico', icoEntries.length, 'sizes')
|
|
console.log('icon.png / resources/icon.png / icon-preview.png done')
|
|
console.log('build/sizes/icon-{', PNG_SIZES.join(','), '}.png done')
|