'Draw rectangle in canvas with loaded pdf file using pdf.js

I am trying to draw rectangle over a pdf file. When I draw rectangle in pdf, rectangle not draw properly.

I want to draw only one rectangle at a time, when I draw new rectangle the old rectangle should be remove, but it is not happening.

Here is my code:

Rendering code of pdf (Rendering is working properly)

function pdfFile (file) {
pdfjsLib.workerSrc = 'pdf.worker.js';
pdfjsLib.getDocument(file).promise.then(function(pdfDoc) {
  pdf = pdfDoc;
  document.getElementById('page_count').textContent = pdfDoc.numPages;
  showButtonGroup(pdf);
  renderPage(pageNum);
});
} 

 function renderPage(num) {
    pageRendering = true;
    pdf.getPage(num).then(function(page) {
      var viewport = page.getViewport({scale: scale});
      canvas.height = viewport.height;
      canvas.width = viewport.width;
      var renderContext = {
        canvasContext: ctx,
        viewport: viewport
      };
      var renderTask = page.render(renderContext);
      renderTask.promise.then(function() {
        pageRendering = false;
        if (pageNumPending !== null) {
          renderPage(pageNumPending);
          pageNumPending = null;
        }
      });
    });

    document.getElementById('page_num').textContent = num;
}

Mouse move function not working properly

function mouseMove(e) {
    if (drag) {
        ctx.putImageData(pdfPages[pageNum], 0, 0);
        ctx.clearRect(rect.startX, rect.startY, rect.w, rect.h);
        rect.w = ((e.pageX - x) - this.offsetLeft) - rect.startX;
        rect.h = ((e.pageY - y) - this.offsetTop) - rect.startY;      
        ctx.strokeStyle = color;
        ctx.strokeRect(rect.startX, rect.startY, rect.w, rect.h);
        Object.assign(data, {
            x: rect.startX,
            y: rect.startY,
            w: rect.w,
            h: rect.h
        })
        console.log(data);
    }
}

Note

  1. When I enable clearRect and putImageData function then rectangle draw properly but canvas pdf shows empty. Here is the attached image

    enter image description here

  2. When only enabled clearRect function then showing multiple rectangle in pdf. Here is the attached image

    enter image description here



Solution 1:[1]

Please check the below, need to clear the existing rectangle before creating new

function renderPage(num) {
    pageRendering = true;
    pdf.getPage(num).then(function (page) {
        var viewport = page.getViewport({ scale: scale });
        canvas.height = viewport.height;
        canvas.width = viewport.width;
        var renderContext = {
            canvasContext: ctx,
            viewport: viewport
        };
        var renderTask = page.render(renderContext);
        renderTask.promise.then(function () {
            pageRendering = false;

            //You need to clear the existing rectangle
            pdfPages[num] = ctx.getImageData(0, 0, canvas.width, canvas.height);

            //Now you can draw new rectangle
            drawRectangle(10, 10, 100, 50);
            if (pageNumPending !== null) {
                renderPage(pageNumPending);
                pageNumPending = null;
            }
        });
    });

    function drawRectangle(x, y, w, h) {
        ctx.strokeStyle = color;
        ctx.strokeRect(x, y, w, h);
    }

Solution 2:[2]

The other way to do selectable rectangles on canvas in react using pdf.js is by keeping original canvas saved using toDataURL() and then after clearRect use canvas.draw() to draw your canvas with the base64 image saved earlier.

import React, { useEffect, useState, useRef, useCallback } from "react";
import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
import * as pdfjsLib from "pdfjs-dist/build/pdf";

export default function CustomPdfReader({ url }) {
  pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;

  const canvasRef = useRef();
  const [pdfRef, setPdfRef] = useState("");
  const currentPage = 1;
  const zoomScale = 1;
  const rotateAngle = 0;
  var pdf_image = "";
  const renderPage = useCallback(
    (pageNum, pdf = pdfRef) => {
      pdf &&
        pdf.getPage(pageNum).then(function (page) {
          const viewport = page.getViewport({ scale: zoomScale, rotation: rotateAngle });
          const canvas = canvasRef.current;
          canvas.height = viewport?.height;
          canvas.width = viewport?.width;
          const renderContext = {
            canvasContext: canvas?.getContext("2d"),
            viewport: viewport,
            textContent: pdfRef,
          };

          page.render(renderContext);
        });
    },
    [pdfRef, url]
  );
  useEffect(() => {
    if (url.slice(-4).toLowerCase() === ".pdf") {
      renderPage(currentPage, pdfRef);
    } else {
      setPdfRef("");
    }
  }, [pdfRef, renderPage, url]);

  useEffect(() => {
    const loadingTask = pdfjsLib.getDocument(url);
    loadingTask.promise.then(
      (loadedPdf) => {
        setPdfRef(loadedPdf);
      },
      function (reason) {
        console.error(reason);
      }
    );
  }, [url]);

  var cursorInCanvas = false;
  var canvasOfDoc = canvasRef?.current;
  var ctx;
  var startX;
  var startY;
  var offsetX;
  var offsetY;

  const saveInitialCanvas = () => {
    if (canvasOfDoc?.getContext) {
      var canvasPic = new Image();
      canvasPic.src = canvasOfDoc.toDataURL();
      pdf_image = canvasPic;
    }
  };

  useEffect(() => {
    if (canvasOfDoc) {
      ctx = canvasOfDoc.getContext("2d");
      var canvasOffset = canvasOfDoc.getBoundingClientRect();
      offsetX = canvasOffset.left;
      offsetY = canvasOffset.top;
    }
  }, [canvasOfDoc, pdfRef,  renderPage, url]);

  function handleMouseIn(e) {
    if (typeof pdf_image == "string") {
      saveInitialCanvas();
    }
    e.preventDefault();
    e.stopPropagation();
    startX = ((e.offsetX * canvasOfDoc.width) / canvasOfDoc.clientWidth) | 0;
    startY = ((e.offsetY * canvasOfDoc.width) / canvasOfDoc.clientWidth) | 0;

    cursorInCanvas = true;
  }

  function handleMouseOut(e) {
    e.preventDefault();
    e.stopPropagation();
    cursorInCanvas = false;
  }

  function handleMouseMove(e) {
    e.preventDefault();
    e.stopPropagation();
    if (!cursorInCanvas) {
      return;
    }
    let mouseX = ((e.offsetX * canvasOfDoc.width) / canvasOfDoc.clientWidth) | 0;
    let mouseY = ((e.offsetY * canvasOfDoc.width) / canvasOfDoc.clientWidth) | 0;

    var width = mouseX - startX;
    var height = mouseY - startY;
    if (ctx) {
      ctx?.clearRect(0, 0, canvasOfDoc.width, canvasOfDoc.height);
      ctx?.drawImage(pdf_image, 0, 0);
      ctx.beginPath();
      ctx.rect(startX, startY, width, height);
      ctx.strokeStyle = "#1B9AFF";
      ctx.lineWidth = 1;
      ctx.stroke();
    }
  }

  canvasOfDoc?.addEventListener("mousedown", function (e) {
    handleMouseIn(e);
  });
  canvasOfDoc?.addEventListener("mousemove", function (e) {
    handleMouseMove(e);
  });
  canvasOfDoc?.addEventListener("mouseup", function (e) {
    handleMouseOut(e);
  });
  canvasOfDoc?.addEventListener("mouseout", function (e) {
    handleMouseOut(e);
  });

  return (
    <>
      <canvas id="pdf-doc" ref={canvasRef} />
    </>
  );
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2