Adding page numbers to PDFs in the browser involves positioning text correctly and handling different page layouts. Here's how to build a browser-based PDF page numbering tool with Vue 3 and pdf-lib.
The challenge: Positioning and formatting
Page number implementation requires:
- Calculating correct X/Y coordinates for different positions
- Supporting various formats (1, 2, 3 or 1 of 10, Page 1, etc.)
- Handling different page sizes (A4, Letter, etc.)
- Applying consistent styling across all pages
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, rgb, StandardFonts } from 'pdf-lib'
type Position = 'top-left' | 'top-center' | 'top-right' |
'bottom-left' | 'bottom-center' | 'bottom-right'
const file = ref<File | null>(null)
const position = ref<Position>('bottom-center')
const format = ref<'simple' | 'of-total' | 'page-prefix'>('simple')
const fontSize = ref(12)
const startingNumber = ref(1)
const numbering = ref(false)
const result = ref<Uint8Array | null>(null)
async function addPageNumbers() {
if (!file.value) return
numbering.value = true
const arrayBuffer = await file.value.arrayBuffer()
const pdf = await PDFDocument.load(arrayBuffer)
const pages = pdf.getPages()
const font = await pdf.embedFont(StandardFonts.Helvetica)
const totalPages = pages.length
const margin = 50
for (let i = 0; i < totalPages; i++) {
const page = pages[i]
const { width, height } = page.getSize()
const pageNum = i + startingNumber.value
let text = format.value === 'simple'
? String(pageNum)
: format.value === 'of-total'
? `${pageNum} / ${totalPages}`
: `Page ${pageNum}`
let x: number
let y: number
switch (position.value) {
case 'top-left':
x = margin
y = height - margin - fontSize.value
break
case 'top-center':
x = (width - 50) / 2
y = height - margin - fontSize.value
break
case 'top-right':
x = width - margin - 50
y = height - margin - fontSize.value
break
case 'bottom-left':
x = margin
y = margin
break
case 'bottom-center':
x = (width - 50) / 2
y = margin
break
case 'bottom-right':
x = width - margin - 50
y = margin
break
}
page.drawText(text, {
x,
y,
size: fontSize.value,
font,
color: rgb(0, 0, 0),
})
}
result.value = await pdf.save()
numbering.value = false
}
</script>
Key implementation details
1. Position calculation
Calculate coordinates based on page dimensions and margins:
const margin = 50
switch (position) {
case 'bottom-center':
x = (width - textWidth) / 2
y = margin
break
// ... other positions
}
2. Format options
Support different numbering formats:
const text = format === 'simple'
? String(pageNum)
: format === 'of-total'
? `${pageNum} / ${totalPages}`
: `Page ${pageNum}`
3. Starting number
Allow customization of the starting page number:
const pageNum = i + startingNumber.value
4. Font embedding
Always embed fonts for consistent rendering:
const font = await pdf.embedFont(StandardFonts.Helvetica)
Limitations
No preview without rendering
pdf-lib doesn't provide real-time preview of page numbers.
Solution: Show a static preview or render thumbnails separately.
Limited formatting options
Basic text positioning only; no headers/footers with images.
Solution: Use desktop software for complex formatting.
Page size variations
Different page sizes require position adjustments.
Solution: Calculate positions dynamically based on page dimensions.
Summary
Building a browser-based PDF page numbering tool involves:
- Loading the PDF with pdf-lib
- Calculating position coordinates for each page
- Drawing page numbers with the correct format
- Saving and downloading the numbered PDF
Try it at en.sotool.top/page-numbers.