DEV Community

sunshey
sunshey

Posted on

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

Rotating a PDF page — turning content 90°, 180°, or 270° — sounds like a simple toggle. But properly rewriting a PDF page's content stream, adjusting its crop box, and updating metadata is surprisingly involved. With pdf-lib and a bit of canvas math, you can build a fully browser-based rotation tool that works correctly in every PDF viewer.

This post walks through the implementation details, including coordinate system transformation, content stream rewriting, and multi-page support.

Why client-side?

Traditional rotation tools upload your file, process it on a server, and send the result back. For documents that might 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 rotation
  • PDF.js (pdfjs-dist) for preview rendering (optional)
  • Vite for bundling

The rotation logic

pdf-lib provides a page.setRotation(degrees) method, but this only updates the rotation metadata flag. It doesn't rewrite the actual content stream, which can cause visual glitches in some viewers. For correct rotation, we need to:

  1. Apply an affine transformation matrix to the page's content stream
  2. Adjust the CropBox dimensions if necessary (90°/270° rotations swap width/height)
  3. Update the rotation metadata
  4. Optionally preview using PDF.js to show users the result before saving

Here's the core rotation function:

<script setup lang="ts">
import { ref } from 'vue'
import { PDFDocument, degreesToRadians, PageRotation } from 'pdf-lib'

const file = ref<File | null>(null)
const rotation = ref<PageRotation>(0) // 0, 90, 180, 270
const rotating = ref(false)

async function rotatePages() {
  if (!file.value) return
  rotating.value = true

  const arrayBuffer = await file.value.arrayBuffer()
  const pdf = await PDFDocument.load(arrayBuffer)

  const pages = pdf.getPages()
  for (const page of pages) {
    // Apply rotation transformation
    page.setRotation(Number(rotation.value))

    // CropBox may need adjustment for 90°/270° rotations
    const { width, height } = page.getSize()
    if (rotation.value === 90 || rotation.value === 270) {
      // Swap width and height for sideways pages
      page.setSize(height, width)
    }
  }

  const rotatedPdf = await pdf.save()
  downloadFile(rotatedPdf, 'rotated.pdf')
  rotating.value = false
}
</script>
Enter fullscreen mode Exit fullscreen mode

Key implementation details

1. Affine transformations

When you rotate a PDF page, the content coordinates (where text and images are drawn) need to transform as well. pdf-lib handles this automatically via setRotation(), which internally:

  • Updates the /Rotate entry in the page dictionary
  • Applies an affine transformation matrix to the content stream operators
  • Adjusts the CropBox dimensions for 90°/270° rotations

2. Handling custom angles (non-multiples of 90°)

pdf-lib only supports standard rotations (0, 90, 180, 270). For custom angles, you need to:

  1. Render the page to a canvas (using PDF.js) at the desired angle
  2. Extract the canvas content as an image
  3. Embed the image back into a new PDF page at the rotated dimensions

This is the "lossy" path — text and vector elements become raster images. Use it only when custom angles are absolutely required.

3. Preserving rotation state during previews

If you want to show users a preview before committing, use PDF.js to render the page with the temporary rotation applied. Don't save yet — the user may cancel. PDF.js respects the /Rotate metadata, so just pass the desired rotation to its viewport:

const viewport = page.getViewport({
  scale: 1.5,
  rotation: Number(rotation.value),
})
Enter fullscreen mode Exit fullscreen mode

4. Batch rotation across multiple pages

Rotate all pages uniformly or select specific pages. For selective rotation, store which pages should be rotated and iterate over them only:

const pagesToRotate = Array.from({ length: pageCount }, (_, i) =>
  selectedPages.has(i + 1) ? i : null
).filter(v => v !== null)

for (const pageIndex of pagesToRotate) {
  const page = pages[pageIndex]
  page.setRotation(Number(rotation.value))
  if (rotation.value === 90 || rotation.value === 270) {
    const { width, height } = page.getSize()
    page.setSize(height, width)
  }
}
Enter fullscreen mode Exit fullscreen mode

What to watch out for

  • Form fields: Rotating a page that contains interactive form fields can misalign the field coordinates. Fields won't "rotate" with the page — they stay fixed. This is expected behavior but users should be aware.
  • Page order: PDF page order in the document's Page tree is unaffected by rotation. Rotation only changes how a single page is displayed.
  • Multiple rotation flags: Some PDFs already have a /Rotate entry. Calling setRotation() replaces it, which is the correct behavior — you want the final rotation to be the one the user requested, not compounded.
  • Very large pages: Architectural drawings or large-format posters may exceed canvas memory limits when rendered for preview. Use smaller preview scales or skip preview for very large pages.

Summary

Building a browser-based PDF rotation tool with pdf-lib involves:

  1. Loading the PDF with PDFDocument.load()
  2. Iterating pages and calling setRotation(degrees)
  3. Adjusting CropBox dimensions for 90°/270° rotations
  4. Saving with pdf.save()
  5. Downloading the result via Blob + <a> element

Try it at en.sotool.top/rotate. It takes about 30 seconds and keeps your files completely private.

Top comments (0)