Techumber
Home Blog Work

How to convert HTML to PDF Using JavaScript - Multipage

Published on May 12, 2017

A few months back I wrote an article on how to convert html to pdf using javascript. But there are few limitations with that code. Especially in comments so many readers asking support for long html pages. So, here finally I wrote code from scratch to support multipage when generating pdf for long html pages.

Demo download

A big thanks to https://github.com/MrRio/jsPDF and https://github.com/niklasvh/html2canvas without these it’s not possible for me to do this.

index.html(boilerplate)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>HTML to PDF - techumber
    </title>
    <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/1.12.0/semantic.min.css" />
  </head>
  <body>
    <!-- code goes here -->

    <!-- scripts -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js">
    </script>
    <script type="text/javascript" src="//cdn.rawgit.com/MrRio/jsPDF/master/dist/jspdf.min.js">
    </script>
    <script type="text/javascript" src="//cdn.rawgit.com/niklasvh/html2canvas/0.5.0-alpha2/dist/html2canvas.min.js">
    </script>
    <script type="text/javascript" src="app.js">
    </script>
  </body>
</html>
ript>
    </body>
</html>

index.html (content)

<div class="ui page grid">
  <div class="wide column">
    <h1 class="ui header aligned center">HTML to PDF 2</h1>
    <div class="ui divider hidden"></div>

    <div class="ui segment">
      <div class="ui button aligned center teal" id="create_pdf">Create PDF</div>
      <div class="ui divider"></div>
      <form class="ui form">
        <h4 class="ui dividing header">Personal Information</h4>
        <div class="two fields">
          <div class="field">
            <label for="">First Name</label>
            <input type="text" name="first-name" placeholder="First Name" />
          </div>
          <div class="field">
            <label for="">Last Name</label>
            <input type="text" name="last-name" placeholder="First Name" />
          </div>
        </div>
        <div class="field">
          <label>Biography</label>
          <textarea></textarea>
        </div>
        <div class="field">
          <label>Age</label>
          <input type="number" placeholder="Age" />
        </div>
        <h4 class="ui dividing header">Account Info</h4>
        <div class="two fields">
          <div class="required field">
            <label>Username</label>
            <div class="ui icon input">
              <input type="text" placeholder="Username" />
              <i class="user icon"></i>
            </div>
          </div>
          <div class="required field">
            <label>Password</label>
            <div class="ui icon input">
              <input type="password" />
              <i class="lock icon"></i>
            </div>
          </div>
        </div>

        <h4 class="ui dividing header">Account Info</h4>
        <div class="two fields">
          <div class="required field">
            <label>Username</label>
            <div class="ui icon input">
              <input type="text" placeholder="Username" />
              <i class="user icon"></i>
            </div>
          </div>
          <div class="required field">
            <label>Password</label>
            <div class="ui icon input">
              <input type="password" />
              <i class="lock icon"></i>
            </div>
          </div>
        </div>

        <h4 class="ui dividing header">Account Info</h4>
        <div class="two fields">
          <div class="required field">
            <label>Username</label>
            <div class="ui icon input">
              <input type="text" placeholder="Username" />
              <i class="user icon"></i>
            </div>
          </div>
          <div class="required field">
            <label>Password</label>
            <div class="ui icon input">
              <input type="password" />
              <i class="lock icon"></i>
            </div>
          </div>
        </div>
        <h4 class="ui dividing header">Account Info</h4>
        <div class="two fields">
          <div class="required field">
            <label>Username</label>
            <div class="ui icon input">
              <input type="text" placeholder="Username" />
              <i class="user icon"></i>
            </div>
          </div>
          <div class="required field">
            <label>Password</label>
            <div class="ui icon input">
              <input type="password" />
              <i class="lock icon"></i>
            </div>
          </div>
        </div>
        <h4 class="ui dividing header">Shipping Information</h4>
        <div class="field">
          <label>Name</label>
          <div class="two fields">
            <div class="field">
              <input type="text" name="shipping[first-name]" placeholder="First Name" />
            </div>
            <div class="field">
              <input type="text" name="shipping[last-name]" placeholder="Last Name" />
            </div>
          </div>
        </div>
        <div class="field">
          <label>Billing Address</label>
          <div class="fields">
            <div class="twelve wide field">
              <input type="text" name="shipping[address]" placeholder="Street Address" />
            </div>
            <div class="four wide field">
              <input type="text" name="shipping[address-2]" placeholder="Apt #" />
            </div>
          </div>
        </div>

        <h4 class="ui dividing header">Full Name</h4>

        <div class="three fields">
          <div class="field">
            <label>First name</label>
            <input type="text" placeholder="First Name" />
          </div>
          <div class="field">
            <label>Middle name</label>
            <input type="text" placeholder="Middle Name" />
          </div>
          <div class="field">
            <label>Last name</label>
            <input type="text" placeholder="Last Name" />
          </div>
        </div>

        <h4 class="ui top attached header">Import Settings</h4>
        <div class="ui bottom attached segment">
          <div class="grouped fields">
            <label for="alone">Would you like us to import your current settings?</label>
            <div class="field">
              <div class="ui checkbox">
                <input type="radio" checked="" name="import" />
                <label>Yes</label>
              </div>
            </div>
            <div class="field">
              <div class="ui checkbox">
                <input type="radio" name="import" />
                <label>No</label>
              </div>
            </div>
          </div>
        </div>
        <h4 class="ui dividing header">Settings</h4>
        <h5 class="ui header">Privacy</h5>
        <div class="field">
          <div class="ui checkbox">
            <input type="radio" name="privacy" />
            <label>Allow<b>anyone</b> to see my account</label>
          </div>
        </div>
        <div class="field">
          <div class="ui checkbox">
            <input type="radio" name="privacy" />
            <label>Allow<b>only friends</b> to see my account</label>
          </div>
        </div>
        <h5 class="ui header">Newsletter Subscriptions</h5>
        <div class="field">
          <div class="ui checkbox">
            <input type="checkbox" name="top-posts" />
            <label>Top Posts This Week</label>
          </div>
        </div>
        <div class="field">
          <div class="ui checkbox">
            <input type="checkbox" name="hot-deals" />
            <label>Hot Deals</label>
          </div>
        </div>
        <div class="ui hidden divider"></div>
        <div class="field">
          <div class="ui checkbox">
            <input type="checkbox" name="hot-deals" />
            <label>I agree to the<a href="#">Terms of Service</a>.</label>
          </div>
        </div>
        <div class="ui error message">
          <div class="header">We noticed some issues</div>
        </div>
        <div class="ui submit button blue">Register</div>
      </form>
    </div>
  </div>
