> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/get-started/samples/showcase-demo-pdf-text-extractor.md).

# PDF Text Extractor Showcase Demo Code Sample

Extract text from PDFs—either from entire pages or just the highlighted sections.

{% hint style="info" %}
**Requirements**

*These packages are required to use these features in production. Trial keys have unlimited access to all features*

<a href="/web/get-started/readme.md" class="button primary">Web SDK</a><a href="/web/full-api/full-api-overview.md" class="button primary">Full API</a><a href="https://showcase.apryse.com/pdf-text-extractor" class="button primary">Live demo</a>
{% endhint %}

Quickly extract text from PDFs—either from entire pages or highlighted sections only.

This demo allows you to:

* Upload your own PDF file.
* Highlighting text to perform extraction.
* Preview extracted text: Full page and highlighted text.

**Implementation steps** To add PDF Text Extraction capability with WebViewer:

Step 1: Choose your [preferred web stack for WebViewer](/web/get-started/readme.md). Step 2: Add the ES6 JavaScript sample code provided in this guide.

Once you generate your license key, it will automatically be included in your sample code below.

{% @apryse-license-key/apryse-license-key platform="WEB\_VIEWER" variant="compact" %}

{% tabs %}
{% tab title="index.js" %}

<pre class="language-js" data-line-numbers><code class="lang-js">// ES6 Compliant Syntax
// GitHub Copilot, Claude Sonnet 4 (Preview), October 14, 2025
// File: showcase-demos/pdf-text-extractor/index.js
import WebViewer from '@pdftron/webviewer';

const licenseKey = '<code class="expression">visitor.claims.wvKey || "YOUR_WEBVIEWER_LICENSE_KEY"</code>';

// Global variables to track state
let redactionDemoFile = "https://apryse.s3.amazonaws.com/public/files/samples/section-508.pdf";
let pageCount = 0;
let textContent = '';
let annotTextContent = '';
let currentPage = 0;

// Function to initialize and load the Redaction Tool
function initializeWebViewer() {

  const element = document.getElementById('viewer');
  if (!element) {
    console.error('Viewer div not found.');
    return;
  }

  WebViewer({
    path: '/lib',
    initialDoc: redactionDemoFile,
    licenseKey: licenseKey,
    fullAPI: true,
    enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File  
  }, element).then(instance => {
    // define documentViewer for use in other functions
    const { documentViewer } = instance.Core;
    documentViewer.addEventListener('documentLoaded', () => {
      const { UI } = instance;
      UI.setLayoutMode(UI.LayoutMode.Single);                  // Set the layout mode to single page view     
      UI.disableFadePageNavigationComponent();                 // Keeps the page navigation component on screen all the time
      pageCount = documentViewer.getDocument().getPageCount(); // Update page count   
      setPage(1);                                              //Set 1st page to trigger text extraction
    });
    // Event listeners for page changes
    documentViewer.addEventListener('pageNumberUpdated', viewerUpdated);
    // Annotation change listener to update annotation text when annotations are modified
    instance.Core.documentViewer
      .getAnnotationManager()
      .addEventListener('annotationChanged', getAnnotListener);
    // UI Section 
    createUIElements();
    // Trigger immediate UI update if available
    if (window.updateUIContent) {
      window.updateUIContent();
    }
  });
}

// Function to extract all text from a given page
async function getAllTextFromDocument(pageNumber) {
  const doc = window.WebViewer.getInstance().Core.documentViewer.getDocument();
  if (pageNumber > 0 &#x26;&#x26; doc) {
    await getText(pageNumber);     // Only proceed if we have a valid document
    await getAnnotText(pageNumber);
  } else {
    console.warn('Document not available or invalid page number');
  }
};

// Function to extract text from a given page
async function getText(pageNumber) {
  const { documentViewer } = window.WebViewer.getInstance().Core;
  const doc = documentViewer.getDocument();
  // Check if document is loaded before proceeding
  if (!doc) {
    return;
  }

  const newPageCount = doc.getPageCount();
  await doc.loadPageText(pageNumber, (newText) => {
    textContent = newText;
    pageCount = newPageCount;
    pageNumber = pageNumber;
  });
}

