DEV Community

sunshey
sunshey

Posted on

How to Build a Duplex Print Helper in the Browser with Vue 3 and pdf-lib

Preparing a PDF for duplex (double-sided) printing seems simple — just separate odd and even pages. But getting the page order right for manual flipping is surprisingly tricky. If you print odd pages 1, 3, 5... and then even pages 2, 4, 6..., the pages won't bind correctly. The even pages need to be in reverse order so they align properly when flipped.

Here's how to build a browser-based duplex print helper with Vue 3 and pdf-lib.

The page ordering problem

For long-edge binding (book-style):

Front side Back side
1, 3, 5, 7, 9... 8, 6, 4, 2 (reversed)

The even pages must be reversed because when you flip the stack, page 2 should be on the back of page 1, page 4 on the back of page 3, and so on.

The stack

  • Vue 3 with Composition API
  • pdf-lib for PDF manipulation
  • Vite for bundling

The core implementation

<script setup lang="ts">
import { ref } from 'vue'
import { PDFDocument } from 'pdf-lib'

const file = ref<File | null>(null)
const bindingMode = ref<'long' | 'short'>('long')
const addBlankPage = ref(false)
const preparing = ref(false)
const result = ref<{ odd: Uint8Array; even: Uint8Array } | null>(null)

async function prepareDuplex() {
  if (!file.value) return
  preparing.value = true

  const arrayBuffer = await file.value.arrayBuffer()
  const pdf = await PDFDocument.load(arrayBuffer)
  const totalPages = pdf.getPageCount()

  // Add blank page if needed for even count
  let effectivePages = totalPages
  if (addBlankPage.value && totalPages % 2 === 1) {
    const blankPage = pdf.addPage([pdf.getPage(0).getWidth(), pdf.getPage(0).getHeight()])
    effectivePages = totalPages + 1
  }

  // Separate odd and even pages
  const oddIndices: number[] = []
  const evenIndices: number[] = []

  for (let i = 0; i < effectivePages; i++) {
    if (i % 2 === 0) {
      oddIndices.push(i) // 0, 2, 4... = pages 1, 3, 5...
    } else {
      evenIndices.push(i) // 1, 3, 5... = pages 2, 4, 6...
    }
  }

  // For long-edge binding, reverse even pages
  if (bindingMode.value === 'long') {
    evenIndices.reverse()
  }

  // Create odd PDF
  const oddPdf = await PDFDocument.create()
  const [oddPages] = await oddPdf.copyPages(pdf, oddIndices)
  oddPages.forEach(page => oddPdf.addPage(page))
  const oddBytes = await oddPdf.save()

  // Create even PDF
  const evenPdf = await PDFDocument.create()
  const [evenPages] = await evenPdf.copyPages(pdf, evenIndices)
  evenPages.forEach(page => evenPdf.addPage(page))
  const evenBytes = await evenPdf.save()

  // Package as ZIP
  result.value = { odd: oddBytes, even: evenBytes }
  preparing.value = false
}
</script>
Enter fullscreen mode Exit fullscreen mode

Key implementation details

1. Page index vs. page number

pdf-lib uses 0-based indexing internally, but users think in 1-based page numbers. The conversion:

  • Index 0 = Page 1 (odd)
  • Index 1 = Page 2 (even)
  • Index 2 = Page 3 (odd)
  • etc.
for (let i = 0; i < effectivePages; i++) {
  if (i % 2 === 0) {
    oddIndices.push(i)  // Pages 1, 3, 5...
  } else {
    evenIndices.push(i) // Pages 2, 4, 6...
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Reversing even pages

For long-edge binding, the even pages must be in reverse order:

// Original: [1, 3, 5, 7] → Pages 2, 4, 6, 8
// Reversed: [7, 5, 3, 1] → Pages 8, 6, 4, 2
evenIndices.reverse()
Enter fullscreen mode Exit fullscreen mode

This ensures that when you print odd pages, flip the stack, and print even pages, the binding is correct.

3. Blank page handling

If the total page count is odd, add a blank page at the end:

if (addBlankPage.value && totalPages % 2 === 1) {
  const blankPage = pdf.addPage([width, height])
  effectivePages = totalPages + 1
}
Enter fullscreen mode Exit fullscreen mode

4. ZIP packaging

Use JSZip to package both PDFs:

async function createZip(odd: Uint8Array, even: Uint8Array): Promise<Blob> {
  const zip = new JSZip()
  zip.file('odd-pages.pdf', odd)
  zip.file('even-pages.pdf', even)
  return await zip.generateAsync({ type: 'blob' })
}
Enter fullscreen mode Exit fullscreen mode

Summary

Building a browser-based duplex print helper involves:

  1. Loading the PDF and counting pages
  2. Separating pages into odd and even groups
  3. Reversing even pages for long-edge binding
  4. Optionally adding a blank page for odd counts
  5. Packaging as a ZIP with two PDFs

Try it at en.sotool.top/duplex-print-helper.

Top comments (0)