0

In my REACT project, I must :

  • Import a pdf file from an url // OK
  • Show PDF in browser // OK

And there is how I do :

function handlePages(page, pdfScale) {
  //This gives us the page's dimensions at full scale
  const viewport = page.getViewport({ scale: pdfScale, rotation: 0 });
  const viewer = document.querySelector("#pdf-viewer");

  //We'll create a canvas for each page to draw it on
  var canvas = document.createElement("canvas");
  canvas.style.display = "block";
  const context = canvas.getContext("2d");
  canvas.height = viewport.height;
  canvas.width = viewport.width;

  //Draw it on the canvas
  page.render({ canvasContext: context, viewport: viewport });

  //Add it to the web page
  viewer.appendChild(canvas);
}

export const XY = () => {
    const [pdfScale, setPdfScale] = useState(1);

    useEffect(() => {
      (async function () {
        const pdfJS = await import("pdfjs-dist/build/pdf");
        pdfJS.GlobalWorkerOptions.workerSrc =
          window.location.origin + "/pdf.worker.min.js"; // copied in public dir
        const pdf = await pdfJS.getDocument("example.pdf").promise;
        document.querySelector("#pdf-viewer").replaceChildren();
        for (let i = 1; i <= pdf.numPages; i++) {
          await pdf.getPage(i).then((page) => handlePages(page, pdfScale));
        }
      })();
    }, [pdfScale]);
}

My question is how can I do for zooming an rotating only the wiewed page in the browser.

I m able to zoom(+/-) the hole document using the proprety scale of viewport. I searched the old questions, but any of them give me an idea.

Why not showing how to import PDFViewerApplication, is that a native JS class ?? if that why I m having undefined, m I missing an npm install ?

Sample example : in this example zooming and rotation are applied to the hole document too, is it possible for only shown page for a given scale ? When scrolling, I can see the current number page, I have triyed to understand the source code but I failed.

Thanks for suggestions.

Ahmed Kallali
  • 79
  • 1
  • 5
  • I tryed to guess current (page/canvas) with some custom logic but I failed : when I create canvas element I store the top and bottom positions in an array and then match window.scrollY within this array but unfortunately when I incease the scale of my pdf more than 2, the scrollY is too small to compare – Ahmed Kallali Aug 28 '23 at 11:50

1 Answers1

0

Solved my self, I will post the solution for future use :

first of all I add an id for each created canvas just after createElement.

canvas.id = "unique_id"

I create 3 button :

<button id="rotation" onClick={rotateCurrentPage} className="pdf-rot">
<button id="zoominbutton" type="button" onClick={zoomIn}>
<button id="zoomoutbutton" type="button" onClick={zoomOut}>

ZoomIn / ZoomOut are juste simple function that update a state for scale

function zoomIn() {
    setPdfScale(pdfScale + 0.5);
}
function zoomOut() {
    if (pdfScale <= 1) {
        return;
    }
    setPdfScale(pdfScale - 0.5);
}

D'ont dorget to :

const [pdfScale, setPdfScale] = useState(0.5);

The interesting part : rotateCurrentPage :

function rotateCurrentPage() {
    if (pdfScale >= 2) {
        const visibleCanvas = [];
        // Get All created canvas
        document.querySelectorAll("[id^=page-]").forEach((canvas) => {
            // check if part of canvas is shown in the visible browser 
            // area
            const vc = isVisible(canvas);
            // if it is visible we we store visible canvas in an array
            if (vc.visible) visibleCanvas.push(vc);
        });
        // sort visible canvas by percent visibility and get the first 
        // one
        const currentCanvasVisible = visibleCanvas.sort(
        (c1, c2) => c2.percent - c1.percent)[0];
        // select the canvas with that id
        const currentCanvas = document.querySelector(
        "#" + currentCanvasVisible.id);
        if (currentCanvas) {
            // check the current canvas rotation, then add a degree for 
            // his transform and store that value in state
            const deg = getRotationDegrees(currentCanvas) + 90;
            rotateCanvas(currentCanvas, deg);
            // Push new canvas deg in an array, aka ({page-1 : 90}, 
            // {page-2: 180}...)
            setCanvasRotates((canvasRotates) => [...canvasRotates,
            { [currentCanvas.id]: deg },]);
        }
    }
}

The others functions :

isVisible

