79 lines
3.3 KiB
JavaScript
79 lines
3.3 KiB
JavaScript
import { readFileSync } from 'node:fs'
|
||
import * as fontkitModul from 'fontkit'
|
||
|
||
/**
|
||
* PDF der Auftragsbestätigung (A4, pdfkit): Schriften und Zeichenabdeckung, Daten (alle Texte fertig formatiert),
|
||
* Seitenaufteilung (rein) und Zeichnen. Aufbau der Datei: 1 Schriften, 2 Daten, 3 Layout, 4 Zeichnen, 5 erzeugeAuftragPdf.
|
||
*/
|
||
|
||
// ===== 1. Schriften, Logo, Zeichenabdeckung =====
|
||
const fontkit = fontkitModul.default ?? fontkitModul
|
||
const SCHRIFT_VERZEICHNIS = new URL('../../assets/fonts/', import.meta.url)
|
||
const LOGO_DATEI = new URL('../../../public/webklar-logo.png', import.meta.url)
|
||
|
||
const SCHRIFT_DATEIEN = Object.freeze({ regular: 'Figtree-Regular.ttf', semibold: 'Figtree-SemiBold.ttf' })
|
||
|
||
let geladen = null
|
||
|
||
/** { regular: Buffer, semibold: Buffer, logo: Buffer, regularFont, semiboldFont } – bei jedem Aufruf dasselbe Objekt. */
|
||
export function ladeSchriften() {
|
||
if (!geladen) {
|
||
const regular = readFileSync(new URL(SCHRIFT_DATEIEN.regular, SCHRIFT_VERZEICHNIS))
|
||
const semibold = readFileSync(new URL(SCHRIFT_DATEIEN.semibold, SCHRIFT_VERZEICHNIS))
|
||
geladen = Object.freeze({
|
||
regular,
|
||
semibold,
|
||
logo: readFileSync(LOGO_DATEI),
|
||
regularFont: fontkit.create(regular),
|
||
semiboldFont: fontkit.create(semibold),
|
||
})
|
||
}
|
||
return geladen
|
||
}
|
||
|
||
/** Hat der Codepunkt ein Glyph in Regular UND SemiBold? */
|
||
export function hatGlyph(codePoint) {
|
||
const { regularFont, semiboldFont } = ladeSchriften()
|
||
return regularFont.hasGlyphForCodePoint(codePoint) && semiboldFont.hasGlyphForCodePoint(codePoint)
|
||
}
|
||
|
||
const ERSATZ = new Map([
|
||
[0x2010, '-'], [0x2011, '-'], [0x2012, '-'], [0x2013, '-'], [0x2014, '-'], [0x2015, '-'], [0x2212, '-'],
|
||
[0x2018, "'"], [0x2019, "'"], [0x201a, "'"], [0x2032, "'"],
|
||
[0x201c, '"'], [0x201d, '"'], [0x201e, '"'], [0x201f, '"'], [0x2033, '"'],
|
||
[0x2026, '...'], [0x20ac, 'EUR'], [0x2022, '-'], [0x00b7, '-'], [0x00d7, 'x'], [0x2192, '->'],
|
||
])
|
||
// Steuerzeichen (außer \n), weiche Trennung, Zero-Width-Zeichen, Variantenselektoren, BOM
|
||
const ENTFERNEN = /[\u{0}-\u{8}\u{B}-\u{1F}\u{7F}-\u{9F}\u{AD}\u{200B}-\u{200D}\u{2060}\u{FE00}-\u{FE0F}\u{FEFF}]/gu
|
||
|
||
/** Ersetzt Zeichen ohne Glyph, damit nie ein leeres Kästchen im PDF steht. */
|
||
export function bereinigeText(text, hatGlyphFn = hatGlyph) {
|
||
const roh = String(text ?? '')
|
||
.normalize('NFC')
|
||
.replace(/\r\n?|[\u{2028}\u{2029}]/gu, '\n')
|
||
.replace(/\t/g, ' ')
|
||
.replace(ENTFERNEN, '')
|
||
let aus = ''
|
||
for (const zeichen of roh) {
|
||
const cp = zeichen.codePointAt(0)
|
||
if (zeichen === '\n' || hatGlyphFn(cp)) aus += zeichen
|
||
else if (ERSATZ.has(cp)) aus += ERSATZ.get(cp)
|
||
else if (/\p{Zs}/u.test(zeichen)) aus += ' '
|
||
else if (/\p{M}/u.test(zeichen)) continue
|
||
else {
|
||
const basis = zeichen.normalize('NFD').codePointAt(0)
|
||
aus += basis !== cp && hatGlyphFn(basis) ? String.fromCodePoint(basis) : '?'
|
||
}
|
||
}
|
||
return aus
|
||
}
|
||
|
||
/** bereinigeText für jede Zeichenkette in beliebig verschachtelten Objekten und Listen. */
|
||
export function bereinigeTexte(wert, hatGlyphFn = hatGlyph) {
|
||
if (typeof wert === 'string') return bereinigeText(wert, hatGlyphFn)
|
||
if (Array.isArray(wert)) return wert.map((e) => bereinigeTexte(e, hatGlyphFn))
|
||
if (wert && typeof wert === 'object') return Object.fromEntries(Object.entries(wert).map(([k, v]) => [k, bereinigeTexte(v, hatGlyphFn)]))
|
||
return wert
|
||
}
|
||
|