Categories
Java Script Tutorials

PDF two page to single page in JavaScript [pdf-lib]

I have this recurring need where we I have PDF of national Identity Cards. It has front side and back side. Both in separate pages.

Most often than not I need to move it to single page PDF. This was it’s easy to print (thought print tool does this is without much trouble) and some government entities accept images, so it’s easy to export one file Pdf to image rather than two page Pdf.

We will achieve exactly that using awesome pdf-lib. It can create, modify PDF. We can draw elements like text, graphics to it. It can also merge and split PDF files.

Planning

We need three major steps

  1. Accept existing PDF file
  2. Manipulate it using pdf-lib
  3. Display it using iframe

Our rough layout will look like this

A wireframe of input file type and file viewer section

Our first version will just accept file and display it as it is. So let’ prepare some HTML for it

The template

<main>
  <input id="fileInput" type="file" accept=".pdf" />
</main>
<output>
  <iframe> </iframe>
</output>
<script type="module" src="/src/main.ts"></script>

We added some basic input element to grab file and iframe to view it. Let’s go to the implementation part.

Base Styles

html, body {
  height: 100%;
}

body {
  display: grid;
  grid-template-rows: auto 1fr;
}

iframe {
  width: 100%;
  height: 100%;
}

input[type='file'] {
  outline: 1px dashed grey;
  padding: 2rem;
}

We added some styles for base layout. The iframe comes with default dimension which is very small to render the pdf file. So we will set to take all the available space of it’s parent element.

Display file

const fileInput = document.querySelector('input[type="file"]')
const iframe = document.querySelector('iframe')

We are getting reference of those element in JavaScript.

fileInput.addEventListener('change', event => {
  const file = (event.target as HTMLInputElement).files[0]
  const reader = new FileReader()

  reader.onload = async () => {
    iframe.src = reader.result
  }

  reader.readAsDataURL(file)
})

On file change aka file upload we use file reader to read it as DataURL. Then on line 6 we are setting iframe source to reader result.

Output

Screenshot of the sample pdf viewer that we have achieved so far in this tutorial

As we can see the pdf viewer shows 1 of 2 pages. This we don’t want. We want one page for both faces of the page.

We have managed to build a simple input and output viewer.

Implementation

Next we need to involve the pdf-lib. We can achieve our target with following plan

  1. Load document in to pdf-lib
  2. Extract pages 1 and 2
  3. Create a brand new document
  4. Create new page and embed page 1 as top half in newly create page
  5. Embed the page 2 as bottom half of the newly created page
  6. Save document and update the viewer
1. Load document in pdf-lib
2. Extract pages 1 and 2
import { PageSizes, PDFDocument, PDFPage } from 'pdf-lib'

fileInput.addEventListener('change', event => {
  //...
  reader.onload = async () => {

    const doc = await PDFDocument.load(reader.result)
    firstPage= doc.getPage(0);
    secondPage = doc.getPage(1)
    createDocument(firstPage, secondPage)
  }

  reader.readAsArrayBuffer(file)
})

First of all we are reading file as arrayBuffer that is what accepted format on line 7. On next two lines we extract first and last page. We hand over control to createDocument function

3. Create a brand new document
async function createDocument(pageAToEmbed: PDFPage, pageBToEmbed: PDFPage) {
  const doc = await PDFDocument.create()

  const embeddedPageA = await doc.embedPage(pageAToEmbed)
  const embeddedPageB = await doc.embedPage(pageBToEmbed)

We start the async createDocument function with creating a brand new document. Next we need to convert the extracted pages in previous code snippets in to embeddable pages. We have references for both of them

4. Create new page and embed page 1 as top half in newly create page
5. Embed the page 2 as bottom half of the newly created page
      const page = doc.addPage()
      let [width ,height] = PageSizes.A4
    
      page.drawPage(embeddedPageA, { x: 10, y: height/2, width: width, height: height/2 })
    
      page.drawPage(embeddedPageB, { x: 10, y: 0, width, height: height/2 })
    

    Next we need to create a brand new page. On line 2 we have width x height for A4 page. That will be used as dimension calculation later.

    On line 4 there is one important detail, x and y axis doesn’t start from top left. I think they start from bottom left. So I have to move the y axis to middle of the page. This way the first page is rendered on top half part of the new page.

    Same thing happens on line 6 , but this time we move the y axis to 0 which means bottom of the page. When embedded page is drawn it appears on bottom of page.

    6. Save pdf document and update the viewer
      const bytes = await doc.save()
      iframe.src = `data:application/pdf;base64,${bytes.toBase64()}`
    }
    

    We have reached to final lines in the our createDocument function. The line 1 returns the Uint8Array . In the next line we are prepend it with proper data: URL with proper media type and assign it as the source of our iframe. And the result becomes

     This is the final result we get, an image that show when we import pdf and after going through process via pdf-lib we get single page pdf of two page pdfs

    As we you can see page 1 of 1 which indicates we have successfully managed to convert two page pdf to single page pdf with both halves of identity card in one page.

    This is ready to be transported or printed.

    Conclusion

    We have managed to create a final version using pdf-lib library. We started with very basic version. We improved it step by step. I have personally learned how valuable iframe is as part of web platform. It provides so much out of the box which requires very minimum to set it up.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.