简体   繁体   中英

Converting html to pdf: how to dynamically add pages to pdf when html content is overflowing? (dynamic multiple page pdf)

I am using Handlebars and Puppeteer to compile html into dynamic .pdf invoices. Sometimes the invoices can get rather large (2-3 pages). As a result, the overflowing html content gets cut off, see this image: 这个图片

I've made a github repo with all my code and problem you can check out here if you are interested (instructions to run the project in read.me ).

So the way I am trying to solve this, is that after Handlebars rendered my template, I start a headless browser with puppeteer and set the rendered html to to page.

//renderTemplate is a function that reads the .hbs file and compiles the template to html.
const html = renderTemplate(data)

await page.setContent(html, {
  waitUntil: 'load'
})

I then keep track of the height with:

const heightTracker = await page.$(".height-tracker");
const heightTrackerDimensions = await heightTracker.boundingBox()

If this height exceeds, say 1100, then a new page has to be added and the last item in my orders array has to be cut from the html and pasted to a new page. The best I could come up with is something like this:

 if (heightTrackerDimensions.height > 1100) {
    let pageIndex = 0;
    let height = 0;

    let sections = await page.$$(".item");
    for (let i = 0; i < sections.length; i++) {
      const sectionDimensions = await sections[i].boundingBox();

      if (height + sectionDimensions.height > 1100) {
        pageIndex++;
        console.log(i)

        await page.evaluate(() => {
          let sections = document.querySelectorAll(`.item`)
          const page = document.querySelector('.page1')
          page.insertAdjacentHTML('afterend', `
              <div style="page-break-after: always;"></div>
              <div class="invoice page2">
               <div class="invoice-inner">
                 <div class="invoice-body">
                   <div class="main-column">
                    <div class="height-tracker">
                      ${sections[i].innerHTML}
                    </div>
                   </div>
                  <div class="secondary-column">
                 </div>
                </div>
               </div>
              </div>
          `
          )
          sections[i].remove()
        });
        height += sectionDimensions.height
      } else {
        height += sectionDimensions.height
      }
    }
  }

But this just doesn't work well and breaks once there are more items that are overflowing from page 1. Maybe I am over complicating to much. Maybe there are much simpler ways to achieve the same goal?

I am just breaking my head over this issue. How can I make it so that overflowing html content automatically gets added to a new pdf page with proper margin and padding? What other options do I have? I can't be the only one struggling with multi page pdf's with Puppeteer...

I've encountered the same issues with PDF generation, though not exactly in the same context.

I've played about with your code, and made a few changes, mainly to allow Puppeteer to create the pages as needed by itself, hopefully simplifying things:

async function printPDF() {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();

  const orders = [
    { item: "Completely new website design to your company style", price: "$300" },
    { item: "Conversions optimalization and management of conversions", price: "$300" },
    { item: "Implementation of content management system - Wordpress", price: "$300" },
    { item: "Installation of Premium Wordpress Theme yearly renewed (Divi)", price: "$300" },
    { item: "Responsive desktop / mobile / tablet", price: "$300" },
    { item: "Loadspeed optimized", price: "$300" },
    { item: "Free service contract (1 year)", price: "$300" },
    { item: "Media Manager", price: "$300" },
    { item: "Google Maps", price: "$300" },
    { item: "Contactforms", price: "$300" },
    { item: "Search Engine indexation", price: "$300" },
    { item: "Google Analytics / Google search console implementation", price: "$300" },
  ];

  const data = {
    invoiceNumber: "123123123",
    company: "Acme Corp",
    orders: orders,
    total: orders.reduce((sum, order) => sum + (+order.price.replace("$", "")) , 0)
  }

  page.on('console', consoleObj => console.log(consoleObj.text()));

  const html = renderTemplate(data)
  await page.emulateMediaType('print');
  await page.setContent(html, {
    waitUntil: 'load'
  })

  const pdf = await page.pdf({
    format: 'A4',
    printBackground: true,
    margin: {
        top: "10mm",
        right: "10mm",
        bottom: "10mm",
        left: "10mm"
    },
    format: "A4"
  })

  
  await browser.close();
  return pdf

}

I also updated the.invoice class in template.hbs (just to remove height and max-height items):

.invoice {
  background-color: white;
  position: relative;
  background-size: cover;
  display: flex;
  flex-direction: column;
  width: 210mm;
  /*height: 297mm; */
  min-width: 210mm;
  min-height: 297mm;
  max-width: 210mm;
  /*max-height: 297mm; */
  font-variant-ligatures: none !important;
  -webkit-font-smoothing: antialiased !important;
  text-rendering: geometricprecision !important;
  overflow: hidden;
  background-repeat: no-repeat;
  background-position: center center;
}

And the total amount:

<tr class="total">
    <td></td>
    <td>
        Total: ${{ total }}
    </td>
</tr>

An example is here: https://dropmefiles.com/xRuqY

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM