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:
- Front pass: Pages 1, 2, 3... N (normal order)
- 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>
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)
}
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()
}
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)
}
Summary
Building a duplex scan reorder tool involves:
- Loading the scanned PDF
- Detecting segment boundaries (front vs back)
- Reversing the back segment
- Merging segments into correct order
- Handling edge cases (odd counts, missing pages)
Try it at en.sotool.top/duplex-scan-reorder.
Top comments (0)