DEV Community

sunshey
sunshey

Posted on

How to Fix Duplex Scan Page Order in the Browser with Vue 3 and pdf-lib

Scanning documents double-sided is convenient, but the resulting PDF often has scrambled page order. The front pages are in sequence (1, 2, 3...), but the back pages are reversed (N, N-1, N-2...). Fixing this manually is tedious.

Here's how to build a browser-based duplex scan reorder tool with Vue 3 and pdf-lib.

The duplex scanning problem

Most scanners work in two passes for double-sided documents:

  1. Front pass: Pages 1, 2, 3... N (normal order)
  2. Back pass: Pages N, N-1, N-2... 1 (reversed order)

The resulting PDF combines both: 1, 2, 3... N, N, N-1, N-2... 1

We need to detect the segment boundary, reverse the back segment, and merge correctly.

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 scanMode = ref<'single' | 'two'>('single')
const processing = ref(false)
const result = ref<Uint8Array | null>(null)

async function reorderDuplexScan() {
  if (!file.value) return
  processing.value = true

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

  // Detect segment boundary by rotation or heuristics
  const frontPages: number[] = []
  const backPages: number[] = []

  let inFront = true
  for (let i = 0; i < totalPages; i++) {
    const page = pdf.getPage(i)
    const rotation = page.getRotation().angle

    // Back pages are often rotated 180°
    if (rotation === 180 && inFront) {
      inFront = false
    }

    if (inFront) {
      frontPages.push(i)
    } else {
      backPages.push(i)
    }
  }

  // Reverse the back pages
  backPages.reverse()

  // Merge: front + reversed back
  const indices = [...frontPages, ...backPages]

  const newPdf = await PDFDocument.create()
  const [pages] = await newPdf.copyPages(pdf, indices)
  pages.forEach(p => newPdf.addPage(p))

  result.value = await newPdf.save()
  processing.value = false
}
</script>
Enter fullscreen mode Exit fullscreen mode

Key implementation details

1. Segment detection

Detecting where the front segment ends and the back segment begins is crucial. Common approaches:

  • Rotation detection: Back pages are often rotated 180°
  • Content analysis: Detect page content changes
  • Manual segmentation: Let the user specify the boundary
// Heuristic: Find the first page with 180° rotation
function findSegmentBoundary(pdf: PDFDocument): number {
  for (let i = 0; i < pdf.getPageCount(); i++) {
    const rotation = pdf.getPage(i).getRotation().angle
    if (rotation === 180) return i
  }
  // Fallback: split in half
  return Math.floor(pdf.getPageCount() / 2)
}
Enter fullscreen mode Exit fullscreen mode

2. Two-file merge mode

When users have separate front and back PDFs:

async function mergeTwoFiles(
  frontPdf: Uint8Array,
  backPdf: Uint8Array
): Promise<Uint8Array> {
  const front = await PDFDocument.load(frontPdf)
  const back = await PDFDocument.load(backPdf)

  const result = await PDFDocument.create()

  const frontCount = front.getPageCount()
  const backCount = back.getPageCount()
  const maxCount = Math.max(frontCount, backCount)

  // Interleave pages
  for (let i = 0; i < maxCount; i++) {
    if (i < frontCount) {
      const [page] = await result.copyPages(front, [i])
      result.addPage(page)
    }
    if (i < backCount) {
      const [page] = await result.copyPages(back, [i])
      result.addPage(page)
    }
  }

  return await result.save()
}
Enter fullscreen mode Exit fullscreen mode

3. Handling edge cases

  • Odd page counts: If front has more pages than back, handle gracefully
  • Missing pages: Some pages might be blank or damaged
  • Mixed orientations: Pages might have different sizes
// Handle mismatched counts
const safeCount = Math.min(frontCount, backCount)
const extraFront = frontCount - safeCount
const extraBack = backCount - safeCount

// Add extra pages from the longer segment
for (let i = safeCount; i < frontCount; i++) {
  const [page] = await result.copyPages(front, [i])
  result.addPage(page)
}
Enter fullscreen mode Exit fullscreen mode

Summary

Building a duplex scan reorder tool involves:

  1. Loading the scanned PDF
  2. Detecting segment boundaries (front vs back)
  3. Reversing the back segment
  4. Merging segments into correct order
  5. Handling edge cases (odd counts, missing pages)

Try it at en.sotool.top/duplex-scan-reorder.

Top comments (0)