Combining images into PDFs is a common need, but handling multiple formats and maintaining quality requires careful implementation.
Here's how to build a browser-based image to PDF converter with Vue 3.
The challenge: Multiple formats, one output
Images come in various formats (JPG, PNG, WebP, HEIC), but PDF only supports specific image types. Converting between them requires:
- Format detection and normalization
- Quality preservation
- Layout management
- Memory optimization
The stack
- Vue 3 with Composition API
- pdf-lib for PDF generation
- Canvas API for image processing
- Vite for bundling
The core implementation
<script setup lang="ts">
import { ref } from 'vue'
import { PDFDocument } from 'pdf-lib'
const files = ref<File[]>([])
const converting = ref(false)
const result = ref<Blob | null>(null)
async function convertToPdf() {
if (files.value.length === 0) return
converting.value = true
const pdfDoc = await PDFDocument.create()
for (const file of files.value) {
const arrayBuffer = await file.arrayBuffer()
let imageBytes: Uint8Array
let imageType: 'jpg' | 'png'
if (file.type === 'image/jpeg') {
imageBytes = arrayBuffer
imageType = 'jpg'
} else if (file.type === 'image/png') {
imageBytes = arrayBuffer
imageType = 'png'
} else {
// Convert to JPEG using Canvas
imageBytes = await convertToJpeg(arrayBuffer, file.type)
imageType = 'jpg'
}
// Add page (A4 size)
const page = pdfDoc.addPage([612, 792])
// Embed and draw image
const img = imageType === 'jpg'
? await pdfDoc.embedJpg(imageBytes)
: await pdfDoc.embedPng(imageBytes)
page.drawImage(img, {
x: 0, y: 0,
width: page.getWidth(),
height: page.getHeight()
})
}
result.value = await pdfDoc.save()
converting.value = false
}
// Convert any image format to JPEG
async function convertToJpeg(arrayBuffer: ArrayBuffer, mimeType: string): Promise<Uint8Array> {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')!
return new Promise((resolve) => {
const img = new Image()
img.onload = () => {
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0)
canvas.toBlob((blob) => {
if (blob) {
const reader = new FileReader()
reader.onload = () => resolve(new Uint8Array(reader.result as ArrayBuffer))
reader.readAsArrayBuffer(blob)
}
}, 'image/jpeg', 0.95)
}
img.src = URL.createObjectURL(new Blob([arrayBuffer]))
})
}
</script>
Key implementation details
1. Format detection
Check file type and handle accordingly:
if (file.type === 'image/jpeg') {
// Direct embedding
} else if (file.type === 'image/png') {
// Direct embedding
} else {
// Convert via Canvas
}
2. Canvas conversion
For unsupported formats, use Canvas to convert:
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
canvas.toBlob((blob) => { /* convert to bytes */ }, 'image/jpeg', 0.95)
3. Image sizing
Handle different image ratios:
function fitImage(imgWidth: number, imgHeight: number, pageWidth: number, pageHeight: number) {
const scale = Math.min(pageWidth / imgWidth, pageHeight / imgHeight)
return {
width: imgWidth * scale,
height: imgHeight * scale
}
}
4. Memory management
Process images in batches to avoid memory issues:
const BATCH_SIZE = 20
for (let i = 0; i < files.value.length; i += BATCH_SIZE) {
const batch = files.value.slice(i, i + BATCH_SIZE)
await processBatch(batch, pdfDoc)
}
Limitations
HEIC support
HEIC images require additional decoding libraries.
Solution: Prompt users to convert HEIC to JPG first.
Large files
Very large images may cause memory issues.
Solution: Implement image resizing or chunked processing.
WebP compatibility
WebP support varies across PDF readers.
Solution: Convert WebP to JPG for maximum compatibility.
Summary
Building a browser-based image to PDF converter involves:
- Detecting image formats
- Normalizing to supported types
- Embedding in PDF pages
- Managing memory for large batches
Try it at en.sotool.top/image-to-pdf.
Top comments (0)