/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable prefer-const */ import type { IActivityHandler } from "@vertigis/workflow"; import { PDFDocument, degrees, rgb, PDFName, PDFString, TextAlignment, PDFArray, StandardFonts, PDFRef } from 'pdf-lib'; import axios from 'axios'; /** An interface that defines the inputs of the activity. */ interface PDFMapGeneratorInputs { /** * @displayName img Base64 Array * @description img Base64 Array * @required */ imgBase64: string[]; /** * @displayName PDF Link * @description The link of blank PDF Template. */ pdfLink: string; /** * @displayName Legend img Base64 * @description Legend img Base64 created by Legend creator */ LegendImgBase64: string; /** * @displayName Scale Array * @description Array contains the scale numbers catched from each extent */ scaleArray: number[]; /** * @displayName Map Title * @description Map Title */ Title: string; /** * @displayName Revision Description * @description Revision Description */ Descr: string; /** * @displayName Note * @description Note */ Note: string; /** * @displayName Revision * @description Revision */ Rev: string; /** * @displayName Printer Name * @description Printer Name */ PrintUser: string; /** * @displayName Rotation angle * @description Rotation angle for compass */ rotation: number[]; /** * @displayName QR Code Url * @description QR Code Url */ QrUrl?: string[]; /** * @displayName link inside the button * @description Display button lead to QR URLs rather than QRCodes */ buttonLink?: string[]; /** * @displayName QR Code image array (url ending with .jpg etc) * @description Put QR image url so it will be put in pdf. If this value given, QR URL and button parameters will be ignored. */ QrCodeImage?: string[]; /** * @displayName Field View Id * @description Field View Id */ FiewViewId: string[] | number[]; /** * @displayName Array of geocoded addresses * @description Array of geocoded addresses */ addressList: string[]; /** * @displayName specific type name of print template * @description specific type name of print template, e.g.'VMP' */ template: string; /** * @displayName Object to provide extra field detail, by key-value(string) * @description Object to provide extra field detail, by key-value(string) */ extraField: object; } /** An interface that defines the outputs of the activity. */ interface PDFMapGeneratorOutputs { /** * @description The result of the activity. */ result: string; } /** * @displayName PDF Map Generator * @category EBTA Utilities * @description An activity which generate the Map PDF using map and legend screenshot */ export default class PDFMapGeneratorActivity implements IActivityHandler { private async fetchData(url: string): Promise { try { console.log('EBTA PDFMapGenerator version 1.1') if (typeof fetch !== 'undefined') { const response = await fetch(url, { mode: 'cors', credentials: 'omit' }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.arrayBuffer(); } else { const response = await axios.get(url, { responseType: 'arraybuffer', withCredentials: false }); return response.data; } } catch (error) { console.error(`Failed to fetch data from ${url}:`, error); throw new Error(`Failed to fetch data from ${url}`); } } private async reencodeImageToArrayBuffer(arrayBuffer: ArrayBuffer): Promise { const blob = new Blob([arrayBuffer], { type: 'image/jpeg' }); const img = new Image(); // Convert the array buffer to a blob URL img.src = URL.createObjectURL(blob); return new Promise((resolve, reject) => { img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); if (!ctx) return reject(new Error('Canvas context could not be created')); ctx.drawImage(img, 0, 0); canvas.toBlob((blob) => { if (!blob) return reject(new Error('Failed to convert canvas to Blob')); const reader = new FileReader(); reader.onloadend = () => { if (reader.result instanceof ArrayBuffer) { resolve(reader.result); } else { reject(new Error('Failed to read Blob as ArrayBuffer')); } }; reader.readAsArrayBuffer(blob); }, 'image/jpeg'); }; img.onerror = (err) => reject(new Error('Image load error: ')); }); } private arrayBufferToBase64(buffer: ArrayBuffer): string { let binary = ''; const bytes = new Uint8Array(buffer); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } private createDownloadUrl(base64String: string): string { return `data:application/pdf;base64,${base64String}`; } private base64ToArrayBuffer(base64: string): ArrayBuffer { const binaryString = atob(base64.split(',')[1]); const len = binaryString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; } private isValidImage(base64: string): boolean { return base64.startsWith('data:image/png;base64,') || base64.startsWith('data:image/jpg;base64,') || base64.startsWith('data:image/jpeg;base64,'); } private nearestNiceInteger(num) { const changeLimit = num * 0.2; const bigInterger = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2]; for (const multiple of bigInterger) { const rounded = Math.round(num / multiple) * multiple; if (Math.abs(rounded - num) <= changeLimit) { return rounded; } } // If no suitable "nice" integer is found within the 20% range, return the original number return num; } /** Perform the execution logic of the activity. */ async execute(inputs: PDFMapGeneratorInputs): Promise { const defaulPDF = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/A3Layout_Landscape_General_Form.pdf' const pdfURL = inputs.pdfLink ? inputs.pdfLink : defaulPDF const imgBase64Array = inputs.imgBase64; // eslint-disable-next-line @typescript-eslint/unbound-method if (!imgBase64Array || imgBase64Array.length === 0 || !imgBase64Array.every(this.isValidImage)) { return { result: 'Invalid image input', }; } try { const pdfBuffer = await this.fetchData(pdfURL); console.log('PDF fetched successfully'); const originalPdfDoc = await PDFDocument.load(pdfBuffer); const newPdfDoc = await PDFDocument.create(); const [originalPage] = await newPdfDoc.copyPages(originalPdfDoc, [0]); const { width, height } = originalPage.getSize(); const pixel2MeterOnPaper = 1 / (71.42857) * 0.0254 const image1url = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/caution.png' const image2url = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/warning.png' const image3url = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/dbyd.png' const imageBarurl = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/Scalebar.png' const imageNorthUrl = 'https://cdn-icons-png.flaticon.com/512/3236/3236586.png' const image10kmurl = 'https://ebta.maps.arcgis.com/sharing/rest/content/items/c15211b9826549f69a13c3f3d32a841b/resources/resources/prints/speedlimit10PNG.png' let image10Buffer, image1Buffer, image2Buffer, image3Buffer if (inputs.template == 'VMP') { image10Buffer = await this.fetchData(image10kmurl); console.log('Image10 fetched successfully'); } else { image1Buffer = await this.fetchData(image1url); console.log('Image1 fetched successfully'); image2Buffer = await this.fetchData(image2url); console.log('Image2 fetched successfully'); image3Buffer = await this.fetchData(image3url); console.log('Image3 fetched successfully'); } const imageBarBuffer = await this.fetchData(imageBarurl); console.log('Image scale bar fetched successfully'); const imageNorthBuffer = await this.fetchData(imageNorthUrl); console.log('Image North Arrow fetched successfully'); let legendBuffer if (inputs.LegendImgBase64) { legendBuffer = this.base64ToArrayBuffer(inputs.LegendImgBase64); } const filledPDFs: Uint8Array[] = []; for (let i = 0; i < imgBase64Array.length; i++) { let doc = await PDFDocument.load(pdfBuffer); let form = doc.getForm(); let newPage = doc.getPage(0) let imgBase64 = imgBase64Array[i] let imageBuffer = this.base64ToArrayBuffer(imgBase64); let image; if (imgBase64.startsWith('data:image/png;base64,')) { image = await doc.embedPng(imageBuffer); } else if (imgBase64.startsWith('data:image/jpeg;base64,') || imgBase64.startsWith('data:image/jpg;base64,')) { image = await doc.embedJpg(imageBuffer); } console.log(`Main Image ${i} data processed successfully`); let image1, image2, image3, image10 if (inputs.template == 'VMP') { image10 = await doc.embedPng(image10Buffer); console.log('image10 embed successfully') } else { image1 = await doc.embedPng(image1Buffer); image2 = await doc.embedPng(image2Buffer); image3 = await doc.embedPng(image3Buffer); console.log('image1,2,3 embed successfully') } const imageBar = await doc.embedPng(imageBarBuffer); const imageNorth = await doc.embedPng(imageNorthBuffer); let legendImage if (inputs.LegendImgBase64) { if (inputs.LegendImgBase64.startsWith('data:image/png;base64,')) { legendImage = await doc.embedPng(legendBuffer); } else if (inputs.LegendImgBase64.startsWith('data:image/jpeg;base64,') || inputs.LegendImgBase64.startsWith('data:image/jpg;base64,')) { legendImage = await doc.embedJpg(legendBuffer); } console.log('legend image embed successfully') } // eslint-disable-next-line @typescript-eslint/no-unsafe-argument newPage.drawImage(image, { x: width * 0.0205, y: height * (0.1695), width: width * 0.8113, height: height * 0.8029, }); console.log('map image drawn successfully') if (inputs.LegendImgBase64) { //Define position & size of legend screeshot, depent on width/height ratio if (legendImage.width / legendImage.height > 0.259) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument newPage.drawImage(legendImage, { x: width * 0.83796, y: height * (0.94611) - width * 0.14 * (legendImage.height / legendImage.width), width: width * 0.14, height: width * 0.14 * (legendImage.height / legendImage.width), }); } else { if (legendImage.width / legendImage.height < 0.259 / 1.6) { if (legendImage.width / legendImage.height < 0.259 / 3.95) { if (legendImage.width / legendImage.height < 0.259 / 5.5) { console.log("split legend into 3 columns"); const fullLegendimg = new Image(); fullLegendimg.src = inputs.LegendImgBase64; fullLegendimg.onload = async () => { const neckHeight = Math.floor(legendImage.height * 0.33); // Approximate neck position const waistHeight = Math.floor(legendImage.height * 0.66); // Approximate waist position // Neck to Top const neckUpCanvas = document.createElement('canvas'); neckUpCanvas.width = legendImage.width; neckUpCanvas.height = neckHeight; const neckUpCtx = neckUpCanvas.getContext('2d'); neckUpCtx!.drawImage(fullLegendimg, 0, 0, legendImage.width, neckHeight, 0, 0, legendImage.width, neckHeight); // Waist to Neck const waistUpCanvas = document.createElement('canvas'); waistUpCanvas.width = legendImage.width; waistUpCanvas.height = waistHeight - neckHeight; const waistUpCtx = waistUpCanvas.getContext('2d'); waistUpCtx!.drawImage(fullLegendimg, 0, neckHeight, legendImage.width, waistHeight - neckHeight, 0, 0, legendImage.width, waistHeight - neckHeight); // Bottom to Waist const waistDownCanvas = document.createElement('canvas'); waistDownCanvas.width = legendImage.width; waistDownCanvas.height = legendImage.height - waistHeight; const waistDownCtx = waistDownCanvas.getContext('2d'); waistDownCtx!.drawImage(fullLegendimg, 0, waistHeight, legendImage.width, legendImage.height - waistHeight, 0, 0, legendImage.width, legendImage.height - waistHeight); const neckUpBase64 = neckUpCanvas.toDataURL('image/jpeg'); const upBuffer1 = this.base64ToArrayBuffer(neckUpBase64); const waistUpBase64 = waistUpCanvas.toDataURL('image/jpeg'); const upBuffer2 = this.base64ToArrayBuffer(waistUpBase64); const waistDownBase64 = waistDownCanvas.toDataURL('image/jpeg'); const downBuffer = this.base64ToArrayBuffer(waistDownBase64); const LegendNeckUp = await doc.embedJpg(upBuffer1); const LegendWaistUp = await doc.embedJpg(upBuffer2); const LegendWaistDown = await doc.embedJpg(downBuffer); newPage.drawImage(LegendNeckUp, { x: width * 0.83796, y: height * 0.94611 - width * 0.14 * 0.33 * (LegendNeckUp.height / LegendNeckUp.width), width: width * 0.14 * 0.33, height: width * 0.14 * 0.33 * (LegendNeckUp.height / LegendNeckUp.width), }); newPage.drawImage(LegendWaistUp, { x: width * 0.83796 + width * 0.14 * 0.33, y: height * 0.94611 - width * 0.14 * 0.33 * (LegendWaistUp.height / LegendWaistUp.width), width: width * 0.14 * 0.33, height: width * 0.14 * 0.33 * (LegendWaistUp.height / LegendWaistUp.width), }); newPage.drawImage(LegendWaistDown, { x: width * 0.83796 + width * 0.14 * .66, y: height * 0.94611 - width * 0.14 * 0.33 * (LegendWaistDown.height / LegendWaistDown.width), width: width * 0.14 * 0.33, height: width * 0.14 * 0.33 * (LegendWaistDown.height / LegendWaistDown.width) }); } } else { //split into 2 columns & strectch console.log("split legend into 2 columns & stretch ") const fullLegendimg = new Image(); fullLegendimg.src = inputs.LegendImgBase64 fullLegendimg.onload = async () => { const halfHeight = Math.floor(legendImage.height / 2) const waistUpCanvas = document.createElement('canvas'); waistUpCanvas.width = legendImage.width; waistUpCanvas.height = halfHeight; const waistUpCtx = waistUpCanvas.getContext('2d'); waistUpCtx!.drawImage(fullLegendimg, 0, 0, legendImage.width, halfHeight, 0, 0, legendImage.width, halfHeight); const waistDownCanvas = document.createElement('canvas'); waistDownCanvas.width = legendImage.width; waistDownCanvas.height = halfHeight; const waistDownCtx = waistDownCanvas.getContext('2d'); waistDownCtx!.drawImage(fullLegendimg, 0, halfHeight, legendImage.width, halfHeight, 0, 0, legendImage.width, halfHeight); const waistUpBase64 = waistUpCanvas.toDataURL('image/jpeg'); const upBuffer = this.base64ToArrayBuffer(waistUpBase64); const waistDownBase64 = waistDownCanvas.toDataURL('image/jpeg'); const downBuffer = this.base64ToArrayBuffer(waistDownBase64); const LegendUp = await doc.embedJpg(upBuffer); const LegendDown = await doc.embedJpg(downBuffer); newPage.drawImage(LegendUp, { x: width * 0.83796, y: height * (0.94611 - 0.77), width: height * 0.77 * (LegendUp.width / LegendUp.height), height: height * 0.77, }); newPage.drawImage(LegendDown, { x: width * 0.83796 + height * 0.77 * (LegendDown.width / LegendDown.height), y: height * (0.94611 - 0.77), width: height * 0.77 * (LegendDown.width / LegendDown.height), height: height * 0.77, }) } } } else { //split into 2 columns & no strectch console.log("split legend into 2 columns & no strectch") const fullLegendimg = new Image(); fullLegendimg.src = inputs.LegendImgBase64 fullLegendimg.onload = async () => { const halfHeight = Math.floor(legendImage.height / 2) const waistUpCanvas = document.createElement('canvas'); waistUpCanvas.width = legendImage.width; waistUpCanvas.height = halfHeight; const waistUpCtx = waistUpCanvas.getContext('2d'); waistUpCtx!.drawImage(fullLegendimg, 0, 0, legendImage.width, halfHeight, 0, 0, legendImage.width, halfHeight); const waistDownCanvas = document.createElement('canvas'); waistDownCanvas.width = legendImage.width; waistDownCanvas.height = halfHeight; const waistDownCtx = waistDownCanvas.getContext('2d'); waistDownCtx!.drawImage(fullLegendimg, 0, halfHeight, legendImage.width, halfHeight, 0, 0, legendImage.width, halfHeight); const waistUpBase64 = waistUpCanvas.toDataURL('image/jpeg'); const upBuffer = this.base64ToArrayBuffer(waistUpBase64); const waistDownBase64 = waistDownCanvas.toDataURL('image/jpeg'); const downBuffer = this.base64ToArrayBuffer(waistDownBase64); const LegendUp = await doc.embedJpg(upBuffer); const LegendDown = await doc.embedJpg(downBuffer); newPage.drawImage(LegendUp, { x: width * 0.83796, y: height * (0.94611) - width * 0.14 * 0.5 * (LegendUp.height / LegendUp.width), width: width * 0.14 * 0.5, height: width * 0.14 * 0.5 * (LegendUp.height / LegendUp.width), }); newPage.drawImage(LegendDown, { x: width * 0.83796 + width * 0.14 * 0.505, y: height * (0.94611) - width * 0.14 * 0.5 * (LegendDown.height / LegendDown.width), width: width * 0.14 * 0.5, height: width * 0.14 * 0.5 * (LegendDown.height / LegendDown.width) }) } } } else { // A little bit slim, just stretch newPage.drawImage(legendImage, { x: width * 0.83796, y: height * (0.94611 - 0.77), width: height * 0.77 * (legendImage.width / legendImage.height), height: height * 0.77, }); } } console.log('legend image drawn successfully') } const lableSize = 100 if (inputs.template == 'VMP') { newPage.drawImage(image10, { x: 40, y: 145, width: 60, height: 60 * (image10.height / image10.width), }); console.log('image10 drawn successfully') } else { newPage.drawImage(image1, { x: 28, y: 145, width: lableSize, height: lableSize * (image1.height / image1.width), }); newPage.drawImage(image2, { x: 28 + lableSize + 6, y: 145, width: lableSize, height: lableSize * (image2.height / image2.width), }); newPage.drawImage(image3, { x: 28 + 2 * lableSize + 6 * 2, y: 145, width: lableSize * .8, height: lableSize * .8 * (image3.height / image3.width), }); console.log('image1,2,3 drawn successfully') } console.log(`success in page. ${i} `) // north arrow const centerX = 40; const centerY = 110; const halfWidth = 10; const halfHeight = 10; const angle = inputs.rotation[i] const newX = centerX - halfWidth * 1.22 * Math.cos((angle - 35) / 180 * Math.PI); const newY = centerY + halfHeight * 1.22 * Math.sin((angle - 35) / 180 * Math.PI); newPage.drawImage(imageNorth, { x: newX, y: newY, width: 20, height: 20, rotate: degrees(-angle), }); // scale bar const barScale = 0.5 const barMaxDistance_nochange = imageBar.width * pixel2MeterOnPaper * inputs.scaleArray[i] * 0.544797456 * barScale const niceBar = this.nearestNiceInteger(barMaxDistance_nochange) const newBarPixel = imageBar.width * (niceBar / barMaxDistance_nochange) * barScale const scalebarX = 59 newPage.drawImage(imageBar, { x: scalebarX, y: 93, width: newBarPixel, height: imageBar.height * barScale, }); // 0 newPage.drawText('0', { x: scalebarX, y: 113, size: 8 }) // half scale bar number newPage.drawText(Math.ceil(niceBar / 2).toString(), { x: scalebarX + newBarPixel / 2,//dynamic y: 113, size: 8 }) // 100% scale bar number newPage.drawText(Math.ceil(niceBar).toString(), { x: scalebarX + newBarPixel,//dynamic y: 113, size: 8 }) const font = await doc.embedFont(StandardFonts.Helvetica); //title const titleField = form.getTextField('Title.' + i) titleField.setText(inputs.Title) titleField.setAlignment(TextAlignment.Center) titleField.updateAppearances(font) //printer const printerField = form.getTextField('Printer.' + i) printerField.setText(inputs.PrintUser) printerField.updateAppearances(font) //description const descrField = form.getTextField('RevDescr.' + i) descrField.setText(inputs.Descr) descrField.updateAppearances(font) //address const addrField = form.getTextField('Address.' + i) addrField.setText(inputs.addressList[i]) addrField.updateAppearances(font) //scale ratio const scaletext = '1:' + Math.ceil(inputs.scaleArray[i] * 0.544797456); const scaleNumber = form.getTextField('Scale.' + i) scaleNumber.setText(scaletext) scaleNumber.enableReadOnly() scaleNumber.updateAppearances(font) //revision const revField = form.getTextField('Rev.' + i) revField.setText(inputs.Rev) revField.updateAppearances(font) if (inputs.template == 'VMP') { //supervisor const supervisorField = form.getTextField('Supervisor') supervisorField.setText(inputs.extraField['supervisor']) supervisorField.setAlignment(TextAlignment.Center) supervisorField.updateAppearances(font) //UHF const uhfField = form.getTextField('UHF') uhfField.setText(inputs.extraField['UHF']) uhfField.setAlignment(TextAlignment.Center) uhfField.updateAppearances(font) console.log('VMP text inserted successful') } //date const today = new Date(); const formattedDate = today.toLocaleDateString('en-AU'); const dateField1 = form.getTextField('DateA.' + i); dateField1.setText(formattedDate); dateField1.updateAppearances(font) const dateField2 = form.getTextField('DateB.' + i); dateField2.setText(formattedDate); dateField2.updateAppearances(font) //Note, will wrap to new line] const noteField = form.getTextField('Note.' + i); noteField.setText(inputs.Note); noteField.updateAppearances(font) console.log('Give values Successful') let FieldNames = ['Title', 'Printer', 'RevDescr', 'Address', 'Rev', 'Scale', 'DateA', 'DateB', 'Note'] //Remove those not used in current page for (let k = 0; k < 10; k++) { if (k != i) { for (let field of FieldNames) { let tmp = form.getTextField(field + '.' + k) const widget0 = tmp.acroField.getWidgets()[0]; const AP = widget0.ensureAP(); AP.set(PDFName.of('N'), PDFRef.of(0, 0)); form.removeField(tmp); } } } console.log('Text Fields removal successful') const boldFont = await doc.embedFont(StandardFonts.HelveticaBold); console.log('bold font embed successful') if (inputs.template == 'VMP') { newPage.drawText('Maximum Speed 10KM/h within Work Area', { x: (28 + 111), y: 145, size: 25, color: rgb(0, 0, 0), // WHITE color, IGNORE VSCODE COLOR font: boldFont, }); newPage.drawText('Maximum Speed 10KM/h within Work Area', { x: (28 + 109), y: 147, size: 25, color: rgb(1, 1, 1), // WHITE color, IGNORE VSCODE COLOR font: boldFont, }); console.log('speed limit text inserted') } if (inputs.buttonLink && inputs.buttonLink.length > 0) { const buttonX = width * 0.74 const buttonY = 130 const buttonW = 90 const buttonH = 50 const buttonBackgroundUrl = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/gis-button.png' const buttonBackgroundBuffer = await this.fetchData(buttonBackgroundUrl); const buttonEmbed = await doc.embedPng(buttonBackgroundBuffer); newPage.drawImage(buttonEmbed, { x: buttonX, y: buttonY, width: buttonW, height: buttonH, }); newPage.drawText('Go to EBTA GIS', { x: buttonX + 5, y: buttonY + 22, size: 10, color: rgb(1, 1, 1), // White color, VS CODE COLOR PICKER IS WRONG font: boldFont, }); const existingAnnots = newPage.node.lookup(PDFName.of('Annots')); let annotationsArray = existingAnnots instanceof PDFArray ? existingAnnots : doc.context.obj([]); const linkAnnotation = doc.context.register( doc.context.obj({ Type: 'Annot', Subtype: 'Link', Rect: [buttonX, buttonY, buttonX + buttonW, buttonY + buttonH], Border: [0, 0, 0], A: { Type: 'Action', S: 'URI', URI: PDFString.of((i <= (inputs.buttonLink.length - 1)) ? inputs.buttonLink[i] : inputs.buttonLink[inputs.buttonLink.length - 1]), }, }) ); // Append the new annotation to the existing ones annotationsArray.push(linkAnnotation); // Set the updated annotations array back to the page newPage.node.set(PDFName.of('Annots'), annotationsArray); console.log('EBTA button inserted') } //draw a QR Code image only when Qr URL is given && QR image not given if (inputs.QrUrl && inputs.QrUrl.length > 0 && (inputs.QrCodeImage == undefined)) { //QR Code // eslint-disable-next-line @typescript-eslint/no-var-requires const QRCode = require('qrcode'); const qrDataUrl = (i <= (inputs.QrUrl.length - 1)) ? await QRCode.toDataURL(inputs.QrUrl[i]) : await QRCode.toDataURL(inputs.QrUrl[inputs.QrUrl.length - 1]); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const qrImageBytes = await fetch(qrDataUrl).then(res => res.arrayBuffer()); const qrImage = await doc.embedPng(qrImageBytes); newPage.drawImage(qrImage, { x: 210, y: 93, width: 40, height: 40, }); const existingAnnots = newPage.node.lookup(PDFName.of('Annots')); let annotationsArray = existingAnnots instanceof PDFArray ? existingAnnots : doc.context.obj([]); const linkAnnotation = doc.context.register( doc.context.obj({ Type: 'Annot', Subtype: 'Link', Rect: [210, 93, 250, 93 + 40], Border: [0, 0, 0], A: { Type: 'Action', S: 'URI', URI: PDFString.of((i <= (inputs.QrUrl.length - 1)) ? inputs.QrUrl[i] : inputs.QrUrl[inputs.QrUrl.length - 1]), }, }) ); // Append the new annotation to the existing ones annotationsArray.push(linkAnnotation); // Set the updated annotations array back to the page newPage.node.set(PDFName.of('Annots'), annotationsArray); } if (inputs.QrCodeImage && inputs.QrCodeImage.length > 0) { const inputQrImageUrl = (i <= (inputs.QrCodeImage.length - 1)) ? inputs.QrCodeImage[i] : inputs.QrCodeImage[inputs.QrCodeImage.length - 1] const inputQrBuffer = await this.fetchData(inputQrImageUrl); console.log('input QR Code image fetched successfully'); let inputQRembed console.log(inputQrImageUrl); console.log(inputQrImageUrl.toLowerCase().includes(".png")); console.log(inputQrImageUrl.toLowerCase().includes(".jpg") || inputQrImageUrl.toLowerCase().includes(".jpeg")); if (inputQrImageUrl.toLowerCase().includes(".png")) { inputQRembed = await doc.embedPng(inputQrBuffer); console.log('embed QR Code image as png'); } else if (inputQrImageUrl.toLowerCase().includes(".jpg") || inputQrImageUrl.toLowerCase().includes(".jpeg")) { console.log(inputQrBuffer) const dataView = new DataView(inputQrBuffer); console.log(dataView) // Read the first two bytes (SOI marker) const soiMarker = dataView.getUint16(0); console.log(soiMarker) // Check if the SOI marker is correct if (soiMarker === 0xFFD8) { console.log('SOI marker found: 0xFFD8, keep going'); inputQRembed = await doc.embedJpg(inputQrBuffer); } else { console.log('SOI marker not found but forcing to be correct.'); let fixedArrayBuffer = await this.reencodeImageToArrayBuffer(inputQrBuffer) inputQRembed = await doc.embedJpg(fixedArrayBuffer); } console.log('embed QR Code image as jpg successful'); } newPage.drawImage(inputQRembed, { x: 210, y: 93, width: 40, height: 40, }); console.log('draw input QR Code image finished'); } // field view ID, only happens when has value if (inputs.FiewViewId && inputs.FiewViewId.length > 0) { const fieldViewIdField = form.createTextField('FieldViewId' + i); fieldViewIdField.setText((i <= (inputs.FiewViewId.length - 1)) ? inputs.FiewViewId[i].toString() : inputs.FiewViewId[inputs.FiewViewId.length - 1].toString()); fieldViewIdField.addToPage(newPage, { x: 213, y: 78, height: 12, width: 40, borderWidth: 0, textColor: rgb(0, 0, 0), backgroundColor: rgb(1, 1, 1), borderColor: rgb(1, 1, 1), }); console.log('field View ID text added.'); } filledPDFs.push(await doc.save()); } const finalPDF = await PDFDocument.create(); // Copy pages from all filled PDFs into the final document for (const pdfBytes of filledPDFs) { const pdf = await PDFDocument.load(pdfBytes); const copiedPages = await finalPDF.copyPages(pdf, pdf.getPageIndices()); copiedPages.forEach(page => finalPDF.addPage(page)); } const pdfBytes = await finalPDF.save(); const base64String = this.arrayBufferToBase64(pdfBytes); //was and still working fine, ignore the bug report const downloadUrl = this.createDownloadUrl(base64String); console.log('pdf file created and ready to download'); return { /** * @description The result pdf created for printing. */ result: downloadUrl, }; } catch (error) { console.error('Error in PDF Map Generator:', error); return { result: 'ERROR', }; } } }