// Function to extract text under annotations on a given page
async function getAnnotText(pageNumber) {
  const { PDFNet, documentViewer } = window.WebViewer.getInstance().Core;
  await PDFNet.initialize();
  await documentViewer.getAnnotationsLoadedPromise(); // Ensure annotations are loaded
  const doc = await getPDFDocument(documentViewer, PDFNet);
  const annotationManager = documentViewer.getAnnotationManager();
  const annotList = annotationManager
    .getAnnotationsList()
    .filter((a) => a.getPageNumber() === pageNumber);
  const xfdf_string = await annotationManager.exportAnnotations({ annotationList: annotList });
  const textOutput = [];

  if (!doc) {
    console.warn('PDF document not available');
    return;
  }

  // Run PDFNet methods with memory management
  await PDFNet.runWithCleanup(async () => {
    // lock the document before a write operation
    // runWithCleanup will auto unlock when complete
    try {
      doc.lock();
      const fdf_doc = await PDFNet.FDFDoc.createFromXFDF(xfdf_string);
      await doc.fdfUpdate(fdf_doc);
      const pageTemp = await doc.getPage(pageNumber);
      const rect = await pageTemp.getCropBox();
      const te = await PDFNet.TextExtractor.create();
      te.begin(pageTemp, rect);
      const annotCount = await pageTemp.getNumAnnots();
      for (let i = 0; i &#x3C; annotCount; ++i) {
        const annot = await pageTemp.getAnnot(i);
        const annotText = await te.getTextUnderAnnot(annot);
        textOutput.push(annotText);
      }
    } catch (e) {
      console.log('Document no longer exists, demo probably unmounted', e);
    }
    annotTextContent = textOutput.join('\n');    
    updateGlobalVars(); // Update global variables after annotation extraction completes
    // Trigger immediate UI update if available
    if (window.updateUIContent) {
      window.updateUIContent();
    }
  });
  // Trigger UI update after annotation extraction is complete
  if (window.updateUIElements) {
    window.updateUIElements(pageNumber);
  }
};

// Helper function to get PDFDoc from DocumentViewer
async function getPDFDocument(documentViewer, PDFNet) {
  const currentDocument = documentViewer.getDocument();
  let doc;
  if (!currentDocument) return;
  if (currentDocument.type === 'office') {
    const coreControls = window.WebViewer.getInstance().Core;
    const buff = await currentDocument.getFileData();
    const split = currentDocument.filename.split('.');
    const extension = split[split.length - 1];
    const options = {
      extension: extension,
    };
    const pdfBuffer = await coreControls.officeToPDFBuffer(buff, options);
    doc = await PDFNet.PDFDoc.createFromBuffer(pdfBuffer);
  } else {
    doc = await currentDocument.getPDFDoc();
  }
  return doc;
};

