lol
lol

Reputation: 11

How to correctly scale and render a PDF in pdfjs-dist with dynamic dimensions, including drawing polygons on precise coordinates?

I’m using pdfjs-dist to render a PDF in a React component. The PDF can have varying sizes (e.g., 1800x1200, 580x250, etc.), and I want to dynamically scale the canvas and render the PDF to fit within the available space (a container). Additionally, I need to draw polygons (rectangles) on specific coordinates that are provided as relative values (e.g., X and Y ranging from 0 to 1).

Here’s my current approach:

I calculate the scale dynamically based on the PDF dimensions (viewport.width, viewport.height) and the container dimensions (Box width and height). I render the PDF using page.getViewport({ scale }) and draw the polygons by mapping relative coordinates to pixels:

const coords = polygon.map((point) => ({
  x: point.X * canvasWidth,
  y: point.Y * canvasHeight, // Invert Y-axis
}));

However, the PDF renders correctly, but the canvas size and polygons are misaligned. For example:

A PDF with original dimensions 1800x1200 displays as 580x250 when logged from the viewport. The polygons don’t align with the correct positions on the PDF. Here’s a snippet of my React code:

  const [viewportCoords, setViewPortCoords] = useState({ width: 0, height: 0 });
  const [scale, setScale] = useState(1);
  const canvasRef = useRef<HTMLCanvasElement | null>(null);

  const polygonDummy = [
    { X: 0.34002524614334106, Y: 0.27618247270584106 },
    { X: 0.8136482834815979, Y: 0.2764469087123871 },
    { X: 0.8136501908302307, Y: 0.2848358154296875 },
    { X: 0.34002646803855896, Y: 0.2845710515975952 },
  ];

  useEffect(() => {
    if (!pdfView) return;

    const url = URL.createObjectURL(pdfView);
    const canvas = canvasRef.current;
    if (!canvas) return;

    GlobalWorkerOptions.workerSrc = '../../node_modules/pdfjs-dist/build/pdf.worker.mjs';

    const loadingTask = getDocument(url);
    loadingTask.promise
      .then(async (pdf) => {
        const page = await pdf.getPage(1);

        // Obtain pdf size
        const viewport = page.getViewport({ scale: 1.5 });

        // Scale based on container size
        const containerWidth = viewportCoords.width || window.innerWidth;
        const containerHeight = viewportCoords.height || window.innerHeight;

        // Calculate scaling factor to fit PDF to container
        const scaleX = containerWidth / viewport.width;
        const scaleY = containerHeight / viewport.height;
        const scale = Math.min(scaleX, scaleY); // Mantener la relación de aspecto

        setScale(scale);

        setViewPortCoords({ width: viewport.width * scale, height: viewport.height * scale });

        const context = canvas.getContext('2d');
        if (context) {
          // Scale the canvas size
          canvas.width = Math.floor(viewport.width * scale);
          canvas.height = Math.floor(viewport.height * scale);
          canvas.style.width = `${viewport.width * scale}px`;
          canvas.style.height = `${viewport.height * scale}px`;

          const renderContext = {
            canvasContext: context,
            transform: [scale, 0, 0, scale, 0, 0],
            viewport,
          };

          await page.render(renderContext).promise;
          drawPolygon(context, viewport, polygonDummy);
        }
      })
      .catch((error) => console.error('Error al cargar el PDF:', error));

    return () => URL.revokeObjectURL(url);
  }, [pdfView]);

  const drawPolygon = (
    context: CanvasRenderingContext2D,
    viewport: any,
    polygon: { X: number; Y: number }[]
  ) => {
    const canvasWidth = viewport.width * scale;
    const canvasHeight = viewport.height * scale;

    context.beginPath();
    context.strokeStyle = 'red';
    context.lineWidth = 2;

    const coords = polygon.map((point) => ({
      x: point.X * canvasWidth,
      y: point.Y * canvasHeight, // Invertir Y
    }));

    if (coords.length > 0) {
      context.moveTo(coords[0].x, coords[0].y);
      for (let i = 1; i < coords.length; i++) {
        context.lineTo(coords[i].x, coords[i].y);
      }
      context.closePath();
      context.stroke();
    }
  };

  return (
    <Box
      sx={{
        width: viewportCoords.width,
        height: viewportCoords.height,
        overflow: 'auto',
        border: '1px solid black',
      }}
    >
      <canvas ref={canvasRef}></canvas>
    </Box>

How can I ensure:

The canvas size matches the PDF dimensions and scales correctly to the container? The polygons align perfectly with the specified coordinates on the PDF?

Upvotes: 1

Views: 61

Answers (0)

Related Questions