Resizing a PDF — changing page dimensions from Letter to A4, or scaling content to fit a target size — sounds like something only desktop software handles. But with pdf-lib, canvas, and some coordinate math, you can build a fully browser-based resize tool.
This post walks through the implementation details, including page dimension conversion, content scaling, orientation detection, and multi-page support.
Why client-side?
Traditional PDF tools upload your file, process it on a server, and send the result back. For documents that might be personal or contain sensitive information, this introduces an unnecessary privacy risk. A browser-based approach:
- Processes everything locally
- Keeps files on the user's device
- Works offline after loading
- Avoids server-side bandwidth costs
The stack
- Vue 3 + Composition API
- pdf-lib for PDF manipulation and resizing
-
PDF.js (
pdfjs-dist) for preview rendering - Vite for bundling
Adding page resize logic
The core operation is: read each page's original dimensions, create a new page with the target size, draw the original content centered and scaled onto the new page.
<script setup lang="ts">
import { ref } from 'vue'
import { PDFDocument, StandardFonts, PdfRange } from 'pdf-lib'
const file = ref<File | null>(null)
const targetSize = ref<'a4' | 'letter' | 'legal' | 'original'>('a4')
const orientation = ref<'auto' | 'portrait' | 'landscape'>('auto')
const marginMm = ref(5)
const applying = ref(false)
// Page size constants in points (1 point = 1/72 inch)
const PAGE_SIZES: Record<string, { width: number; height: number }> = {
a4: { width: 595.28, height: 841.89 },
letter: { width: 612.00, height: 792.00 },
legal: { width: 612.00, height: 1008.00 },
}
</script>
Scaling and centering content
This is where the math happens. Each page needs to be scaled to fit within the target dimensions while maintaining aspect ratio, then centered.
async function handleResize() {
if (!file.value) return
applying.value = true
const arrayBuffer = await file.value.arrayBuffer()
const srcDoc = await PDFDocument.load(arrayBuffer)
const embedFont = await srcDoc.embedFont(StandardFonts.Helvetica)
const newDoc = await srcDoc.copy(PdfRange.All)
const marginPts = marginMm.value * (72 / 25.4)
const pages = newDoc.getPages()
for (let i = 0; i < pages.length; i++) {
const page = pages[i]
const { width: origW, height: origH } = page.getSize()
// Determine target dimensions
let { width: tw, height: th } =
targetSize.value === 'original'
? PAGE_SIZES[origW > origH ? 'letter' : 'a4']
: PAGE_SIZES[targetSize.value]
// Handle orientation
if (orientation.value === 'portrait' && tw > th) {
;[tw, th] = [th, tw]
} else if (orientation.value === 'landscape' && tw < th) {
;[tw, th] = [th, tw]
} else if (orientation.value === 'auto') {
// Keep existing orientation
if (origW < origH && tw > th) {
;[tw, th] = [th, tw]
}
}
// Scale factor: fit content within available area (minus margins)
const availW = tw - 2 * marginPts
const availH = th - 2 * marginPts
const scale = Math.min(availW / origW, availH / origH, 1)
// New content dimensions after scaling
const cw = origW * scale
const ch = origH * scale
// Center position
const cx = (tw - cw) / 2
const cy = (th - ch) / 2
page.setSize(tw, th)
page.drawImage(embedFont, { x: 0, y: 0, width: 1, height: 1 })
// Draw original page content scaled and positioned
page.drawImage(newDoc.pages[i], {
x: cx,
y: cy,
width: cw,
height: ch,
opacity: 1,
})
}
const result = await newDoc.save()
downloadResult(result)
applying.value = false
}
function downloadResult(data: Uint8Array) {
const blob = new Blob([data], { type: 'application/pdf' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'resized.pdf'
a.click()
URL.revokeObjectURL(url)
}
</script>
Key challenges and solutions
Problem 1: Copying pages between PDFDocument instances
You can't directly use page.drawImage(srcPage) across different PDFDocument instances. The solution is to use copy() from pdf-lib:
const newDoc = await srcDoc.copy(PdfRange.All)
Then access pages from newDoc for drawing while using srcDoc as the source.
Problem 2: Preserving page rotation flags
Some PDFs use the /Rotate metadata field instead of swapping width/height. Before processing, normalize rotations:
for (const pg of pages) {
const rotate = pg.getRotation().degrees
if (rotate === 90 || rotate === 270) {
pg.setRotated(true)
// Swap dimensions internally
const { width, height } = pg.getSize()
pg.setSize(height, width)
}
}
Problem 3: Content getting clipped with large margins
If margins are too wide relative to the source page, the scaled content may become unreadable. Set a minimum content ratio and warn users:
const minContentRatio = 0.15
if (scale < minContentRatio) {
alert(`Page ${i + 1}: margins are too large for the chosen paper size.`)
}
Preview with PDF.js
Before finalizing the resize, show users a preview of what the resized page looks like:
<script setup lang="ts">
import { ref, watch } from 'vue'
import { PDFDocument } from 'pdf-lib'
import { pdfToCanvases } from '@/utils/pdfPreview'
const canvases = ref<HTMLCanvasElement[]>([])
const previewPage = ref(0)
watch(file, async () => {
if (file.value) {
canvases.value = await pdfToCanvases(file.value, 1)
}
})
</script>
<template>
<div class="preview" v-if="canvases.length">
<canvas :ref="setPreviewRef" />
</div>
</template>
What to watch out for
- Complex vector content may render slightly differently when scaled (font hinting, thin lines)
- Interactive elements (form fields, links) won't survive the resize operation — they're part of the original page, not the new one
- Very large source pages (architectural drawings) may produce large output files since every pixel gets resampled
- Scanned images inside PDFs — resizing a PDF containing scanned photos will upsample if the target is larger. Downscaling a letter-size page to A4 is fine; enlarging a small scanned receipt to Letter will make it blurry
Summary
Building a browser-based PDF resize tool with pdf-lib involves:
- Loading the PDF with
PDFDocument.load() - Creating a copy with
copy(PdfRange.All) - Iterating pages, computing scale factors, and redrawing content
- Handling edge cases: rotation, margins, and extreme aspect ratios
- Saving and downloading the result via
Blob+<a>element
Try it at en.sotool.top/resize-pdf. It takes about 30 seconds and keeps your files completely private.
Top comments (0)