function isVisible(canvas) {
const elementRect = canvas.getBoundingClientRect();
let visibilityPercentage = 0;

// Obtenez les dimensions de la fenêtre visible du navigateur
const viewportWidth =
  window.innerWidth || document.documentElement.clientWidth;
const viewportHeight =
  window.innerHeight || document.documentElement.clientHeight;

// Obtenez le niveau de zoom (ratio entre la largeur de la fenêtre et la largeur intérieure de l'élément)
const zoomLevel = viewportWidth / elementRect.width;

// Calculez les coordonnées de l'élément dans la fenêtre du navigateur avec le niveau de zoom pris en compte
const elementX = elementRect.left * zoomLevel;
const elementY = elementRect.top * zoomLevel;

// Vérifiez si une partie de l'élément est visible en comparant ses coordonnées avec les dimensions de la fenêtre visible
const visible =
  elementX < viewportWidth &&
  elementX + elementRect.width > 0 &&
  elementY < viewportHeight &&
  elementY + elementRect.height > 0;

if (visible) {
  const visibleWidth =
    Math.min(elementX + elementRect.width, viewportWidth) -
    Math.max(elementX, 0);
  const visibleHeight =
    Math.min(elementY + elementRect.height, viewportHeight) -
    Math.max(elementY, 0);

  const visibleArea = visibleWidth * visibleHeight;
  const totalArea = elementRect.width * elementRect.height;

  visibilityPercentage = (visibleArea / totalArea) * 100;
}

return { id: canvas.id, visible: visible, percent: visibilityPercentage };

}

getRotationDegrees

function getRotationDegrees(canvas) {
const styles = window.getComputedStyle(canvas);
const transform = styles.transform;
if (transform === "none") {
  return 0;
}
const matrix = styles.transform;
let angle = 0;
if (matrix !== "none") {
  const values = matrix.split("(")[1].split(")")[0].split(",");
  const a = values[0];
  const b = values[1];
  angle = Math.round(Math.atan2(b, a) * (180 / Math.PI));
}

return angle < 0 ? angle + 360 : angle;

}

rotateCanvas

function rotateCanvas(canvas, deg) {
    canvas.style.transform = "rotate(" + deg + "deg)";
}

To mixing business with pleasure, I do clean my component specially useEffect and at the end, I add a function to get canvas rotations degrees so I can rotate my canvas even after zomming in/out

THE COMPLETE COMPONENT

import React, { useEffect, useState } from "react";
import "./suiviOF.css";

let numPages = 0;

