Skip to main content

Canvas to PDF — Export HTML5 Canvas as PDF with JavaScript

Export any HTML5 Canvas content to a PDF document using JavaScript. This demo shows how to convert a Konva stage into a downloadable PDF file using jsPDF, with support for high-quality rendering and selectable text.

The approach works in four steps: generate your canvas content, export the canvas as an image, insert that image into a PDF document, and save. Two tips to get the best results:

High-quality export: Use the pixelRatio attribute when converting canvas to an image — see the High Quality Export guide for details.

Selectable text in PDF: Even though the canvas is added as an image, you can insert text nodes manually into the PDF layer beneath it. The text won't be visible (it sits behind the image) but remains selectable and searchable. Text rendering in PDF differs from Konva's, so complex styles may need adjustment.

Instructions: view the canvas below, then click the button to save it as a PDF.

import Konva from 'konva';

// Create a button for PDF export
const saveButton = document.createElement('button');
saveButton.textContent = 'Save as PDF';
saveButton.style.position = 'absolute';
saveButton.style.top = '5px';
saveButton.style.left = '5px';
document.body.appendChild(saveButton);

// Create a stage
const width = window.innerWidth;
const height = window.innerHeight;

const stage = new Konva.Stage({
  container: 'container',
  width: width,
  height: height,
});

const layer = new Konva.Layer();
stage.add(layer);

// Add background
const back = new Konva.Rect({
  width: stage.width(),
  height: stage.height(),
  fill: 'rgba(200, 200, 200)',
});
layer.add(back);

// Add text with blur effect
const text = new Konva.Text({
  text: 'This is the Darth Vader',
  x: 15,
  y: 40,
  rotation: -10,
  filters: [Konva.Filters.Blur],
  blurRadius: 4,
  fontSize: 18,
});
text.cache();
layer.add(text);

// Add arrow
const arrow = new Konva.Arrow({
  points: [70, 50, 100, 80, 150, 100, 190, 100],
  tension: 0.5,
  stroke: 'black',
  fill: 'black',
});
layer.add(arrow);

// Add image
const imageUrl = 'https://konvajs.org/assets/darth-vader.jpg';
Konva.Image.fromURL(
  imageUrl,
  function (darthNode) {
    darthNode.setAttrs({
      x: 200,
      y: 50,
      scaleX: 0.5,
      scaleY: 0.5,
    });
    layer.add(darthNode);
  },
  function () {
    console.error('Failed to load image');
  }
);

// Handle PDF export
saveButton.addEventListener('click', function () {
  // We need to check if jsPDF is loaded
  if (typeof jsPDF !== 'undefined') {
    const pdf = new jsPDF('l', 'px', [stage.width(), stage.height()]);
    pdf.setTextColor('#000000');
    
    // First add texts
    stage.find('Text').forEach((text) => {
      const size = text.fontSize() / 0.75; // convert pixels to points
      pdf.setFontSize(size);
      pdf.text(text.text(), text.x(), text.y(), {
        baseline: 'top',
        angle: -text.getAbsoluteRotation(),
      });
    });

    // Then put image on top of texts (so texts are not visible)
    pdf.addImage(
      stage.toDataURL({ pixelRatio: 2 }),
      0,
      0,
      stage.width(),
      stage.height()
    );

    pdf.save('canvas.pdf');
  } else {
    console.error('jsPDF library is not loaded. Please include it in your project.');
    alert('jsPDF library is not loaded. In a real project, you need to include it.');
  }
});

// Add jsPDF library dynamically for demo purposes
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.debug.js';
script.integrity = 'sha384-NaWTHo/8YCBYJ59830LTz/P4aQZK1sS0SneOgAvhsIl3zBu8r9RevNg5lHCHAuQ/';
script.crossOrigin = 'anonymous';
document.head.appendChild(script);