Compressing PDF files in the browser requires careful balancing of file size reduction and quality preservation. Here's how to build a browser-based PDF compression tool with Vue 3 and pdf-lib.
The challenge: Quality vs. Size
PDF compression involves trade-offs:
- Reducing image quality to shrink file size
- Removing unnecessary metadata
- Subsetting or removing fonts
- Optimizing vector graphics
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 quality = ref<'low' | 'medium' | 'high'>('medium')
const compressing = ref(false)
const result = ref<Uint8Array | null>(null)
const originalSize = ref(0)
const compressedSize = ref(0)
async function compressPdf() {
if (!file.value) return
compressing.value = true
const arrayBuffer = await file.value.arrayBuffer()
originalSize.value = arrayBuffer.byteLength
const pdf = await PDFDocument.load(arrayBuffer, {
ignoreEncryption: true,
})
// Remove unnecessary metadata
pdf.setTitle('')
pdf.setAuthor('')
pdf.setSubject('')
pdf.setKeywords([])
pdf.setProducer('')
pdf.setCreator('')
// Compress embedded images by re-encoding
const pages = pdf.getPages()
for (const page of pages) {
const width = page.getWidth()
const height = page.getHeight()
// Trigger image compression during save
page.setSize(width, height)
}
const options = {
useObjectStreams: true,
addDefaultPage: false,
}
result.value = await pdf.save(options)
compressedSize.value = result.value.byteLength
compressing.value = false
}
const compressionRatio = computed(() => {
if (!originalSize.value || !compressedSize.value) return 0
return Math.round((1 - compressedSize.value / originalSize.value) * 100)
})
</script>
Key implementation details
1. Metadata cleanup
Removing metadata reduces file size without affecting content:
pdf.setTitle('')
pdf.setAuthor('')
pdf.setSubject('')
pdf.setKeywords([])
pdf.setProducer('')
pdf.setCreator('')
2. Object streams
Enable object streams for better compression:
const options = { useObjectStreams: true }
const compressed = await pdf.save(options)
3. Quality settings
Adjust compression aggressiveness based on user preference:
async function compressWithQuality(arrayBuffer: ArrayBuffer, level: 'low' | 'medium' | 'high'): Promise<Uint8Array> {
const pdf = await PDFDocument.load(arrayBuffer, { ignoreEncryption: true })
// Remove metadata
pdf.setTitle('')
pdf.setAuthor('')
pdf.setSubject('')
pdf.setKeywords([])
pdf.setProducer('')
pdf.setCreator('')
const options = { useObjectStreams: level !== 'low' }
return await pdf.save(options)
}
4. Progress feedback
Show compression ratio in real-time:
const compressionRatio = computed(() => {
if (!originalSize.value || !compressedSize.value) return 0
return Math.round((1 - compressedSize.value / originalSize.value) * 100)
})
Limitations
No image re-encoding
pdf-lib doesn't re-encode embedded images for maximum compression.
Solution: For image-heavy PDFs, consider using a dedicated compression tool or desktop software.
Font subsetting
pdf-lib doesn't subset fonts automatically.
Solution: This is handled at the browser level by the PDF viewer's rendering engine.
Large files
Very large PDFs may cause memory issues.
Solution: Process in smaller batches or use Web Workers.
Summary
Building a browser-based PDF compression tool involves:
- Loading the PDF with pdf-lib
- Removing unnecessary metadata
- Enabling object streams for better compression
- Showing compression ratio to the user
Try it at en.sotool.top/compress.
Top comments (0)