</div>

##app.js

var form = $('.form'),
  cache_width = form.width(),
  a4 = [595.28, 990.89]; // for a4 size paper width and height

var canvasImage,
  winHeight = a4[1],
  formHeight = form.height(),
  formWidth = form.width();

var imagePieces = [];

// on create pdf button click
$('#create_pdf').on('click', function() {
  $('body').scrollTop(0);
  imagePieces = [];
  imagePieces.length = 0;
  main();
});

// main code
function main() {
  getCanvas().then(function(canvas) {
    canvasImage = new Image();
    canvasImage.src = canvas.toDataURL('image/png');
    canvasImage.onload = splitImage;
  });
}

// create canvas object
function getCanvas() {
  form.width(a4[0] * 1.33333 - 80).css('max-width', 'none');
  return html2canvas(form, {
    imageTimeout: 2000,
    removeContainer: true,
  });
}

// chop image horizontally
function splitImage(e) {
  var totalImgs = Math.round(formHeight / winHeight);
  for (var i = 0; i < totalImgs; i++) {
    var canvas = document.createElement('canvas'),
      ctx = canvas.getContext('2d');
    canvas.width = formWidth;
    canvas.height = winHeight;
    //                    source region                   dest. region
    ctx.drawImage(
      canvasImage,
      0,
      i * winHeight,
      formWidth,
      winHeight,
      0,
      0,
      canvas.width,
      canvas.height,
    );

    imagePieces.push(canvas.toDataURL('image/png'));
  }
  console.log(imagePieces.length);
  createPDF();
}

// crete pdf using chopped images
function createPDF() {
  var totalPieces = imagePieces.length - 1;
  var doc = new jsPDF({
    unit: 'px',
    format: 'a4',
  });
  imagePieces.forEach(function(img) {
    doc.addImage(img, 'JPEG', 20, 40);
    if (totalPieces) doc.addPage();
    totalPieces--;
  });
  doc.save('techumber-html-to-pdf.pdf');
  form.width(cache_width);
}

getCanvas gives a promise with canvas which we can later use as an Image.

splitImage chops the images horizontally as per a4 size (here I might change a4 height little bit so that it could work with our example).

createPdf is where we will generate pdf document out of images we got earlier.

Hope this help.