在 ReactJS 中将创建的 PDF 与现有的本地 PDF 合并

Merge created PDF with existing local PDF in ReactJS

我使用 react-pdf/renderer and download it using file-saver 在 ReactJS 中创建了一个 PDF。

这是我创建 PDF 并下载它的代码:

const LazyDownloadPDFButton = (number, date, totalHours, formattedHours) => (
        <Button
            className={classes.download}
            onClick={
                async () => {
                    const doc = <InvoicePDF number={number} date={date} totalHours={totalHours} formattedHours={formattedHours} />
                    const asPdf = pdf()
                    asPdf.updateContainer(doc)
                    const blob = await asPdf.toBlob()
                    saveAs(blob, `PDF${number}.pdf`)
                }}>
            Download
        </Button>
    ) 

其中 InvoicePDF 是一个单独的组件,它使用必要的参数呈现 PDF 页面,如 react-pdf/renderer 文档页面。

在下载实际的 PDF 之前,我必须将其与另一个将从计算机驱动器中选择的现有 PDF 合并。为此,我有下一个代码片段:

fileRef = useRef()

<Button onClick={() => fileRef.current.click()}>
    Upload file

    <input
        ref={fileRef}
        type='file'
        style={{ display: 'none' }}
    />
</Button>

哪个returns我的文件的详细信息。

我尝试 updateContainer 使用这个选定的文件,但出现错误。

如何将这个新文件与创建的 InvoicePDF 合并?


与此同时,我尝试从 arrayBuffers 创建最后一个 blob,如下所示:

这是将 created PDFselected PDF 连接起来的函数,它 returns 是正确的总和。

function concatArrayBuffers(buffer1, buffer2) {
        if (!buffer1) {
          return buffer2;
        } else if (!buffer2) {
          return buffer1;
        }
      
        var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
        tmp.set(new Uint8Array(buffer1), 0);
        tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
        return tmp.buffer;
      };

我的方法现在有一个 finalBlob 是用 arrayBuffers 创建的,但是 问题 是结果 PDF 将始终包含只是 content of the second arrayBuffer (which is either the selected pdf or the created pdf)

const LazyDownloadPDFButton = (number, date, totalHours, formattedHours) => (
        <Button
            className={classes.download}
            onClick={
                async () => {
                    const doc = <InvoicePDF number={number} date={date} totalHours={totalHours} formattedHours={formattedHours} />
                    const asPdf = pdf()

                    asPdf.updateContainer(doc)

                    const initialBlob = await new Blob([fileRef.current.files[0]], { type: 'application/pdf' }).arrayBuffer()
                    const blob = await (await asPdf.toBlob()).arrayBuffer()
        
                    const finalArrayBuffer = concatArrayBuffers(initialBlob, blob)

                    const finalBlob = new Blob([finalArrayBuffer], { type: 'application/pdf' })

                    saveAs(finalBlob, `PDF${number}.pdf`)
                }}
        >
            Download
        </Button>
    )

解决方案

经过一些研究和多次失败的尝试后,我得出了一个关于如何以正确的顺序合并 PDFsbonus 添加图像(在我的例子中是签名)的答案在最终 PDF 的每一页上。

这是最终代码:

function base64toBlob(base64Data, contentType) {
    contentType = contentType || '';
    var sliceSize = 1024;
    var byteCharacters = atob(base64Data);
    var bytesLength = byteCharacters.length;
    var slicesCount = Math.ceil(bytesLength / sliceSize);
    var byteArrays = new Array(slicesCount);

    for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
        var begin = sliceIndex * sliceSize;
        var end = Math.min(begin + sliceSize, bytesLength);

        var bytes = new Array(end - begin);
        for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
            bytes[i] = byteCharacters[offset].charCodeAt(0);
        }
        byteArrays[sliceIndex] = new Uint8Array(bytes);
    }

    return new Blob(byteArrays, { type: contentType });
}


