DocumentScanner Example

Here’s an example of a Lightning web component that uses DocumentScanner to capture text data from an image.

There’s an option to select the source of the image to be scanned, from either the device camera or the device image library.

<template>
  <table class="rootTable">
   <tbody>
    
    <!-- Document scanning controls -->
    <tr>
     <td style="height: 1px;">
      // Choose source of the document to be scanned
      <lightning-card title="Document Scanner" icon-name="custom:display_text">
       <div class="slds-var-p-around_medium">
        Select source of document to be scanned:
        <br/><br/>
        
        <lightning-button
         variant="brand"
         label="Camera" 
         title="Capture document with camera"
         onclick={handleScanFromCameraClick}>
        </lightning-button>
        
        <lightning-button
         variant="brand"
         label="Photo Library" 
         title="Scan document from photo library"
         onclick={handleScanFromPhotoLibraryClick} 
         class="slds-var-m-left_x-small">
        </lightning-button>
       </div>
       
       <!-- Display errors, if any -->
       <template if:true={scannerError}>
        <lightning-formatted-text value={scannerError}>
        </lightning-formatted-text>
       </template>
       
       <!-- Display text of scanned document, if any -->
       <template if:true={scannedDocument}>
        // results of the scan are displayed here
        <div class="slds-var-p-around_medium">
         Text Recognition Result: <br/><br/>
         {scannedDocument.text}
        </div>
       </template>
       
      </lightning-card>
     </td>
    </tr>
    
    <!-- If there is a scanned document, display a preview -->
    <tr>
     <td>
      <template if:true={scannedDocument}>
       <div class="previewDiv">
        
        <!-- document image --> 
        <div class="divContentCentered">
         <img class="previewImage" src={scannedDocument.imageBytes} 
          onload={addImageHighlights} />
        </div>
        
        <!-- highlights overlay; note use of manual DOM rendering -->
        <div class="divContentCentered">
         <div class="contour" lwc:dom="manual"></div>
        </div>
        
       </div>
      </template>
     </td>
    </tr>
    
   </tbody>
  </table>
 </template>

This example uses DocumentScanner to choose a source for the document to be scanned, and to perform a basic scanning operation. After the scan completes, the results are displayed, along with an SVG graphic that annotates the scanned document graphic with an overlay of the result structured text data. A status message is returned when there’s an error.

import { LightningElement } from "lwc";
import { getDocumentScanner } from "lightning/mobileCapabilities";

export default class DocumentScanner extends LightningElement {
  // Scan results (if any)
  scannerError;
  scannedDocument;

  handleScanFromCameraClick() {
    this.scanDocument("DEVICE_CAMERA");
  }

  handleScanFromPhotoLibraryClick() {
    this.scanDocument("PHOTO_LIBRARY");
  }

  scanDocument(imageSource) {
    // Clear previous results / errors
    this.resetScanResults();

    // Main document scan cycle
    const myScanner = getDocumentScanner();
    if (myScanner.isAvailable()) {
      // Configure the scan
      const options = {
        imageSource: imageSource,
        scriptHint: "LATIN",
        returnImageBytes: true,
      };

      // Perform document scan
      myScanner
        .scan(options)
        .then((results) => {
          // Do something with the results
          this.processScannedDocuments(results);
        })
        .catch((error) => {
          // Handle errors
          this.scannerError =
            "Error code: " + error.code + "\nError message: " + error.message;
        });
    } else {
      // Scanner not available
      this.scannerError =
        "Problem initiating scan. Are you using a mobile device?";
    }
  }

  resetScanResults() {
    this.scannedDocument = null;
    this.scannerError = null;
  }

  processScannedDocuments(documents) {
    // DocumentScanner only processes the first scanned document in an array
    this.scannedDocument = documents[0];
    // And this is where you take over; process results as desired
  }

  // Build an annotation overlay graphic, to display on top of the scanned image
  addImageHighlights(event) {
    const textBlocks = this.scannedDocument?.blocks;
    if (!textBlocks) {
      return;
    }

    const img = event.srcElement;
    const cWidth = img.clientWidth;
    const cHeight = img.clientHeight;
    const nWidth = img.naturalWidth;
    const nHeight = img.naturalHeight;
    const width = Math.min(cWidth, nWidth);
    const height = Math.min(cHeight, nHeight);

    let svg =
      `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" ` +
      `xmlns:xlink="http://www.w3.org/1999/xlink" ` +
      `width="${width}" height="${height}" viewBox="0, 0, ${nWidth}, ${nHeight}">`;
    textBlocks.forEach((block) =>
      block.lines.forEach((line) =>
        line.elements.forEach((element) => {
          const frame = element.frame;
          svg +=
            `<rect x="${frame.x}" y="${frame.y}" width="${frame.width}" ` +
            `height="${frame.height}" style="fill:green;fill-opacity:0.5" />`;
        })
      )
    );
    svg += "</svg>";

    // Manually attach the overlay SVG to the LWC DOM to render it
    this.template.querySelector(".contour").innerHTML = svg;
  }
}