Files
dreport/frontend/src/workers/layout.worker.ts

195 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 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<void> | 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<string>()
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<void> {
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<void> {
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<WorkerMessage>) => {
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 })
}
}
}