DEV Community

sunshey
sunshey

Posted on

How to Resize PDF Pages in the Browser with Vue 3 and pdf-lib

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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.`)
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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:

  1. Loading the PDF with PDFDocument.load()
  2. Creating a copy with copy(PdfRange.All)
  3. Iterating pages, computing scale factors, and redrawing content
  4. Handling edge cases: rotation, margins, and extreme aspect ratios
  5. 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)