diff --git a/administration/src/components/generation/GenerationController.tsx b/administration/src/components/generation/GenerationController.tsx index 97895fb43..5f0de3c27 100644 --- a/administration/src/components/generation/GenerationController.tsx +++ b/administration/src/components/generation/GenerationController.tsx @@ -9,6 +9,7 @@ import downloadDataUri from "../../util/downloadDataUri"; import generateCards from "./generateCards"; import RegionSelector from "../RegionSelector"; import {RegionContext} from "../../RegionProvider"; +import {PDFError} from "./PdfFactory"; enum Mode { input, @@ -30,17 +31,30 @@ const GenerationController = () => { } const confirm = async () => { - try { - setMode(Mode.loading) - const pdfDataUri = await generateCards(client, cardCreationModels, region) - downloadDataUri(pdfDataUri, "ehrenamtskarten.pdf") - setMode(Mode.finished) - } catch (e) { - console.error(e) - AppToaster.show({message: "Etwas ist schiefgegangen.", intent: "danger"}) - setMode(Mode.input) - } + setMode(Mode.loading); + (await generateCards(client, cardCreationModels, region)).unwrap( + (blob: Blob) => { + downloadDataUri(blob, "ehrenamtskarten.pdf"); + setMode(Mode.finished); + }, + (error: PDFError) => { + console.error(error) + switch (error.type) { + case "pdf-generation": + AppToaster.show({message: "Etwas ist schiefgegangen.", intent: "danger"}) + break; + case "unicode": + AppToaster.show({message: `Ein Zeichen konnte nicht in der PDF eingebunden werden: ${error.unsupportedChar}`, intent: "danger"}) + break; + + } + + setMode(Mode.input) + } + ); + } + if (mode === Mode.input) return diff --git a/administration/src/components/generation/PdfFactory.ts b/administration/src/components/generation/PdfFactory.ts index e9243742e..7a6ac1fcd 100644 --- a/administration/src/components/generation/PdfFactory.ts +++ b/administration/src/components/generation/PdfFactory.ts @@ -5,6 +5,34 @@ import {CardActivateModel} from "../../generated/compiled"; import uint8ArrayToBase64 from "../../util/uint8ArrayToBase64"; import {format, fromUnixTime} from "date-fns"; import {getRegions_regions as Region} from "../../graphql/regions/__generated__/getRegions"; +import {Err, Ok, Result} from "../../util/result"; + +export type PDFError = UnexpectedUnicodeError | PDFGenerationError + +type PDFGenerationError = { + type: "pdf-generation" +} + +type UnexpectedUnicodeError = { + type: "unicode" + unsupportedChar: string +} + +function checkForeignText(doc: jsPDF, text: string): string | null { + let font = doc.getFont(); + + for (let i = 0; i < text.length; i++) { + if (font.metadata.characterToGlyph(text.charCodeAt(i)) == 0) { + return text.charAt(i) + } + } + + return null +} + +export function checkCardActivateModel(doc: jsPDF, model: CardActivateModel): string | null { + return checkForeignText(doc, model.fullName) +} function addLetter(doc: jsPDF, model: CardActivateModel, region: Region) { const pageSize = doc.internal.pageSize @@ -59,8 +87,8 @@ Aussteller: ${region.prefix} ${region.name}`, }); } -export function generatePdf(models: CardActivateModel[], region: Region) { - const doc = new jsPDF({ +export async function generatePdf(models: CardActivateModel[], region: Region): Promise> { + const doc: jsPDF = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' @@ -73,7 +101,16 @@ export function generatePdf(models: CardActivateModel[], region: Region) { doc.setFont("NotoSans-Regular") for (let k = 0; k < models.length; k++) { - addLetter(doc, models[k], region) + let model = models[k]; + let unsupportedChar = checkCardActivateModel(doc, model); + if (unsupportedChar) { + return Err({ + type: "unicode", + unsupportedChar + }) + } + + addLetter(doc, model, region) if (k !== models.length - 1) doc.addPage() } @@ -85,5 +122,6 @@ export function generatePdf(models: CardActivateModel[], region: Region) { creator: "Bayern" }); - return new Blob([doc.output('blob')], {type: "application/pdf"}) + let blob = doc.output('blob'); + return Ok(new Blob([blob], {type: "application/pdf"})) } diff --git a/administration/src/components/generation/generateCards.tsx b/administration/src/components/generation/generateCards.tsx index 1fde5f156..5ded2e669 100644 --- a/administration/src/components/generation/generateCards.tsx +++ b/administration/src/components/generation/generateCards.tsx @@ -7,11 +7,12 @@ import generateHashFromCardDetails from "../../util/generateHashFromCardDetails" import uint8ArrayToBase64 from "../../util/uint8ArrayToBase64"; import {addCard, addCardVariables} from "../../graphql/verification/__generated__/addCard"; import {ADD_CARD} from "../../graphql/verification/mutations"; -import {generatePdf} from "./PdfFactory"; +import {generatePdf, PDFError} from "./PdfFactory"; import {ApolloClient} from "@apollo/client"; import {getRegions_regions as Region} from "../../graphql/regions/__generated__/getRegions"; +import {Result} from "../../util/result"; -const generateCards = async (client: ApolloClient, cardCreationModels: CardCreationModel[], region: Region) => { +const generateCards = async (client: ApolloClient, cardCreationModels: CardCreationModel[], region: Region): Promise> => { const activateModels = cardCreationModels.map(model => { const cardType = model.cardType === CardType.gold ? CardActivateModel.CardType.GOLD