Home > front end >  How to create a draggable element on top of a PDF using PDF.js and jQuery
How to create a draggable element on top of a PDF using PDF.js and jQuery

Time:09-22

I am creating a service to stamp a pdf with an image using PDF.js and jQuery. I managed to create a draggable object using PDF.js but the the object leaves a trail of past objects when it is dragged.

I used context.clearRect(0, 0, canvas.width, canvas.height); to clear the past objects but it also clears the underlying PDF in the canvas.

How can I drag this object without affecting the underlying PDF?

Here is what I have done so far.

I am loading the PDF to the canvas using following code.

function loadPdfPreview(base64pdf){

        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.0.943/pdf.worker.min.js';

        var loadingTask = pdfjsLib.getDocument({data: base64pdf});

        loadingTask.promise.then(function (pdf) {

            // Fetch the first page
            pdf.getPage(1).then(function (page) {

                var scale = 1.0;

                var viewport = page.getViewport(scale);

                // Prepare canvas using PDF page dimensions
                canvas = document.getElementById('pdf-canvas');
                context = canvas.getContext('2d');

                canvas.height = viewport.height;
                canvas.width = viewport.width;

                canvasOffset = $("#pdf-canvas").offset();
                offsetX = canvasOffset.left;
                offsetY = canvasOffset.top;


                // Render PDF page into canvas context
                var renderContext = {
                    canvasContext: context,
                    viewport: viewport
                };

                page.render(renderContext).then(function () {

                    // creating the dummy object on success
                    drawObjectFromBlueprint(blueprint);

                }, function (e) {
                    console.log(e);
                });

            });
        });
    }

I use following function to draw the object on top of the canvas after the pdf is loaded to the canvas.

function drawObjectFromBlueprint(blueprint) {
        // drawing a draggable element inside the canvas
        context.strokeStyle = "lightgray";

        // Clearing the previous dummy objects
        context.clearRect(0, 0, canvas.width, canvas.height);

        // drawing the dummy object
        context.beginPath();
        context.moveTo(blueprint.x, blueprint.y);
        context.lineTo(blueprint.right, blueprint.y);
        context.lineTo(blueprint.right, blueprint.bottom);
        context.lineTo(blueprint.x, blueprint.bottom);
        context.closePath();

        context.fillStyle = blueprint.fill;
        context.fill();
        context.stroke();
    }

I handle the mouse move event using the following code.

function handleMouseMove(e) {

        var mouseX = parseInt(e.clientX - offsetX1);
        var mouseY = parseInt(e.clientY - offsetY1);

        // always update the global blueprint
        blueprint.x  = (mouseX - lastX);
        blueprint.y  = (mouseY - lastY);
        blueprint.right = blueprint.x   blueprint.width;
        blueprint.bottom = blueprint.y   blueprint.height;

        lastX = mouseX;
        lastY = mouseY;

        drawObjectFromBlueprint(blueprint);
    }

And I listen to the mouse move event using following code.

$("#drawable-canvas").mousemove(function (e) {
        handleMouseMove(e);
    });

I want to re-draw the object without affecting the underlying PDF. Tried to load the PDF and re-draw the object by clearing the whole canvas, and it does not work as intended.

CodePudding user response:

In the callback of the page.render method, the pdf page will be drawn on the canvas. You have to save the drawn image separately so that the original image does not disappear by dragging.

// maybe globalScope...?
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');

// your code

