Files
skrift-backend/src/lib/page-layout.js
Lucas Orth 0ddaab27d0
All checks were successful
Build & Deploy / deploy (push) Successful in 10s
Import aus Monorepo (frischer Start)
2026-08-24 12:13:50 +02:00

147 lines
5.3 KiB
JavaScript
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.
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, '&lt;')
.replace(/>/g, '&gt;');
}
// 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,
};