DEV Community

Cover image for Image to PDF in the Browser - No Libraries, No Backend
TechMind
TechMind

Posted on

Image to PDF in the Browser - No Libraries, No Backend

As Steve Jobs said, "Design is not just what it looks like and feels like. Design is how it works."

Converting an image to PDF should work like this: open tool → upload image → get PDF. That is it. No backend calls, no third-party APIs, no file size limits from a server.

Here is how it actually works in the browser — and why TechMind.click built it this way.

The Core Approach - Canvas + jsPDF

The cleanest client-side image-to-PDF conversion uses the HTML5 Canvas API and jsPDF:

import { jsPDF } from "jspdf";

async function imageToPDF(imageFile) {
  return new Promise((resolve) => {
    const reader = new FileReader();

    reader.onload = (e) => {
      const img = new Image();
      img.onload = () => {
        // A4 dimensions in mm
        const pdf = new jsPDF({
          orientation: img.width > img.height ? "landscape" : "portrait",
          unit: "mm",
          format: "a4"
        });

        const pageWidth = pdf.internal.pageSize.getWidth();
        const pageHeight = pdf.internal.pageSize.getHeight();

        // Calculate scaled dimensions preserving aspect ratio
        const ratio = Math.min(
          pageWidth / img.width,
          pageHeight / img.height
        );

        const imgWidth = img.width * ratio;
        const imgHeight = img.height * ratio;

        // Center on page
        const x = (pageWidth - imgWidth) / 2;
        const y = (pageHeight - imgHeight) / 2;

        pdf.addImage(
          e.target.result,
          "JPEG",
          x, y,
          imgWidth, imgHeight
        );

        resolve(pdf.output("blob"));
      };
      img.src = e.target.result;
    };

    reader.readAsDataURL(imageFile);
  });
}
Enter fullscreen mode Exit fullscreen mode

Handling Multiple Images

async function multipleImagesToPDF(imageFiles) {
  const pdf = new jsPDF({ unit: "mm", format: "a4" });
  const pageWidth = pdf.internal.pageSize.getWidth();
  const pageHeight = pdf.internal.pageSize.getHeight();

  for (let i = 0; i < imageFiles.length; i++) {
    if (i > 0) pdf.addPage();

    const dataUrl = await fileToDataURL(imageFiles[i]);
    const dimensions = await getImageDimensions(dataUrl);

    const ratio = Math.min(
      pageWidth / dimensions.width,
      pageHeight / dimensions.height
    );

    pdf.addImage(
      dataUrl, "JPEG",
      (pageWidth - dimensions.width * ratio) / 2,
      (pageHeight - dimensions.height * ratio) / 2,
      dimensions.width * ratio,
      dimensions.height * ratio
    );
  }

  return pdf.output("blob");
}

const fileToDataURL = (file) => new Promise((res) => {
  const reader = new FileReader();
  reader.onload = (e) => res(e.target.result);
  reader.readAsDataURL(file);
});

const getImageDimensions = (src) => new Promise((res) => {
  const img = new Image();
  img.onload = () => res({ width: img.width, height: img.height });
  img.src = src;
});
Enter fullscreen mode Exit fullscreen mode

Why Client-Side Matters

As Alan Turing would appreciate — the most elegant solution solves the problem with the least complexity. Client-side conversion means:

Zero server costs — no file storage, no bandwidth for uploads
Privacy — user files never leave their device
Speed — no round-trip to a server
Offline capable — works without internet after initial page load

Quick Manual Fix

For non-developers or one-off conversions, TechMind.click has this built in — upload image, download PDF, done. Uses the same browser-based approach — no server, no storage.

What is your preferred client-side PDF library? jsPDF vs pdf-lib — drop it in the comments.

Top comments (0)