page.render(renderContext).then(function () {
  // Save the original page image.
  tempCanvas.width = canvas.width;
  tempCanvas.height = canvas.height;
  tempCtx.drawImage(canvas, 0, 0);

  // creating the dummy object on success
  drawObjectFromBlueprint(blueprint);
}, ...

Next, please modify it to draw with the original page image in drawObjectFromBlueprint.

function drawObjectFromBlueprint(blueprint) {
  // draw original page image
  context.drawImage(tempCanvas, 0, 0);

  // drawing a draggable element inside the canvas
  context.strokeStyle = "lightgray";
  ...
}

CodePudding user response:

After two hours of trying figures out the way to do this. I used two canvases as @Nikolaus suggested on top of each other and used the bottom canvas to load the PDF and the top canvas for the drawing.

Following is my HTML:

<div class="col-md-12" id="pdfDisplay" style="display: none;">
     <div id="pageContainer" class="pdfViewer nopadding" style="background-color:transparent">
          <canvas id="pdf-canvas" style="border:1px  solid black"></canvas>
          <canvas id="drawable-canvas" style="border:1px  solid black"></canvas>
      </div>
 </div>

Following is my CSS to place both canvases on top of each other.

#pageContainer { position: relative; }
#drawable-canvas { position: absolute; top: 0; left: 0; }

Following is my global Javascript variables:

var isUploading = false;
var base64pdf = "";

var initX = 50;
var initY = 50;
var initWidth = 200;
var initHeight = 150;

// blueprint options are in pixels
// this blueprint holds the latest values for the draggable object
// always update this global blueprint when making a change so it holds the latest values
var blueprint = {
    x: initX,
    y: initY,
    width: initWidth,
    height: initHeight,
    right: (initX initWidth), // x   width
    bottom: (initY initHeight), // y   height
    fill: "skyblue"
 };
 var context = null;
 var canvas = null;
 var drawableContext = null;
 var drawableCanvas = null;

 var canvasOffset = null;
 var offsetX = 0;
 var offsetY = 0;

 var canvasOffset1 = null;
 var offsetX1 = 0;
 var offsetY1 = 0;
 
 var lastX = 0;
 var lastY = 0;
 
 var mouseIsDown = false;

Javascript function to listen to mouse up, down and movement as I only need to track the mouse movements when the object is dragged using the mouse.

$("#drawable-canvas").mousedown(function (e) {
     var mouseX = parseInt(e.clientX - offsetX);
     var mouseY = parseInt(e.clientY - offsetY);

     lastX = mouseX;
     lastY = mouseY;
     mouseIsDown = true;
 });

 $("#drawable-canvas").mousemove(function (e) {
     handleMouseMove(e);
 });

 $("#drawable-canvas").mouseup(function (e) {
      mouseIsDown = false;
 });

Javascript function to load the PDF to the bottom canvas.

    function loadPdfPreview(base64pdf){

        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.0.943/pdf.worker.min.js';

        var loadingTask = pdfjsLib.getDocument({data: base64pdf});

        loadingTask.promise.then(function (pdf) {

            // Fetch the first page
            pdf.getPage(1).then(function (page) {

                var scale = 1.0;

                var viewport = page.getViewport(scale);

                // Prepare canvas using PDF page dimensions
                canvas = document.getElementById('pdf-canvas');
                context = canvas.getContext('2d');

                canvas.height = viewport.height;
                canvas.width = viewport.width;

                canvasOffset = $("#pdf-canvas").offset();
                offsetX = canvasOffset.left;
                offsetY = canvasOffset.top;

                canvasOffset1 = $("#drawable-canvas").offset();
                offsetX1 = canvasOffset1.left;
                offsetY1 = canvasOffset1.top;

                // creating the drawable-canvas canvas for drawing purposes without affecting the pdf-canvas
                // it has identical width and height and X and Y values
                drawableCanvas = document.getElementById('drawable-canvas');;
                drawableCanvas.height = viewport.height;
                //bringing the drawable canvas up using z-index
                drawableCanvas.style.zIndex = 1;
                drawableCanvas.width = viewport.width;
                drawableContext = drawableCanvas.getContext('2d');

                // Render PDF page into canvas context
                var renderContext = {
                    canvasContext: context,
                    viewport: viewport
                };

                page.render(renderContext).then(function () {

                    // creating the dummy object on success
                    drawObjectFromBlueprint(blueprint);

                }, function (e) {
                    console.log(e);
                });

            });
        });
    }

Handling Mouse movement(dragging the object):

    function handleMouseMove(e) {
        if (!mouseIsDown) {
            return;
        }

        var mouseX = parseInt(e.clientX - offsetX1);
        var mouseY = parseInt(e.clientY - offsetY1);

        // always update the global blueprint
        blueprint.x  = (mouseX - lastX);
        blueprint.y  = (mouseY - lastY);
        blueprint.right = blueprint.x   blueprint.width;
        blueprint.bottom = blueprint.y   blueprint.height;

        lastX = mouseX;
        lastY = mouseY;

        drawObjectFromBlueprint(blueprint);

        console.log(blueprint);
    }

Javascript function to draw the object on the drawable canvas, totally independent from the pdf-canvas.

    function drawObjectFromBlueprint(blueprint) {
        // drawing a draggable element inside the canvas
        drawableContext.strokeStyle = "lightgray";

        // Clearing the previous dummy objects
        drawableContext.clearRect(0, 0, drawableCanvas.width, drawableCanvas.height);

        // drawing the dummy object
        drawableContext.beginPath();
        drawableContext.moveTo(blueprint.x, blueprint.y);
        drawableContext.lineTo(blueprint.right, blueprint.y);
        drawableContext.lineTo(blueprint.right, blueprint.bottom);
        drawableContext.lineTo(blueprint.x, blueprint.bottom);
        drawableContext.closePath();

        drawableContext.fillStyle = blueprint.fill;
        drawableContext.fill();
        drawableContext.stroke();
    }
  • Related