Felipe97/llama-cpp-compiled
01.2k
1import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';2import { dirname, resolve } from 'node:path';3import { fileURLToPath } from 'node:url';4 5const HERE = dirname(fileURLToPath(import.meta.url));6const PROJECT_ROOT = resolve(HERE, '..');7const DEFAULT_LOGO = resolve(PROJECT_ROOT, 'src/lib/assets/logo.svg');8const DEFAULT_OUT_DIR = resolve(PROJECT_ROOT, 'static');9const DEFAULT_OUT_LIGHT = resolve(DEFAULT_OUT_DIR, 'favicon.svg');10const DEFAULT_OUT_DARK = resolve(DEFAULT_OUT_DIR, 'favicon-dark.svg');11const CURRENT_COLOR = 'currentColor';12 13export interface ColorizedFavicon {14 light: string;15 dark: string;16}17 18export interface WriteThemeFaviconsOptions {19 sourcePath?: string;20 lightOutPath?: string;21 darkOutPath?: string;22 /**23 * Fraction of the icon (0..1) to leave as an even margin on each side.24 * Applied by wrapping the inner content in a `<g transform="...">` so the25 * source `src/lib/assets/logo.svg` is not modified. Pass 0 to disable.26 */27 padding?: number;28}29 30/**31 * Replace every `currentColor` occurrence in the SVG with the given color.32 * Pure: no filesystem access, so it is straightforward to unit-test.33 */34export function colorizeFaviconSvg(35 svg: string,36 lightColor: string,37 darkColor: string38): ColorizedFavicon {39 return {40 dark: svg.replaceAll(CURRENT_COLOR, darkColor),41 light: svg.replaceAll(CURRENT_COLOR, lightColor)42 };43}44 45/**46 * Shrink the inner SVG content uniformly and re-center it so `padding` (a47 * 0..1 fraction) is reserved as equal margin on each side. Returns the input48 * unchanged for non-positive padding, missing/invalid `viewBox`, or unexpected49 * markup so the caller always gets a renderable SVG.50 */51export function padFaviconSvg(svg: string, padding: number): string {52 if (!(padding > 0) || padding >= 1) return svg;53 54 const viewBoxMatch = svg.match(/viewBox\s*=\s*["']([^"']+)["']/i);55 56 if (!viewBoxMatch) return svg;57 58 const parts = viewBoxMatch[1]59 .trim()60 .split(/[\s,]+/)61 .map(Number);62 63 if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n))) return svg;64 65 const [, , width, height] = parts;66 67 if (width <= 0 || height <= 0) return svg;68 69 const scale = 1 - padding;70 const translateX = (padding * width) / 2;71 const translateY = (padding * height) / 2;72 const openTagStart = svg.search(/<svg\b/i);73 74 if (openTagStart === -1) return svg;75 76 const openTagEnd = svg.indexOf('>', openTagStart);77 78 if (openTagEnd === -1) return svg;79 80 const closeStart = svg.lastIndexOf('</svg');81 82 if (closeStart === -1 || closeStart <= openTagEnd) return svg;83 84 const openTag = svg.slice(0, openTagEnd + 1);85 const inner = svg.slice(openTagEnd + 1, closeStart);86 const closeTag = svg.slice(closeStart);87 const group = `<g transform="translate(${translateX} ${translateY}) scale(${scale})">`;88 89 return `${openTag}${group}${inner}</g>${closeTag}`;90}91 92/**93 * Read `src/lib/assets/logo.svg`, colorize it for both themes, and write94 * the results to the static directory so the PWA asset generator can consume95 * them. Paths can be overridden for tests.96 */97export function writeThemeFavicons(98 lightColor: string,99 darkColor: string,100 {101 darkOutPath = DEFAULT_OUT_DARK,102 lightOutPath = DEFAULT_OUT_LIGHT,103 padding = 0,104 sourcePath = DEFAULT_LOGO105 }: WriteThemeFaviconsOptions = {}106): void {107 const source = readFileSync(sourcePath, 'utf-8');108 const { dark, light } = colorizeFaviconSvg(source, lightColor, darkColor);109 110 mkdirSync(dirname(lightOutPath), { recursive: true });111 writeFileSync(lightOutPath, padFaviconSvg(light, padding));112 writeFileSync(darkOutPath, padFaviconSvg(dark, padding));113}114 