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>
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...
}
}
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()
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
}
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' })
}
Summary
Building a browser-based duplex print helper involves:
- Loading the PDF and counting pages
- Separating pages into odd and even groups
- Reversing even pages for long-edge binding
- Optionally adding a blank page for odd counts
- Packaging as a ZIP with two PDFs
Try it at en.sotool.top/duplex-print-helper.
Top comments (0)