export const SuiviOF = () => {
    const [pdfScale, setPdfScale] = useState(0.5);
    const [canvasRotates, setCanvasRotates] = useState([]);
    
    function canvasWithRotate() {
        canvasRotates.forEach((canvas) =>
        rotateCanvas(document.querySelector("#" + Object.keys(canvas) 
        [0]),Object.values(canvas)[0]));
    }

    function zoomIn() {
        setPdfScale(pdfScale + 0.5);
    }

    function zoomOut() {
        if (pdfScale <= 1) {
            return;
        }
        setPdfScale(pdfScale - 0.5);
    }

    function isVisible(canvas) {
        const elementRect = canvas.getBoundingClientRect();
        let visibilityPercentage = 0;

        // Obtenez les dimensions de la fenêtre visible du navigateur
        const viewportWidth =
        window.innerWidth || document.documentElement.clientWidth;
        const viewportHeight =
        window.innerHeight || document.documentElement.clientHeight;

       // Obtenez le niveau de zoom (ratio entre la largeur de la fenêtre 
       // et la largeur intérieure de l'élément)
       const zoomLevel = viewportWidth / elementRect.width;

       // Calculez les coordonnées de l'élément dans la fenêtre du 
       // navigateur avec le niveau de zoom pris en compte
       const elementX = elementRect.left * zoomLevel;
       const elementY = elementRect.top * zoomLevel;

       // Vérifiez si une partie de l'élément est visible en comparant 
       // ses coordonnées avec les dimensions de la fenêtre visible
       const visible =
           elementX < viewportWidth &&
           elementX + elementRect.width > 0 &&
           elementY < viewportHeight &&
           elementY + elementRect.height > 0;

       if (visible) {
           const visibleWidth = Math.min(elementX + elementRect.width, 
           viewportWidth) - Math.max(elementX, 0);
           const visibleHeight = Math.min(elementY + elementRect.height, 
           viewportHeight) - Math.max(elementY, 0);

           const visibleArea = visibleWidth * visibleHeight;
           const totalArea = elementRect.width * elementRect.height;

           visibilityPercentage = (visibleArea / totalArea) * 100;
       }

       return { id: canvas.id, visible: visible, percent: 
                visibilityPercentage };
  }

  function getRotationDegrees(canvas) {
      const styles = window.getComputedStyle(canvas);
      const transform = styles.transform;
      if (transform === "none") {
          return 0;
      }
      const matrix = styles.transform;
      let angle = 0;
      if (matrix !== "none") {
          const values = matrix.split("(")[1].split(")")[0].split(",");
          const a = values[0];
          const b = values[1];
          angle = Math.round(Math.atan2(b, a) * (180 / Math.PI));
      }

      return angle < 0 ? angle + 360 : angle;
  }

  function rotateCanvas(canvas, deg) {
      canvas.style.transform = "rotate(" + deg + "deg)";
  }

  function rotateCurrentPage() {
      if (pdfScale >= 2) {
          const visibleCanvas = [];
          // Get All created canvas
          document.querySelectorAll("[id^=page-]").forEach((canvas) => {
          // check if part of canvas is shown in the visible browser area 
          const vc = isVisible(canvas);
          // if it is visible we we store visible canvas in an array
          if (vc.visible) visibleCanvas.push(vc);
  });
  // sort visible canvas by percent visibility and get the first one
  const currentCanvasVisible = visibleCanvas.sort(
    (c1, c2) => c2.percent - c1.percent)[0];
  // select the canvas with that id 
  const currentCanvas = document.querySelector("#" + 
  currentCanvasVisible.id);
      if (currentCanvas) {
          // check the current canvas rotation, then add a degree for his 
          // transform and store that value in state
          const deg = getRotationDegrees(currentCanvas) + 90;
          rotateCanvas(currentCanvas, deg);
          // Push new canvas deg in an array, aka ({page-1 : 90}, {page- 
          // 2:180}...)
          setCanvasRotates((canvasRotates) => [...canvasRotates,
          { [currentCanvas.id]: deg },]);
      }
    }
  }

  function handlePage(viewer, page, pdfScale, index) {
      //This gives us the page's dimensions at full scale
      const viewport = page.getViewport({ scale: pdfScale });

      //We'll create a canvas for each page to draw it on
      const canvas = document.createElement("canvas");
      const id = "page-" + index;
      canvas.id = id;
      canvas.classList.add("page");
      const context = canvas.getContext("2d");
      canvas.height = viewport.height;
      canvas.width = viewport.width;

      //Draw it on the canvas
      page.render({ canvasContext: context, viewport: viewport });

      //Add it to the web page
      viewer.appendChild(canvas);
  }

  async function handlePages(pdfScale) {
      const pdfJS = await import("pdfjs-dist/build/pdf");
      pdfJS.GlobalWorkerOptions.workerSrc =
      window.location.origin + "/pdf.worker.min.js";
      const pdf = await pdfJS.getDocument("example.pdf").promise;
      numPages = pdf.numPages;
      const loader = document.querySelector("#load-wrapper");
      const viewer = document.querySelector("#pdf-viewer");
      loader.classList.remove("d-none");

      // Empty viewer wrapper everytime to update viewport
      viewer.replaceChildren();
      for (let i = 1; i <= pdf.numPages; i++) {
          await pdf.getPage(i).then((page) => handlePage(viewer, page, 
          pdfScale, i));
      }
      canvasWithRotate();
      loader.classList.add("d-none");
  }

  useEffect(() => {(async function () {await handlePages(pdfScale);})();
  }, [pdfScale]);

  return (
      <>
          <div id="load-wrapper" className="d-none">
              <div id="loader">
              <div className="lds-roller">
                  <div></div>
                  <div></div>
                  <div></div>
                  <div></div>
                  <div></div>
                  <div></div>
                  <div></div>
                  <div></div>
                  </div>
              </div>
              </div>
          <div className="pdf-nav-wrapper">
          <div className="pdf-nav">
              <button id="rotation" onClick={rotateCurrentPage} 
              className="pdf-rot">Rotate</button>
              <button id="zoominbutton" type="button" onClick={zoomIn}>
              zoom in</button>
              <button id="zoomoutbutton" type="button" onClick={zoomOut}>
              zoom out</button>
          </div>
          </div>
          <div className="pdf-viewer-wrapper">
              <div id="pdf-viewer" />
          </div>
          <canvas style={{ height: "100vh" }} id="canvas" className="d- 
          none" />
      </>);
  };

Tested and works fine in chrome, it may be some adaptation if you use other browsers

Ahmed Kallali
  • 79
  • 1
  • 5