Improve unsupported PDF character errors
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 30s
Build and Push Docker Images / build-frontend (push) Successful in 1m12s
Build and Push Docker Images / build-website (push) Successful in 18s
Build and Push Docker Images / build-docs (push) Successful in 17s

This commit is contained in:
2026-07-24 10:25:10 +02:00
parent 4ab4c24b1a
commit d3ad53bcf0
4 changed files with 121 additions and 1 deletions

View File

@@ -25,6 +25,7 @@ import { s3 } from "../utils/s3";
import { secrets } from "../utils/secrets"; import { secrets } from "../utils/secrets";
import { storeExtractedTextForFile } from "../utils/documentText"; import { storeExtractedTextForFile } from "../utils/documentText";
import { generateLiquidityForecast } from "../utils/liquidityForecast"; import { generateLiquidityForecast } from "../utils/liquidityForecast";
import { getUnsupportedPdfCharacterMessage } from "../utils/pdfCharacterError";
dayjs.extend(customParseFormat) dayjs.extend(customParseFormat)
dayjs.extend(isoWeek) dayjs.extend(isoWeek)
dayjs.extend(isBetween) dayjs.extend(isBetween)
@@ -134,7 +135,15 @@ export default async function functionRoutes(server: FastifyInstance) {
return pdf // Fastify wandelt automatisch in JSON return pdf // Fastify wandelt automatisch in JSON
} catch (err) { } catch (err) {
console.log(err) console.log(err)
reply.code(500).send({ error: "Failed to create PDF" }) const unsupportedCharacterMessage = getUnsupportedPdfCharacterMessage(err, body.data)
if (unsupportedCharacterMessage) {
return reply.code(422).send({
error: unsupportedCharacterMessage,
code: "UNSUPPORTED_PDF_CHARACTER",
})
}
return reply.code(500).send({ error: "Failed to create PDF" })
} }
}) })

View File

@@ -0,0 +1,79 @@
const PDF_CHARACTER_ERROR_PATTERN = /WinAnsi cannot encode "[\s\S]+?" \(0x([0-9a-f]+)\)/i
const DOCUMENT_FIELD_LABELS: Record<string, string> = {
title: "Dokumenttitel",
description: "Dokumentbeschreibung",
startText: "Einleitungstext",
endText: "Schlusstext",
text: "Bezeichnung",
descriptionText: "Beschreibung",
unit: "Einheit",
}
type CharacterLocation = {
label: string
characterPosition: number
}
const labelForPath = (path: Array<string | number>, root: any): string => {
if (path[0] === "rows" && typeof path[1] === "number") {
const row = root?.rows?.[path[1]]
const position = row?.pos ? `Position ${row.pos}` : `Dokumentzeile ${path[1] + 1}`
const field = DOCUMENT_FIELD_LABELS[String(path[path.length - 1])] || String(path[path.length - 1])
return `${position}, ${field}`
}
const field = String(path[path.length - 1])
return DOCUMENT_FIELD_LABELS[field] || field
}
const findCharacterLocations = (
value: unknown,
unsupportedCharacter: string,
root: any,
path: Array<string | number> = [],
): CharacterLocation[] => {
if (typeof value === "string") {
const characters = Array.from(value)
return characters.flatMap((character, index) =>
character === unsupportedCharacter
? [{ label: labelForPath(path, root), characterPosition: index + 1 }]
: []
)
}
if (Array.isArray(value)) {
return value.flatMap((entry, index) =>
findCharacterLocations(entry, unsupportedCharacter, root, [...path, index])
)
}
if (value && typeof value === "object") {
return Object.entries(value).flatMap(([key, entry]) =>
findCharacterLocations(entry, unsupportedCharacter, root, [...path, key])
)
}
return []
}
export const getUnsupportedPdfCharacterMessage = (error: unknown, documentData: any): string | null => {
const errorMessage = error instanceof Error ? error.message : String(error)
const match = errorMessage.match(PDF_CHARACTER_ERROR_PATTERN)
if (!match) return null
const numericCodePoint = Number.parseInt(match[1], 16)
if (!Number.isSafeInteger(numericCodePoint) || numericCodePoint > 0x10FFFF) return null
const unsupportedCharacter = String.fromCodePoint(numericCodePoint)
const [location] = findCharacterLocations(documentData, unsupportedCharacter, documentData)
const codePoint = numericCodePoint
.toString(16)
.toUpperCase()
.padStart(4, "0")
const locationText = location
? ` Fundstelle: ${location.label}, Zeichen ${location.characterPosition}.`
: ""
return `Das Zeichen „${unsupportedCharacter}“ (Unicode U+${codePoint}) wird in PDF-Dokumenten nicht unterstützt.${locationText} Bitte ersetze oder entferne das Zeichen.`
}

View File

@@ -0,0 +1,27 @@
import assert from "node:assert/strict"
import test from "node:test"
import { getUnsupportedPdfCharacterMessage } from "../src/utils/pdfCharacterError"
test("reports unsupported characters with the document position and character offset", () => {
// pdf-lib displays supplementary Unicode characters as a private-use glyph,
// while the hexadecimal value still contains the original code point.
const error = new Error('WinAnsi cannot encode "" (0x1f600)')
const documentData = {
rows: [
{ pos: "1", text: "Montage" },
{ pos: "2.3", descriptionText: "Gerät 😀 montieren" },
],
}
assert.equal(
getUnsupportedPdfCharacterMessage(error, documentData),
"Das Zeichen „😀“ (Unicode U+1F600) wird in PDF-Dokumenten nicht unterstützt. Fundstelle: Position 2.3, Beschreibung, Zeichen 7. Bitte ersetze oder entferne das Zeichen."
)
})
test("returns null for unrelated PDF errors", () => {
assert.equal(
getUnsupportedPdfCharacterMessage(new Error("Background PDF is invalid"), {}),
null
)
})

View File

@@ -45,6 +45,11 @@ export default defineNuxtPlugin(() => {
let title = "Fehler" let title = "Fehler"
let description = "Ein unerwarteter Fehler ist aufgetreten." let description = "Ein unerwarteter Fehler ist aufgetreten."
if (status === 422 && response._data?.code === "UNSUPPORTED_PDF_CHARACTER") {
title = "Nicht unterstütztes Zeichen"
description = response._data.error
}
switch (status) { switch (status) {
case 400: case 400:
title = "Anfrage fehlerhaft" title = "Anfrage fehlerhaft"