// Function to set the current page and trigger text extraction
function setPage(pageNumber) {
  // Validate page number
  if (isNaN(pageNumber) || pageNumber &#x3C; 0 || pageNumber > pageCount) return;
  window.WebViewer.getInstance().Core.documentViewer.setCurrentPage(Number(pageNumber));
  currentPage = pageNumber;
  // Trigger text extraction for the new page
  getAllTextFromDocument(pageNumber);
};

// Listener for annotation changes to update annotation text on the current page
function getAnnotListener() { 
  getAllTextFromDocument(window.WebViewer.getInstance().Core.documentViewer.getCurrentPage());
}

// Function called on page change to update current page
function viewerUpdated() {
  setPage(window.WebViewer.getInstance().Core.documentViewer.getCurrentPage());
}

// UI Elements
// Function to create and initialize UI elements
function createUIElements() {
  // Create a container for all controls (label, dropdown, and buttons)
  // Dynamically load ui-elements.js if not already loaded
  if (!window.SidePanel) {
    const script = document.createElement('script');
    script.src = '/showcase-demos/pdf-text-extractor/ui-elements.js';
    script.onload = () => {
      UIElements.init('viewer');

    };
    document.head.appendChild(script);
  }
}

// Function to update global window variables
function updateGlobalVars() {
  window.currentPage = currentPage;
  window.pageCount = pageCount;
  window.textContent = textContent;
  window.annotTextContent = annotTextContent;
}

// Initialize the WebViewer
initializeWebViewer();

</code></pre>

{% endtab %}

{% tab title="ui-elements.js" %}
{% code title="ui-elements.js" lineNumbers="true" %}

```js
// ES6 Compliant Syntax
// GitHub Copilot, Claude Sonnet 4 (Preview), October 14, 2025
// File: showcase-demos/pdf-text-extractor/ui-elements.js

class UIElements {

  static init(viewerId) {
    this.createSidePanel(viewerId);
  }

  // Function to create a side panel that sits on the left side of the viewer
  static createSidePanel(viewerId) {
    const viewerElement = document.getElementById(viewerId);
    if (!viewerElement) {
      console.error(`Viewer element with id '${viewerId}' not found.`);
      return;
    }

    // Create the side panel container
    const sidePanel = document.createElement('div');
    sidePanel.id = 'side-panel';
    sidePanel.className = 'side-panel';

    // Create side panel content
    const content = document.createElement('div');
    content.className = 'side-panel-content';

    // Add the text extraction content
    const sampleContent = document.createElement('div');
    sampleContent.innerHTML = `
      <div class="panel-section">
        <h4>Text Extraction</h4>

        <div id="page-info">
          <label class="page-label"><strong>Page</strong></label>
          <input type="number" id="input-page-number" min="1" value="0" class="page-input" readonly>
          <label id="page-count-label" class="page-count-label">of 0 full page text</label>
        </div>

        <div id="page-text-all">
          <label class="text-section-label">Page Text Content:</label>
          <textarea id="page-text-content" class="text-display" readonly placeholder="Page text will appear here..."></textarea>
        </div>

        <div id="page-text-annotations">
          <label id="annotations-label" class="text-section-label">Page 1 text under annotations:</label>
          <textarea id="page-annotations-content" class="text-display" readonly placeholder="Annotation text will appear here..."></textarea>
        </div>
      </div>
    `;

    content.appendChild(sampleContent);
    sidePanel.appendChild(content);
 
    // Create a wrapper to contain both the side panel and viewer
    const wrapper = document.createElement('div');
    wrapper.id = 'viewer-wrapper';
    wrapper.className = 'viewer-wrapper';
 
    // Insert the wrapper before the viewer element
    viewerElement.parentNode.insertBefore(wrapper, viewerElement);
 
    // Move the viewer element into the wrapper and add the side panel
    wrapper.appendChild(sidePanel);
    wrapper.appendChild(viewerElement);
 
    // Add the viewer-with-panel class to the viewer element
    viewerElement.classList.add('viewer-with-panel');
    console.log('Side panel created successfully');
  }

  // Function to add content to the side panel
  addPanelContent(content) {
    const panelContent = document.querySelector('.side-panel-content');
    if (panelContent) {
      const contentDiv = document.createElement('div');
      contentDiv.className = 'panel-section';
      contentDiv.innerHTML = content;
      panelContent.appendChild(contentDiv);
    }
  }

  // Setup event handlers for the UI elements
  static updateUIElements() {
    const pageInput = document.getElementById('input-page-number');
    const pageCountLabel = document.getElementById('page-count-label');
    const pageTextContent = document.getElementById('page-text-content');
    const pageAnnotationsContent = document.getElementById('page-annotations-content');
    const annotationsLabel = document.getElementById('annotations-label');
 
    // Function to update page count display
    const updatePageCount = () => {
      const totalPages = window.pageCount || 0;
      pageCountLabel.textContent = `of ${totalPages} full page text`;
    };
    // Function to update content displays
    const updateContent = (pageNumber) => {
      // Update annotations label
      annotationsLabel.textContent = `Page ${pageNumber} text under annotations:`;
      
      // Call the global function to get text
      if (window.getAllTextFromDocument) {
        window.getAllTextFromDocument(pageNumber);
      }
    };    
 
    // Function to immediately update UI content from global variables
    const updateUIContent = () => {
      if (window.textContent !== undefined) {
        pageTextContent.value = window.textContent || 'No text found on this page.';
      }
      if (window.annotTextContent !== undefined) {
        pageAnnotationsContent.value = window.annotTextContent || 'No annotation text found on this page.';
      }
    };
    
    // Expose the UI update function globally
    window.updateUIContent = updateUIContent;
 
    // Function to update page input from window.currentPage
    const updatePageInput = () => {
      console.log('Updating page input. Current page:', window.currentPage);
      if (window.currentPage !== undefined && pageInput.value != window.currentPage) {
        console.log('Page input changed, updating to:', window.currentPage);
        pageInput.value = window.currentPage;
        updateContent(window.currentPage);
      }
    };

    // Monitor for page count updates and current page changes
    const checkPageCount = () => {
      updatePageCount();
      updatePageInput();
      if (window.pageCount > 0) {
        pageInput.max = window.pageCount;
      }
    };

    // Update content for the current page
    updatePageCount();
    updatePageInput();
    updateContent(window.currentPage);
  }
}

window.updateUIElements = UIElements.updateUIElements; //Make it globally accessible

```

{% endcode %}
{% endtab %}

{% tab title="index.css" %}
{% code title="index.css" lineNumbers="true" %}

```css
/* Main layout - side by side containers within #viewer */
#viewer {
  display: flex;
  height: 100%;
  width: 100%;
}

/* Side Panel Styles */
.viewer-wrapper {
  display: flex;
  height: 100vh;
  width: 100%;
}

.side-panel {
  width: 300px;
  min-width: 250px;
  max-width: 400px;
  background-color: #f5f5f5;
  border-right: 1px solid #ddd;
  box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s ease;
  z-index: 1000;
  display: flex;
  flex-direction: column;
}

.side-panel.collapsed {
  transform: translateX(-100%);
}

.side-panel-header {
  background-color: #e9ecef;
  padding: 15px 20px;
  border-bottom: 1px solid #ddd;
  flex-shrink: 0;
}

.side-panel-header h3 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  color: #333;
}

.side-panel-content {
  flex: 1;
  padding: 20px;
  overflow-y: auto;
}

.panel-section {
  margin-bottom: 25px;
}

.panel-section h4 {
  margin: 0 0 12px 0;
  font-size: 14px;
  font-weight: 600;
  color: #555;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.panel-button {
  display: block;
  width: 100%;
  padding: 10px 15px;
  margin-bottom: 8px;
  background-color: #fff;
  border: 1px solid #ddd;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s ease;
  font-size: 14px;
}

.panel-button:hover {
  background-color: #007bff;
  color: white;
  border-color: #007bff;
}

.panel-button:active {
  transform: translateY(1px);
}

/* Text Extraction UI Styles */
#page-info {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 15px;
  flex-wrap: wrap;
}

.page-label {
  font-size: 14px;
  font-weight: 600;
  color: #333;
  white-space: nowrap;
}

.page-input {
  width: 60px;
  padding: 4px 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
  text-align: center;
}

.page-input:focus {
  outline: none;
  border-color: #007bff;
  box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.page-count-label {
  font-size: 12px;
  color: #666;
  white-space: nowrap;
}

.text-section-label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  color: #555;
  margin-bottom: 5px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.text-display {
  width: 100%;
  height: 120px;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 12px;
  font-family: 'Courier New', monospace;
  line-height: 1.4;
  resize: vertical;
  background-color: #f9f9f9;
  color: #333;
  white-space: pre-wrap;
  word-wrap: break-word;
  overflow-y: auto;
  overflow-x: hidden;
}

.text-display:focus {
  outline: none;
  border-color: #007bff;
  box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.text-display::placeholder {
  color: #999;
  font-style: italic;
}

#page-text-all {
  margin-bottom: 20px;
}

#page-text-annotations {
  margin-bottom: 15px;
}

.setting-item {
  margin-bottom: 15px;
}

.setting-item label {
  display: flex;
  align-items: center;
  font-size: 14px;
  color: #555;
  cursor: pointer;
}

.setting-item input[type="checkbox"] {
  margin-right: 10px;
}

.setting-item input[type="range"] {
  margin-left: 10px;
  flex: 1;
}

.viewer-with-panel {
  flex: 1;
  height: 100vh;
}

/* Dark mode styles */
@media (prefers-color-scheme: dark) {
  .side-panel {
    background-color: #2d3748;
    border-right-color: #4a5568;
  }
  
  .side-panel-header {
    background-color: #1a202c;
    border-bottom-color: #4a5568;
  }
  
  .side-panel-header h3 {
    color: #e2e8f0;
  }
  
  .panel-section h4 {
    color: #a0aec0;
  }
  
  .panel-button {
    background-color: #4a5568;
    border-color: #718096;
    color: #e2e8f0;
  }
  
  .panel-button:hover {
    background-color: #007bff;
    border-color: #007bff;
  }
  
  .setting-item label {
    color: #a0aec0;
  }
  
  /* Text extraction dark mode styles */
  .page-label {
    color: #e2e8f0;
  }
  
  .page-input {
    background-color: #4a5568;
    border-color: #718096;
    color: #e2e8f0;
  }
  
  .page-input:focus {
    border-color: #007bff;
    box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.4);
  }
  
  .page-count-label {
    color: #a0aec0;
  }
  
  .text-section-label {
    color: #a0aec0;
  }
  
  .text-display {
    background-color: #4a5568;
    border-color: #718096;
    color: #e2e8f0;
  }
  
  .text-display:focus {
    border-color: #007bff;
    box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.4);
  }
  
  .text-display::placeholder {
    color: #718096;
  }
}

/* Responsive design */
@media (max-width: 768px) {
  .side-panel {
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    z-index: 1001;
  }
  
  .viewer-wrapper {
    position: relative;
  }
  
  .side-panel.collapsed {
    transform: translateX(-100%);
  }
}

/* Toggle button for mobile */
.side-panel-toggle {
  position: fixed;
  top: 20px;
  left: 20px;
  z-index: 1002;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 10px;
  cursor: pointer;
  display: none;
}

@media (max-width: 768px) {
  .side-panel-toggle {
    display: block;
  }
  
  /* Text extraction responsive styles */
  #page-info {
    flex-direction: column;
    align-items: flex-start;
    gap: 5px;
  }
  
  .page-input {
    width: 80px;
  }
  
  .text-display {
    height: 100px;
    font-size: 11px;
  }
  
  .page-count-label {
    font-size: 11px;
  }
}

/* Theme Switch Styles */
.theme-switch-container {
  display: flex;
  justify-content: center;
  margin-top: 15px;
}

.theme-switch {
  position: relative;
  display: flex;
  background-color: #e9ecef;
  border-radius: 25px;
  padding: 4px;
  border: 2px solid #dee2e6;
  width: 200px;
  height: 50px;
  overflow: hidden;
}

.theme-switch input[type="radio"] {
  display: none;
}

.switch-option {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  position: relative;
  z-index: 2;
  transition: color 0.3s ease;
  padding: 5px;
}

.switch-option.left {
  border-radius: 20px 0 0 20px;
}

.switch-option.right {
  border-radius: 0 20px 20px 0;
}

.switch-icon {
  font-size: 16px;
  margin-bottom: 2px;
}

.switch-label {
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.switch-slider {
  position: absolute;
  top: 4px;
  left: 4px;
  width: calc(50% - 4px);
  height: calc(100% - 8px);
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 20px;
  transition: transform 0.3s ease, background 0.3s ease;
  z-index: 1;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

/* Dark mode selected */
#dark-mode:checked ~ .switch-slider {
  transform: translateX(0);
  background: linear-gradient(135deg, #2c3e50 0%, #5a5c5e 100%);
}

/* Light mode selected */
#light-mode:checked ~ .switch-slider {
  transform: translateX(100%);
  background: linear-gradient(135deg, #e4ce85 0%, #e9ca1d 100%);
}

/* Text color changes */
#dark-mode:checked ~ .switch-option.left {
  color: white;
}

#light-mode:checked ~ .switch-option.right {
  color: white;
}

.switch-option {
  color: #6c757d;
}

/* Dark mode theme styles */
@media (prefers-color-scheme: dark) {
  .theme-switch {
    background-color: #4a5568;
    border-color: #718096;
  }
  
  .switch-option {
    color: #a0aec0;
  }
  
  #dark-mode:checked ~ .switch-option.left {
    color: white;
  }
  
  #light-mode:checked ~ .switch-option.right {
    color: white;
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/get-started/samples/showcase-demo-pdf-text-extractor.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
