react-pdf-viewer / react-pdf-viewer

A React component to view a PDF document
https://react-pdf-viewer.dev
Other
2.27k stars 251 forks source link

[New Feature] Working Code for Selecting Portion of PDF and Extracting Image (User Drag to Extract) #1707

Open jatinvinkumar opened 9 months ago

jatinvinkumar commented 9 months ago

Here's working code I wrote to allow the user to select a part of the pdf and download the selected area as an image. Hope this helps anyone looking for this feature :) - be sure to set the document url. @phuocng Thank you for the awesome repo! I'd be happy to clean up my code and submit an official pull request for this feature over the next few days. Lmk what you think.

import React, { useState, useEffect, useRef } from 'react'; import { Button, Position, Tooltip, Viewer, RenderPageProps, Worker, PluginRenderPageLayer, Plugin, PluginFunctions } from '@react-pdf-viewer/core'; // Import the styles import '@react-pdf-viewer/core/lib/styles/index.css'; import '@react-pdf-viewer/default-layout/lib/styles/index.css'; import { MessageIcon, RenderHighlightTargetProps, highlightPlugin, getImageFromArea } from '../../../../packages/highlight/src';

const Drag = ({ PluginRenderPageLayer, pageIndex, setCanvas, setDimensions }) => { var isDragging = false const dragStart = useRef({ x: 0, y: 0 }); const initialCanvasState = useRef(null); // New ref to store initial canvas state const { canvasLayerRef, scale, width } = PluginRenderPageLayer;

const captureCanvasState = () => {
    const canvas = canvasLayerRef.current;
    const ctx = canvas.getContext('2d');
    initialCanvasState.current = ctx.getImageData(0, 0, canvas.width, canvas.height);
};

const restoreCanvasState = () => {
    const canvas = canvasLayerRef.current;
    const ctx = canvas.getContext('2d');
    ctx.putImageData(initialCanvasState.current, 0, 0);
};

const handleMouseMove = (e) => {
    if (!isDragging || !canvasLayerRef.current) {
        return;
    }

    restoreCanvasState(); // Restore the initial canvas state

    const canvas = canvasLayerRef.current;
    const ctx = canvas.getContext('2d');
    const rect = canvas.getBoundingClientRect();

    const scaleConst = canvas.width / width;
    const currentX = (e.clientX - rect.left) * scaleConst;
    const currentY = (e.clientY - rect.top) * scaleConst;
    const rectWidth = currentX - dragStart.current.x;
    const rectHeight = currentY - dragStart.current.y;

    ctx.lineWidth = 4; // Set the line width to 2 pixels
    ctx.setLineDash([6, 3]); // Set dashed line style
    ctx.beginPath();
    ctx.rect(dragStart.current.x, dragStart.current.y, rectWidth, rectHeight);
    ctx.strokeStyle = '#1A84FF';
    ctx.stroke();
};

const handleMouseDown = (e) => {
    e.preventDefault();
    if (isDragging || !canvasLayerRef.current) {
        return;
    }
    const canvas = canvasLayerRef.current;
    isDragging = true;
    //document.body.style.cursor = 'crosshair'; // Change cursor to crosshair
    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('mouseup', handleMouseUp);

    // Restore the canvas to its initial state before drawing a new rectangle
    restoreCanvasState();

    const ctx = canvas.getContext('2d');
    const rect = canvas.getBoundingClientRect();
    const scaleConst = canvas.width/width

    dragStart.current = {
        x: (e.clientX - rect.left)*scaleConst,
        y: (e.clientY - rect.top)*scaleConst,
    };
    isDragging = true;
    const x = (e.clientX - rect.left)*scaleConst;
    const y = (e.clientY - rect.top)*scaleConst;
    console.log("Mouse down at:", x, y, "Canvas Width:", canvas.width, "Page Width:", canvas.width, "Scale:", scale);
}

const handleMouseUp = (e) => {
    e.preventDefault();

    if (!PluginRenderPageLayer.canvasLayerRef.current) {
        return;
    }

    window.removeEventListener('mouseup', handleMouseUp);
    console.log("mouseup")
    const canvas = canvasLayerRef.current;
    const ctx = canvas.getContext('2d');
    const rect = canvas.getBoundingClientRect();

    const scaleConst = canvas.width/width
    const x = (e.clientX - rect.left)*scaleConst;
    const y = (e.clientY - rect.top)*scaleConst;

    if (PluginRenderPageLayer.canvasLayerRef.current) {
        const canvas = PluginRenderPageLayer.canvasLayerRef.current;
        setCanvas(canvas);
        setDimensions({
            x: dragStart.current.x,
            y: dragStart.current.y,
            width: x - dragStart.current.x,
            height: y - dragStart.current.y,
        });
    }

    isDragging = (false);
};

useEffect(() => {
    const textLayerEle = PluginRenderPageLayer.textLayerRef.current;
    if (!PluginRenderPageLayer.canvasLayerRendered || !PluginRenderPageLayer.textLayerRendered || !textLayerEle) {
        return;
    }
    // Capture the initial state of the canvas
    captureCanvasState();

    textLayerEle.addEventListener('mousedown', handleMouseDown);
    return () => {
        textLayerEle.removeEventListener('mousedown', handleMouseDown);
    };
}, [PluginRenderPageLayer.canvasLayerRendered, PluginRenderPageLayer.textLayerRendered]);

return <></>;

};

