vert/src/lib/util/icns-encoder.ts

112 lines
3.1 KiB
TypeScript

/**
* ICNS (Apple Icon Image) file format encoder
*
* Creates .icns files from PNG images at various sizes.
*
* File structure:
* - 8 byte header: 'icns' magic + 4 byte file length (big-endian)
* - Icon elements: OSType (4 bytes) + length (4 bytes) + data
*
* References:
* - https://en.wikipedia.org/wiki/Apple_Icon_Image_format
* - https://github.com/fiahfy/icns
*/
/**
* Icon type definitions for ICNS format
* Maps pixel dimensions to OSType codes
*
* Only modern PNG-based types (OS X 10.5+) are included.
* Legacy types (is32, il32, ih32, etc.) require raw RGB + mask encoding
* and are not supported by this encoder.
*/
export const ICNS_TYPES = {
// Modern PNG-based formats (OS X 10.5+)
'ic07': { size: 128, scale: 1, format: 'png' }, // 128x128
'ic08': { size: 256, scale: 1, format: 'png' }, // 256x256
'ic09': { size: 512, scale: 1, format: 'png' }, // 512x512
'ic10': { size: 1024, scale: 2, format: 'png' }, // 1024x1024 (512x512@2x retina)
'ic11': { size: 32, scale: 2, format: 'png' }, // 32x32 (16x16@2x retina)
'ic12': { size: 64, scale: 2, format: 'png' }, // 64x64 (32x32@2x retina)
'ic13': { size: 256, scale: 2, format: 'png' }, // 256x256 (128x128@2x retina)
'ic14': { size: 512, scale: 2, format: 'png' }, // 512x512 (256x256@2x retina)
} as const;
export type IconType = keyof typeof ICNS_TYPES;
export interface IconEntry {
type: IconType;
data: Uint8Array;
}
/**
* Encodes icon entries into a complete ICNS file
*/
export function encodeIcns(entries: IconEntry[]): Uint8Array {
if (entries.length === 0) {
throw new Error('At least one icon entry is required');
}
// Calculate total file size
let totalSize = 8; // Header size
for (const entry of entries) {
totalSize += 8 + entry.data.length; // type (4) + length (4) + data
}
// Create output buffer
const buffer = new ArrayBuffer(totalSize);
const view = new DataView(buffer);
const uint8 = new Uint8Array(buffer);
let offset = 0;
// Write file header
// Magic number: 'icns'
uint8[offset++] = 0x69; // 'i'
uint8[offset++] = 0x63; // 'c'
uint8[offset++] = 0x6E; // 'n'
uint8[offset++] = 0x73; // 's'
// File length (big-endian)
view.setUint32(offset, totalSize, false);
offset += 4;
const textEncoder = new TextEncoder();
// Write icon elements
for (const entry of entries) {
// Write OSType (4 bytes)
const typeBytes = textEncoder.encode(entry.type);
uint8.set(typeBytes, offset);
offset += 4;
// Write data length (big-endian): 8 (header) + data length
const elementSize = 8 + entry.data.length;
view.setUint32(offset, elementSize, false);
offset += 4;
// Write image data
uint8.set(entry.data, offset);
offset += entry.data.length;
}
return uint8;
}
/**
* Get the recommended icon sizes for a complete ICNS file
* Returns sizes in descending order (largest first)
*/
export function getRecommendedSizes(): IconType[] {
return [
'ic10', // 1024x1024 (512@2x)
'ic14', // 512x512 (256@2x)
'ic09', // 512x512
'ic13', // 256x256 (128@2x)
'ic08', // 256x256
'ic12', // 64x64 (32@2x)
'ic07', // 128x128
'ic11', // 32x32 (16@2x)
];
}