chunyenHuang / hummusRecipe

A powerful PDF tool for NodeJS based on HummusJS.
https://hummus-recipe.s3.amazonaws.com/docs/Recipe.html
MIT License
340 stars 91 forks source link

Modify an appended page #153

Open simon-dk opened 4 years ago

simon-dk commented 4 years ago

Hi, Is it possible to either append a page and then modify it, or to modify the same input.pdf page multiple times?

I have an array of recipients and a template.pdf. I need to create one page for each recipient where the name, addresses etc. is be printed on top.

When I'm using .appendPage(), I can't use .editPage after the page has been appended, and when i'm using .editPage() I can only edit the original template.pdf - but I need to do this in a loop.

Has anyone tackled this?

Examples:

const HummusRecipe = require('hummus-recipe');
const pdfDoc = new HummusRecipe('template.pdf', 'output.pdf')

const append = 'template.pdf';

const recipients = [
    {name: 'John Doe', address: "My Streetname 23"}, 
    {name: 'Jane Doe', address: "Another Streetname 44"}
    //...array can be hundreds
]

//example 1. Output is 3 pages identical to template.pdf. No text is added
for (i = 0; i < recipients.length; i++) {
    pdfDoc
    .appendPage(append)
    .text('Name: ' + recipients[i].name, 75, 100)
    .text('Address: ' + recipients[i].address, 75, 115)
    .endPage()
}

//example 2. Output is 1 page, and the text from the last object in the array gets added correctly, but object[0] is missing.
for (i = 0; i < recipients.length; i++) {
    pdfDoc
    .editPage(1)
    .text('Name: ' + recipients[i].name, 75, 100)
    .text('Address: ' + recipients[i].address, 75, 115)
    .endPage()
}

pdfDoc.endPDF();
shaehn commented 4 years ago

I have been part of a team that has written an application that does exactly what you are attempting to do. You cannot edit an appended page. What has to be done is first create the number of template pages you are going to need in a separate document. You can then edit that document to your heart's content. Basically, it is a two step process.

simon-dk commented 4 years ago

Thanks shaehn. Interestingly, I haven’t found any library that does this natively, although it seems to me a pretty ordinary usecase (e.g. for simple mail merging).

So if I understand you right, I should make a first iteration where I loop an appendPage for the length of the array, and then take that output, and make my editPage on top.

Could it be done in the same code, for example instead of saving the file after the first loop, i create a stream that the second loop uses as a template?

simon-dk commented 4 years ago

I created a working code. Takes approx 60 seconds to create a 200 page pdf from a 1,6mb template. The filesize though is 200*1,6 = 350mb, but I guess theres nothing to do about that?

I have made the functions async as to better control them when run on a lambda. The files will also need to be read from and saved to S3.

Any ideas for improvement will be appreciated :-)

const HummusRecipe = require('hummus-recipe');
const template = 'input/input.pdf';

const recipients = [
    {name: 'John Doe', address: "My Streetname 23"}, 
    {name: 'Jane Doe', address: "Another Streetname 44"},
    //...etc.
]

async function createMasterPdf(recipientsLength) {
    const key = `${(Date.now()).toString()}.pdf`;
    const filePath = `output/tmp/${key}`;
    const pdfDoc = new HummusRecipe(template, filePath)

    for (i = 1; i < recipientsLength; i++) {
        pdfDoc
        .appendPage(template)
        .endPage()
    }
    pdfDoc.endPDF();
    return(filePath)

}

async function editMasterPdf(masterPdf, recipientsLength) {
    const key = `${(Date.now()).toString()}.pdf`;
    const filePath = `output/final/${key}`;
    const pdfDoc = new HummusRecipe(masterPdf, filePath)

    for (i = 0; i < recipientsLength; i++) {
        pdfDoc
        .editPage(i+1)
        .text(recipients[i].name, 75, 100)
        .text(recipients[i].address, 75, 115)
        .endPage()
    }
    pdfDoc.endPDF()
    return('Finished file: ' + filePath)

}

const run = async () => {
    const masterPdf = await createMasterPdf(recipients.length);
    const editMaster = await editMasterPdf(masterPdf, recipients.length)
    console.log(editMaster)
}

run()