const WaterMarkExample: React.FC = () => { const [documentURL, setDocumentURL] = useState("");

useEffect(() => {
    const interval = setInterval(() => {
        const canvas = document.querySelector('canvas');
        if (canvas) {
            downloadCanvasAsImage(canvas);
            clearInterval(interval);
        }
    }, 1000);

    return () => clearInterval(interval);
}, []);

// Define your custom plugin const createCustomPlugin = (setCanvas, setDimensions) => { const customPlugin: Plugin = { // Install is called when the plugin is added to the Viewer install: (pluginFunctions: PluginFunctions) => { // You can use pluginFunctions to interact with the viewer }, // Called when the document is successfully loaded onDocumentLoad: (props) => { console.log('Document is loaded', props); }, // Add other necessary plugin methods and properties here renderPageLayer: (props: PluginRenderPageLayer) => { // Your custom page layer rendering console.log("RenderPageLayer props:", props);

        //useCanvasMouseEvents(props.canvasLayerRef);

        return (
            <>
                <Drag PluginRenderPageLayer={props} pageIndex={props.pageIndex} setCanvas={setCanvas} setDimensions={setDimensions}/>
            </>
        );
    },
};

return customPlugin;

};

const renderPage = (props: RenderPageProps) => {
    return (
        <>
            {props.canvasLayer.children}
            {props.textLayer.children}
                {props.annotationLayer.children}
            <div
            style={{
                alignItems: 'center',
                display: 'flex',
                height: '100%',
                justifyContent: 'center',
                left: 0,
                position: 'absolute',
                top: 0,
                width: '100%',
            }}
        >
            <div
                style={{
                    color: 'rgba(0, 0, 0, 0.2)',
                    fontSize: `${8 * props.scale}rem`,
                    fontWeight: 'bold',
                    textTransform: 'uppercase',
                    transform: 'rotate(-45deg)',
                    userSelect: 'none',
                }}
            >
                Draft
            </div>
        </div>
    </>
    );
};

const [pdfCanvas, setCanvas] = useState(null)
const [dimensions, setDimensions] = useState(null)

const handleExtractImage = () => {
    if (pdfCanvas && dimensions) {
        // Logic to extract and download the image
        const ctx = pdfCanvas.getContext('2d');
        const imageData = ctx.getImageData(dimensions.x, dimensions.y, dimensions.width, dimensions.height);
        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = dimensions.width;
        tempCanvas.height = dimensions.height;
        const tempCtx = tempCanvas.getContext('2d');
        tempCtx.putImageData(imageData, 0, 0);

        const imageUrl = tempCanvas.toDataURL("image/png");
        const downloadLink = document.createElement("a");
        downloadLink.href = imageUrl;
        downloadLink.download = "extracted-image.png";
        document.body.appendChild(downloadLink);
        downloadLink.click();
        document.body.removeChild(downloadLink);
    }
};

// Create an instance of your custom plugin
const customPluginInstance = createCustomPlugin(setCanvas, setDimensions);
return (
    <div style={{ height: '800px' }}>
        <button onClick={handleExtractImage}>Extract Image</button>
        <Worker workerUrl="https://unpkg.com/pdfjs-dist@3.4.120/build/pdf.worker.min.js">
            <Viewer
                fileUrl={documentURL}
                renderPage={renderPage}
                plugins={[customPluginInstance]}
            />
        </Worker>
    </div>
);

};

export default WaterMarkExample;

phuocng commented 9 months ago

Thanks you. I'll consider adding an example based on this work.