import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const FONTS_DIR = path.join(__dirname, '..', 'fonts') const FONT_FILE = path.join(FONTS_DIR, 'NotoSansSC-Regular.otf') const CDN_URLS = [ 'https://cdn.jsdelivr.net/gh/googlefonts/noto-cjk@main/Sans/SubsetOTF/SC/NotoSansSC-Regular.otf', 'https://raw.githubusercontent.com/googlefonts/noto-cjk/main/Sans/SubsetOTF/SC/NotoSansSC-Regular.otf', ] let fontReady: string | null = null export async function ensureChineseFont(): Promise { if (fontReady) return fontReady // Check if already downloaded if (fs.existsSync(FONT_FILE) && fs.statSync(FONT_FILE).size > 100000) { fontReady = FONT_FILE return FONT_FILE } // Ensure directory exists if (!fs.existsSync(FONTS_DIR)) fs.mkdirSync(FONTS_DIR, { recursive: true }) // Try each CDN URL for (const url of CDN_URLS) { try { console.log(`Downloading Chinese font from ${url.split('/')[2]}...`) const res = await fetch(url, { signal: AbortSignal.timeout(60000) }) if (!res.ok) continue const buf = Buffer.from(await res.arrayBuffer()) if (buf.length < 100000) continue // too small, probably error page fs.writeFileSync(FONT_FILE, buf) console.log(`Chinese font downloaded (${(buf.length / 1024 / 1024).toFixed(1)} MB)`) fontReady = FONT_FILE return FONT_FILE } catch (e: any) { console.warn(`Font download failed from ${url.split('/')[2]}: ${e.message}`) } } // Fallback: try local system fonts const fallbacks = [ 'C:\\Windows\\Fonts\\simhei.ttf', 'C:\\Windows\\Fonts\\msyh.ttf', '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', '/System/Library/Fonts/PingFang.ttc', ] for (const f of fallbacks) { if (fs.existsSync(f)) { console.log(`Using fallback system font: ${f}`) fontReady = f return f } } throw new Error('No Chinese font available. Set PDF_FONT_PATH env variable to a TTF/OTF font path.') } export function getChineseFont(): string { if (!fontReady) throw new Error('Font not initialized. Call ensureChineseFont() first.') return fontReady }