const PX_PER_MM = 3.78; // 96dpi const FONT_SIZE_PX = 26; // ── Formate: Defaults (Fallback) + datengetriebener Cache ─────────────────── // Die Werte kommen zentral aus Directus (Collection render_formats) und werden // im Speicher gehalten (stündlich / manuell aufgefrischt via applyRenderFormats). // Ist Directus nicht erreichbar oder ein Format nicht hinterlegt, greifen die // eingebauten Defaults – die Generierung läuft also immer. const DEFAULT_LINE_LIMITS = { a4: 27, a6p: 14, a6l: 11 }; const DEFAULT_FORMAT_LAYOUTS = { a4: { marginTopMm: 25, marginLeftMm: 20, lineHeightFactor: 1.35 }, a6p: { marginTopMm: 8, marginLeftMm: 8, lineHeightFactor: 1.3 }, a6l: { marginTopMm: 8, marginLeftMm: 8, marginRightMm: 14, lineHeightFactor: 1.2 }, din_lang: { marginTopMm: 50, marginLeftMm: 10, lineHeightFactor: 1.2 }, c6: { marginTopMm: 60, marginLeftMm: 10, lineHeightFactor: 1.2 }, }; const DEFAULT_PAGE_FORMATS = { a4: { widthMm: 210, heightMm: 297 }, a6p: { widthMm: 105, heightMm: 148 }, a6l: { widthMm: 148, heightMm: 105 }, din_lang: { widthMm: 220, heightMm: 110 }, c6: { widthMm: 162, heightMm: 114 }, }; // Live-Cache (wird von applyRenderFormats ersetzt). Start = Defaults. let LINE_LIMITS = { ...DEFAULT_LINE_LIMITS }; let FORMAT_LAYOUTS = { ...DEFAULT_FORMAT_LAYOUTS }; let PAGE_FORMATS = { ...DEFAULT_PAGE_FORMATS }; /** * Übernimmt Format-Definitionen aus Directus (render_formats) in den Cache. * Baut immer auf den Defaults auf, damit Teilangaben nichts kaputt machen. * rows: [{ key, page_width_mm, page_height_mm, margin_top_mm, margin_left_mm, * margin_right_mm, line_height_factor, line_limit }] */ function applyRenderFormats(rows) { const pf = { ...DEFAULT_PAGE_FORMATS }; const fl = { ...DEFAULT_FORMAT_LAYOUTS }; const ll = { ...DEFAULT_LINE_LIMITS }; for (const r of Array.isArray(rows) ? rows : []) { const k = String(r.key || '').toLowerCase(); if (!k) continue; if (r.page_width_mm && r.page_height_mm) { pf[k] = { widthMm: Number(r.page_width_mm), heightMm: Number(r.page_height_mm) }; } const alt = fl[k] || {}; const layout = { marginTopMm: Number(r.margin_top_mm ?? alt.marginTopMm ?? 25), marginLeftMm: Number(r.margin_left_mm ?? alt.marginLeftMm ?? 20), lineHeightFactor: Number(r.line_height_factor ?? alt.lineHeightFactor ?? 1.35), }; const mr = r.margin_right_mm ?? alt.marginRightMm; if (mr != null && mr !== '') layout.marginRightMm = Number(mr); fl[k] = layout; if (r.line_limit != null && r.line_limit !== '') ll[k] = Number(r.line_limit); } PAGE_FORMATS = pf; FORMAT_LAYOUTS = fl; LINE_LIMITS = ll; signaledMisses.clear(); } // mm in Pixel umrechnen function mmToPx(mm) { return mm * PX_PER_MM; } // ── Refresh bei unbekanntem Format ────────────────────────────────────────── // Wird ein Format angefragt, das (noch) nicht im Cache liegt, meldet page-layout // das einmalig an einen Handler (den der Server auf refreshRenderFormats setzt). // So werden neu in Directus angelegte Formate sofort nachgeladen, ohne die // stündliche Aktualisierung abzuwarten. Für DIESEN Aufruf greift noch der // Default – der nächste Aufruf hat das Format. let missHandler = null; const signaledMisses = new Set(); function setFormatMissHandler(fn) { missHandler = fn; } function signalMiss(key) { const k = String(key || '').toLowerCase(); if (!missHandler || !k || signaledMisses.has(k)) return; signaledMisses.add(k); try { missHandler(k); } catch { /* egal */ } } // Layout für Format holen function getLayoutForFormat(format) { const normalizedFormat = String(format).toLowerCase(); const layoutConfig = FORMAT_LAYOUTS[normalizedFormat]; if (!layoutConfig) signalMiss(normalizedFormat); if (!layoutConfig) { // Fallback auf a4 return { marginTopPx: mmToPx(FORMAT_LAYOUTS.a4.marginTopMm), marginLeftPx: mmToPx(FORMAT_LAYOUTS.a4.marginLeftMm), lineHeightPx: FONT_SIZE_PX * FORMAT_LAYOUTS.a4.lineHeightFactor, }; } return { marginTopPx: mmToPx(layoutConfig.marginTopMm), marginLeftPx: mmToPx(layoutConfig.marginLeftMm), marginRightPx: layoutConfig.marginRightMm ? mmToPx(layoutConfig.marginRightMm) : null, lineHeightPx: FONT_SIZE_PX * layoutConfig.lineHeightFactor, }; } // Seiten-Dimensionen holen function getPageDimensions(format) { const normalizedFormat = String(format).toLowerCase(); if (!PAGE_FORMATS[normalizedFormat]) signalMiss(normalizedFormat); const dimensions = PAGE_FORMATS[normalizedFormat] || PAGE_FORMATS.a4; return { widthMm: dimensions.widthMm, heightMm: dimensions.heightMm, widthPx: mmToPx(dimensions.widthMm), heightPx: mmToPx(dimensions.heightMm), }; } // XML Escaping function escapeXml(str) { return String(str) .replace(/&/g, '&') .replace(//g, '>'); } // Zeilenlimit für Format holen function getLineLimit(format) { const normalizedFormat = String(format).toLowerCase(); if (!LINE_LIMITS[normalizedFormat]) signalMiss(normalizedFormat); return LINE_LIMITS[normalizedFormat] || LINE_LIMITS.a4; } module.exports = { PX_PER_MM, FONT_SIZE_PX, mmToPx, getLayoutForFormat, getPageDimensions, getLineLimit, escapeXml, applyRenderFormats, setFormatMissHandler, };