async function mergeBetweenPDF(pdfFileList, number) {
    const doc = await PDFDocument.create()

    const getUserSignature = () => {
        switch (selectedUser.id) {
            case 1:
                return FirstImage

            case 2:
                return SecondImage

            default:
                return null
        }

    }

    const pngURL = getUserSignature()
    const pngImageBytes = pngURL ? await fetch(pngURL).then((res) => res.arrayBuffer()) : null

    const pngImage = pngURL ? await doc.embedPng(pngImageBytes) : null
    const pngDims = pngURL ? pngImage.scale(0.5) : null

    const initialPDF = await PDFDocument.load(pdfFileList[0])
    const appendixPDF = await PDFDocument.load(pdfFileList[1])

    const initialPDFPages = await doc.copyPages(initialPDF, initialPDF.getPageIndices())

    for (const page of initialPDFPages) {
        if (pngURL) {
            page.drawImage(pngImage, {
                x: page.getWidth() / 2 - pngDims.width / 2 + 75,
                y: page.getHeight() / 2 - pngDims.height,
                width: pngDims.width,
                height: pngDims.height,

            });
        }
        doc.addPage(page)
    }

    const appendixPDFPages = await doc.copyPages(appendixPDF, appendixPDF.getPageIndices())
    for (const page of appendixPDFPages) {
        if (pngURL) {
            page.drawImage(pngImage, {
                x: page.getWidth() / 2 - pngDims.width / 2 + 75,
                y: page.getHeight() / 2 - pngDims.height,
                width: pngDims.width,
                height: pngDims.height,

            });
        }

        doc.addPage(page)
    }

    const base64 = await doc.saveAsBase64()

    const bufferArray = base64toBlob(base64, 'application/pdf')

    const blob = new Blob([bufferArray], { type: 'application/pdf' })

    saveAs(blob, `Appendix${number}.pdf`)
}

const LazyDownloadPDFButton = (number, date, totalHours, formattedHours) => (
    <Button
        className={classes.download}
        onClick={
            async () => {
                const doc = <InvoicePDF number={number} date={date} totalHours={totalHours} formattedHours={formattedHours} />
                const asPdf = pdf()

                asPdf.updateContainer(doc)

                let initialBlob = await new Blob([fileRef.current.files[0]], { type: 'application/pdf' }).arrayBuffer()
                let appendixBlob = await (await asPdf.toBlob()).arrayBuffer()

                mergeBetweenPDF([initialBlob, appendixBlob], number)
            }}
    >
        Download
    </Button>
)

所以 LazyDownloadPDFButton 是我的按钮,它请求相应的参数来创建最终的 PDF。 InvoicePDF是我创建的带有参数的PDF,initialBlob是我上传到我页面的PDF,它要求在合并的PDF中是第一个,appendixBlob是创建的 PDF 将附加到 initialBlob.

mergeBetweenPDF 中,我正在使用 pdf-lib 库创建最终文档,我在其中创建图像,获取发送的 2 个初始 PDF,循环它们,在每一页上添加图像, 然后将每一页添加到将要下载的最终文档中。

希望有一天这会对某人有所帮助。

只是我做的一个简单的解决方案...

https://github.com/ManasMadan/pdf-actions https://www.npmjs.com/package/pdf-actions

    import { createPDF,pdfArrayToBlob, mergePDF } from "pdf-actions";
    
    // Async Function To Merge PDF Files Uploaded Using The Input Tag in HTML
    const mergePDFHandler = async (files) => {
      // Converting File Object Array To PDF Document Array
      files.forEach((file)=>await createPDF.PDFDocumentFromFile(file))
      // Merging The PDF Files to A PDFDocument
      const mergedPDFDocument = await mergePDF(files)
      // Converting The Merged Document to Unit8Array
      const mergedPdfFile = await mergedPDFDocument.save();
      // Saving The File To Disk
      const pdfBlob = pdfArrayToBlob(mergedPdfFile);
    };