How to build a snipping tool in WebViewer

Here is a quick example of how to build a snipping tool where a user can make a selection and the selection will be downloaded as a PNG:

import React, { useRef, useEffect } from 'react';
import WebViewer from '@pdftron/webviewer';
import './App.css';

const App = () => {
  const viewer = useRef(null);

  // if using a class, equivalent of componentDidMount 
  useEffect(() => {
    WebViewer(
      {
        path: '/webviewer/lib',
        initialDoc: '/files/PDFTRON_about.pdf',
      },
      viewer.current,
    ).then((instance) => {
      const { docViewer, Annotations, Tools, iframeWindow, annotManager } = instance;
      
      const createSnipTool = function() {
        const SnipTool = function() {
          Tools.RectangleCreateTool.apply(this, arguments);
          this.defaults.StrokeColor = new Annotations.Color('#F69A00');
          this.defaults.StrokeThickness = 2;
        }
      
        SnipTool.prototype = new Tools.RectangleCreateTool();
        
        return new SnipTool(docViewer);
      };

      const customSnipTool = createSnipTool();

      instance.registerTool({
        toolName: 'SnipTool',
        toolObject: customSnipTool,
        buttonImage: '/cut-solid.svg',
        buttonName: 'snipToolButton',
        tooltip: 'Snipping Tool'
      });

      instance.setHeaderItems(header => {
        header.push({
          type: 'toolButton',
          toolName: 'SnipTool',
        });
      });

      const downloadURI = (uri, name) => {
        const link = document.createElement("a");
        link.download = name;
        link.href = uri;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
      }

      customSnipTool.on('annotationAdded', (annotation) => {
        const pageIndex = annotation.PageNumber;
        // get the canvas for the page
        const pageContainer = iframeWindow.document.getElementById('pageContainer' + pageIndex);
        const pageCanvas = pageContainer.querySelector('.canvas' + pageIndex);
    
        const topOffset = parseFloat(pageCanvas.style.top) || 0;
        const leftOffset = parseFloat(pageCanvas.style.left) || 0;
        const zoom = docViewer.getZoom();
    
        const x = annotation.X * zoom - leftOffset;
        const y = annotation.Y * zoom - topOffset;
        const width = annotation.Width * zoom;
        const height = annotation.Height * zoom;
    
        const copyCanvas = document.createElement('canvas');
        copyCanvas.width = width;
        copyCanvas.height = height;
        const ctx = copyCanvas.getContext('2d');
        // copy the image data from the page to a new canvas so we can get the data URL
        ctx.drawImage(pageCanvas, x, y, width, height, 0, 0, width, height);
        downloadURI(copyCanvas.toDataURL(), "snippet.png");
    
        annotManager.deleteAnnotation(annotation);
      });

    });
  }, []);

  return (
    <div className="App">
      <div className="header">React sample</div>
      <div className="webviewer" ref={viewer}></div>
    </div>
  );
};

export default App;

Some users with screens with higher pixel resolution may find that the code above results in snips that are shifted slightly. In order to account for this you can use the the devicePixelRatio to scale the position of the snip, so it shows the correct area selected. The following code shows how this can be done (note you only need to update the actual snipping logic):

  customSnipTool.on('annotationAdded', (annotation) => {
    const pageNumber = annotation.PageNumber;
    // get the canvas for the page
    const pageContainer = instance.UI.iframeWindow.document.getElementById('pageContainer' + pageNumber);
    const pageCanvas = pageContainer.querySelector('.canvas' + pageNumber);
  
    // Scale the coordinates using the devicePixelRatio
    const scale = window.devicePixelRatio
    const topOffset = (parseFloat(pageCanvas.style.top) || 0) * scale;
    const leftOffset = (parseFloat(pageCanvas.style.left) || 0) *  scale;
    const zoom = documentViewer.getZoomLevel() * scale;

    const x = annotation.X * zoom - leftOffset;
    const y = annotation.Y * zoom - topOffset;
    const width = annotation.Width * zoom;
    const height = annotation.Height * zoom;

    const copyCanvas = document.createElement('canvas');
    copyCanvas.width = width;
    copyCanvas.height = height;
    const ctx = copyCanvas.getContext('2d');
    // copy the image data from the page to a new canvas so we can get the data URL
    ctx.drawImage(pageCanvas, x, y, width, height, 0, 0, width, height);
    downloadURI(copyCanvas.toDataURL(), "snippet.png");

    annotationManager.deleteAnnotation(annotation);
  });