/// Layout Engine Web Worker /// Template JSON + Data JSON → Layout WASM → LayoutResult /// Font loading is dynamic — fetches from backend API based on template needs. import init, { loadFonts, addFonts, computeLayout, generateBarcode } from '../core/wasm-layout/dreport_layout.js' import type { LayoutResult } from '../core/layout-types' let initPromise: Promise | null = null /** Configurable font API base URL. Default: same origin /api/fonts */ let fontApiBase = '/api/fonts' /** Font catalog from backend API */ interface FontVariantInfo { weight: number italic: boolean } interface FontFamilyInfo { family: string variants: FontVariantInfo[] } let fontCatalog: FontFamilyInfo[] = [] /** Track which font families are already loaded into WASM */ const loadedFamilies = new Set() async function doInit() { console.log('[layout-worker] WASM başlatılıyor...') await init({ module_or_path: '/wasm/dreport_layout_bg.wasm' }) // Fetch font catalog from backend try { const res = await fetch(fontApiBase) if (res.ok) { fontCatalog = await res.json() console.log(`[layout-worker] Font kataloğu yüklendi (${fontCatalog.length} aile)`) } else { console.warn(`[layout-worker] Font kataloğu alınamadı (HTTP ${res.status}), static fallback deneniyor`) await loadStaticFallback() return } } catch { console.warn('[layout-worker] Font API erişilemedi, static fallback deneniyor') await loadStaticFallback() return } // Load default fonts (Noto Sans + Noto Sans Mono) await ensureFamiliesLoaded(['Noto Sans', 'Noto Sans Mono']) console.log('[layout-worker] Hazır') } /** Fallback: load fonts from static /fonts/ directory (backwards compat) */ async function loadStaticFallback() { const STATIC_FONTS = [ '/fonts/NotoSans-Regular.ttf', '/fonts/NotoSans-Bold.ttf', '/fonts/NotoSans-Italic.ttf', '/fonts/NotoSans-BoldItalic.ttf', '/fonts/NotoSansMono-Regular.ttf', ] const buffers: Uint8Array[] = [] await Promise.all( STATIC_FONTS.map(async (path) => { const url = new URL(path, self.location.origin).href const res = await fetch(url) if (res.ok) { buffers.push(new Uint8Array(await res.arrayBuffer())) } }) ) if (buffers.length > 0) { loadFonts(buffers) loadedFamilies.add('noto sans') loadedFamilies.add('noto sans mono') console.log(`[layout-worker] Static fallback: ${buffers.length} font yüklendi`) } } /** Load all variants of given families from the API into WASM */ async function ensureFamiliesLoaded(families: string[]): Promise { const toLoad = families.filter(f => !loadedFamilies.has(f.toLowerCase())) if (toLoad.length === 0) return const buffers: Uint8Array[] = [] for (const family of toLoad) { const info = fontCatalog.find(f => f.family.toLowerCase() === family.toLowerCase()) if (!info) { console.warn(`[layout-worker] Font ailesi bulunamadı: ${family}`) continue } const fetches = info.variants.map(async (v) => { const url = `${fontApiBase}/${encodeURIComponent(info.family)}/${v.weight}/${v.italic}` const res = await fetch(url) if (res.ok) { return new Uint8Array(await res.arrayBuffer()) } return null }) const results = await Promise.all(fetches) for (const buf of results) { if (buf && buf.byteLength > 0) { buffers.push(buf) } } loadedFamilies.add(family.toLowerCase()) } if (buffers.length > 0) { if (loadedFamilies.size <= toLoad.length) { // First load — use loadFonts loadFonts(buffers) } else { // Subsequent loads — use addFonts addFonts(buffers) } console.log(`[layout-worker] ${toLoad.join(', ')} yüklendi (${buffers.length} variant)`) } } function ensureInit(): Promise { if (!initPromise) { initPromise = doInit() } return initPromise } type WorkerMessage = | { type: 'compile'; templateJson: string; dataJson: string; id: number } | { type: 'barcode'; format: string; value: string; width: number; height: number; includeText: boolean; id: number } | { type: 'configure'; fontApiBase?: string } self.onmessage = async (e: MessageEvent) => { const msg = e.data if (msg.type === 'configure') { if (msg.fontApiBase) { fontApiBase = msg.fontApiBase } return } if (msg.type === 'compile') { try { await ensureInit() // Extract font families from template and ensure they're loaded try { const tpl = JSON.parse(msg.templateJson) if (Array.isArray(tpl.fonts) && tpl.fonts.length > 0) { await ensureFamiliesLoaded(tpl.fonts) } } catch { // Template parse failure will be caught by computeLayout below } const t0 = performance.now() const resultJson = computeLayout(msg.templateJson, msg.dataJson) const layout: LayoutResult = JSON.parse(resultJson) console.log(`[layout-worker] render ${(performance.now() - t0).toFixed(1)}ms`) self.postMessage({ type: 'result', layout, id: msg.id }) } catch (err) { const errorMsg = err instanceof Error ? err.message : String(err) console.error(`[layout-worker] Hata (id: ${msg.id}):`, err) self.postMessage({ type: 'error', error: errorMsg, id: msg.id }) } } else if (msg.type === 'barcode') { try { await ensureInit() const raw = generateBarcode(msg.format, msg.value, msg.width, msg.height, msg.includeText) // İlk 8 byte header: width (4 byte LE) + height (4 byte LE) const dv = new DataView(raw.buffer, raw.byteOffset, 8) const w = dv.getUint32(0, true) const h = dv.getUint32(4, true) const rgba = raw.slice(8) self.postMessage( { type: 'barcode-result', width: w, height: h, rgba: rgba.buffer, id: msg.id }, [rgba.buffer] as any, ) } catch (err) { const errorMsg = err instanceof Error ? err.message : String(err) console.error(`[layout-worker] Barcode hatası (id: ${msg.id}):`, err) self.postMessage({ type: 'barcode-error', error: errorMsg, id: msg.id }) } } }