> 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-demos-key-value-extraction.md).

# Key-Value Extraction Showcase Demo Code Sample

Extract key-value pairs from PDFs, convert them into JSON, and display annotations that highlight each paired element

{% 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="/core/get-started/get-started.md" class="button primary">Server SDK</a><a href="https://apryse.com/capabilities#SmartDataExtraction" class="button primary">Package: Smart Data Extraction</a><a href="https://showcase.apryse.com/key-value-extraction" class="button primary">Live demo</a>
{% endhint %}

Quickly extract key-value pairs from PDFs, convert values into JSON for easy analysis, and display annotations that highlight each paired element.

This demo allows you to:

* Upload your own PDF file or test on sample files.
* Extract a JSON containing the key-value elements in the PDF.
* Create colorized key-value annotations for extracted paired elements.

### Implementation steps

To add key-value 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 v1.0, Claude Sonnet 4, November 13, 2025
// File: index.js

import WebViewer from '@pdftron/webviewer';

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

// Key-Value Extraction Demo
// This code demonstrates how to extract key-value pairs from documents using WebViewer
//
// **Important** 
// 1. You must get a license key from Apryse for the server to run. 
// A trial key can be obtained from:
// https://docs.apryse.com/core/guides/get-started/trial-key
//
// 2. You need to also run the `npm install` command at /key-value-extraction/server/ location to install the `@pdftron/pdfnet-node` and `@pdftron/data-extraction` packages.

function initializeWebViewer() {

    // This code initializes the WebViewer with the basic settings
    WebViewer({
        path: '/lib',
        licenseKey: licenseKey,
        enableFilePicker: true,
    }, document.getElementById('viewer')).then((instance) => {

        // Add the demo-specific functionality
        customizeUI(instance).then(() => {
            // Create UI controls after demo is initialized
            UIElements.createUIControls(instance);
        });
    });
}

// Starting page for extraction
let startPage = 1;

// Global variable to hold result data
globalThis.resultData = null;

// Custom File factory function to hold file metadata (not to be confused with browser's File API)
function FileMetadata(options) {
    return {
        name: options.name,
        displayName: options.displayName,
        path: options.path,
        extension: options.extension,
        displayExtension: options.displayExtension,
        id: options.id,
    };
}

const files = {
    DRIVERS_LICENSE: FileMetadata({
        name: 'sample-license.pdf',
        displayName: 'Driver\'s License',
        path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/sample-license.pdf',
        extension: 'pdf',
        id: 100
    }),
    SALES_INVOICE: FileMetadata({
        name: 'sales-invoice.pdf',
        displayName: 'Sales Invoice',
        path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/sales-invoice.pdf',
        extension: 'pdf',
        id: 14
    })
}

const sampleDocuments = [
    files.DRIVERS_LICENSE,
    files.SALES_INVOICE
];
globalThis.sampleDocuments = sampleDocuments;

const defaultFile = sampleDocuments[1].path; // SALES_INVOICE

const customizeUI = async (instance) => {
    // Customize the UI for the key value extraction demo
    instance.UI.setToolbarGroup('toolbarGroup-View');
    instance.UI.disableElements(['thumbnailControl']);

    // Reset variables when new document is loaded
    instance.Core.documentViewer.addEventListener('documentLoaded', async () => {
        globalThis.resultData = null;
        startPage = 1;

        // Reset the JSON display area and Color Legend
        UIElements.resetUI(instance);
    });

    // Load the default file for demonstration
    if (defaultFile) {
        instance.UI.loadDocument(defaultFile);
    }
};

// Function to extract key-value pairs from title block via server
const extractKeyValuePairs = async (instance) => {
    const doc = instance.Core.documentViewer.getDocument();
    if (doc) {
        const pdfBuffer = await doc.getFileData({ flags: instance.Core.SaveOptions.LINEARIZED });
        console.log('Sending PDF to server for key-value extraction...');
        const pdfBlob = new Blob([pdfBuffer], { type: 'application/pdf' });
        const formData = new FormData();
        formData.append('pdffile', pdfBlob, 'viewerDocument.pdf');

        // Send the PDF to the server to extract key-value pairs
        const postResponse = await fetch('http://localhost:5050/server/handler.js/extract-key-value-pairs', {
            method: 'POST',
            body: formData,
        });

        if (postResponse.status !== 200) {
            throw new Error(`Server error during PDF upload: ${postResponse.status}`);
        }

        // Retrieve and parse the JSON response
        const jsonResponse = await postResponse.json();
        const docStructureData = JSON.parse(jsonResponse);
        globalThis.resultData = JSON.stringify(docStructureData, null, 2);

        // Draw annotations on the document based on extracted data
        drawAnnotations(docStructureData, instance);
    }
}

globalThis.extractKeyValuePairs = extractKeyValuePairs; // Make extractKeyValuePairs globally available so that the UIElements module can access it

// Function to draw annotations on the document based on extracted key-value data
const drawAnnotations = (docStructureData, instance) => {
    const { annotationManager, Annotations } = instance.Core;

    // Retrieve the first page's data
    const page = docStructureData.pages[startPage - 1];
    const pageNumber = page?.properties?.pageNumber;
    console.log(`Processing Page ${pageNumber} for annotations...`);
    for (const kv of page.keyValueElements ?? []) {
        const valueRect = kv?.rect;
        const keyRect = kv?.key?.rect;
        const hasValueWords = (kv?.words?.length ?? 0) > 0;

        // Only draw if value has words
        if (!hasValueWords) {
            console.log('Skipping annotation for key-value pair with no value words.');
            continue;
        }
        // value: blue
        const valueAnnot = new Annotations.RectangleAnnotation({
            PageNumber: pageNumber,
            X: valueRect[0],
            Y: valueRect[1],
            Width: valueRect[2] - valueRect[0],
            Height: valueRect[3] - valueRect[1],
            StrokeColor: new Annotations.Color(0, 0, 255),
            StrokeThickness: 1,
        });
        annotationManager.addAnnotation(valueAnnot);
        annotationManager.redrawAnnotation(valueAnnot);

        // key: red
        const keyAnnot = new Annotations.RectangleAnnotation({
            PageNumber: pageNumber,
            X: keyRect[0],
            Y: keyRect[1],
            Width: keyRect[2] - keyRect[0],
            Height: keyRect[3] - keyRect[1],
            StrokeColor: new Annotations.Color(255, 0, 0),
            StrokeThickness: 1,
        });
        annotationManager.addAnnotation(keyAnnot);
        annotationManager.redrawAnnotation(keyAnnot);

        // Green connector
        const line = new Annotations.LineAnnotation();
        line.pageNumber = pageNumber;
        line.StrokeColor = new Annotations.Color(0, 255, 0);
        line.StrokeThickness = 1;
        line.Start = topLeftPoint(valueRect, instance);
        line.End = topLeftPoint(keyRect, instance);
        annotationManager.addAnnotation(line);
        annotationManager.redrawAnnotation(line);
    }
};

// Helper function to get top-left point of a rectangle
const topLeftPoint = ([x1, y1, x2, y2], instance) => {
    return new instance.Core.Math.Point(Math.min(x1, x2), Math.min(y1, y2));
};

// Cleanup function for when the demo is closed or page is unloaded
const cleanup = (instance) => {
    if (instance !== undefined &#x26;&#x26; instance.UI) {
        if (instance.Core.documentViewer.getDocument()) {
            // Insert any other cleanup code here
        }
        console.log('Cleaning up title-block-data-extraction demo');
    }
};

// Register cleanup for page unload
globalThis.addEventListener('beforeunload', () => cleanup(instance));
globalThis.addEventListener('unload', () => cleanup(instance));

// Helper function to load the ui-elements.js script
function loadUIElementsScript() {
    return new Promise((resolve, reject) => {
        if (globalThis.UIElements) {
            console.log('UIElements already loaded');
            resolve();
            return;
        }
        const script = document.createElement('script');
        script.src = '/showcase-demos/key-value-extraction/client/ui-elements.js';
        script.onload = function () {
            console.log('✅ UIElements script loaded successfully');
            resolve();
        };
        script.onerror = function () {
            console.error('Failed to load UIElements script');
            reject(new Error('Failed to load ui-elements.js'));
        };
        document.head.appendChild(script);
    });
}

// Load UIElements script first, then initialize WebViewer
loadUIElementsScript().then(() => {
    initializeWebViewer();
}).catch((error) => {
    console.error('Failed to load UIElements:', error);
});

</code></pre>

{% endtab %}

{% tab title="handler.js" %}

<pre class="language-js" data-line-numbers><code class="lang-js">// ES6 Compliant Syntax
// GitHub Copilot v1.0, Claude Sonnet 4, November 13, 2025
// File: handler.js
// This file will handle key-value extraction requests.

const fs = require('node:fs');
const { PDFNet } = require('@pdftron/pdfnet-node');

// **Important** 
// 1. You must get a license key from Apryse for the server to run. 
// A trial key can be obtained from:
// https://docs.apryse.com/core/guides/get-started/trial-key 
// 
// 2. You need to also run the `npm install` command at /key-value-extraction/server/ location to install the `@pdftron/pdfnet-node` and `@pdftron/data-extraction` packages.
const licenseKey = '<code class="expression">visitor.claims.wvKey || "YOUR_SERVER_LICENSE_KEY"</code>';
const multer = require('multer');
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'sentFiles/')
  },
  filename: function (req, file, cb) {
    // Save with original filename and extension
    cb(null, file.originalname)
  }
});
const upload = multer({ storage: storage });
const { response } = require('express');
const e = require('express');
const serverFolder = 'server';
const sentFiles = 'sentFiles';
const serverHandler = `/${serverFolder}/handler.js`;

const handler = async (app) => {

  // Function to initialize PDFNet and check for module availability
  async function initializePDFNet() {
    // Create folder sentFiles that will hold the sent files, if it doesn't exist
    if (!fs.existsSync(sentFiles))
      fs.mkdirSync(sentFiles);  

    // Initialize PDFNet
    await PDFNet.initialize(licenseKey);

    // Specify the Data Extraction library path
    await PDFNet.addResourceSearchPath('./node_modules/@pdftron/data-extraction/lib/');

    // Check if the Apryse SDK Data Extraction module is available.
    if (await PDFNet.DataExtractionModule.isModuleAvailable(PDFNet.DataExtractionModule.DataExtractionEngine.e_GenericKeyValue))
      console.log('Apryse SDK Data Extraction module is available.');
    else
      console.log('Unable to run: Apryse SDK Data Extraction module not available.');
  }

  // Handle POST request sent to '/server/handler.js/extract-key-value-pairs'
  // This endpoint receives the PDF file path, extracts key-value data from the title block, and returns it as JSON
  app.post(`${serverHandler}/extract-key-value-pairs`, upload.single('pdffile'), async (request, response) => {
    try {
      console.log('Received PDF for key-value extraction');
      const pdfPath = request.file.path;
      const jsonResponse = await extractKeyValuePairs(pdfPath);
      response.status(200).json(jsonResponse);
    } catch (error) {
      console.error('Error extracting key-value data:', error);
      response.status(500).send('Error extracting key-value data');
    } finally {
      // Cleanup: remove the sent PDF file
      const pdfPath = request.file.path;
      fs.unlink(pdfPath, (err) => {
        if (err) {
          console.error(`Error removing PDF file ${pdfPath}: ${err.message}`);
        }
      });
    }
  });

  // Function to extract key-value pairs from PDF using Data Extraction module
  const extractKeyValuePairs = async (pdf) => {
    try {
      // Set up data extraction options
      const options = new PDFNet.DataExtractionModule.DataExtractionOptions();
      console.log('Setting extraction language to English');
      options.setLanguage('eng');
      options.setPages('1-1'); // Extract from first page only

      // Extract key-value data from the PDF using the provided JSON template
      const jsonString = await PDFNet.DataExtractionModule.extractDataAsString(pdf, PDFNet.DataExtractionModule.DataExtractionEngine.e_GenericKeyValue, options);
      return jsonString;
      
    } catch (err) {
      console.log(err);
      throw new Error(err);
    }
  };

  // Initialize PDFNet
  PDFNet.runWithoutCleanup(initializePDFNet, licenseKey).then(
    function onFulfilled() {
      response.status(200);
    },
    function onRejected(error) {
      // log error and close response
      console.error('Error initializing PDFNet', error);
      response.status(503).send();
    }
  );
};

module.exports = handler;

</code></pre>

{% endtab %}

{% tab title="server.js" %}
{% code title="server.js" lineNumbers="true" %}

```js
// ES6 Compliant Syntax
// GitHub Copilot v1.0, Claude Sonnet 4, November 13, 2025
// File: server.js
// This file is to run a server in localhost.

const express = require('express');
const fs = require('node:fs');
const bodyParser = require('body-parser');
const handler = require('./handler.js');
const port = process.env.PORT || 5050;
const app = express();
const sentPdfs = 'sentPdfs';

// CORS middleware to allow cross-origin requests from the playground
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
  
  // Handle preflight OPTIONS requests
  if (req.method === 'OPTIONS') {
    res.sendStatus(200);
  } else {
    next();
  }
});

app.use(bodyParser.text());
app.use('/client', express.static('../client')); // For statically serving 'client' folder at '/'

handler(app);

// Run server
const server = app.listen(port, 'localhost', (err) => {
  if (err) {
    console.error(err);
  } else {
    console.info(`Server is listening at http://localhost:${port}`);
  }
});

// Server shutdown and cleanup
function shutdown() {
  console.log('Cleanup started...');

  // Example: Close server
  server.close(() => {
    console.log('Server closed.');

    // Removes sent PDFs folder
    if (fs.existsSync(sentPdfs))
      fs.rmdirSync(sentPdfs, { recursive: true });

    // If no async cleanup, exit directly
    process.exit(0);
  });
}

// Handle shutdown signals
process.on('SIGINT', shutdown);   // Ctrl+C
process.on('SIGTERM', shutdown);  // kill command or Docker stop
process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err);
  shutdown();
});
```

{% endcode %}
{% endtab %}

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

```js
// ES6 Compliant Syntax
// GitHub Copilot v1.0, Claude Sonnet 4, November 13, 2025
// File: ui-elements.js
// UI Elements class to create and manage custom UI controls
// Helper code to add controls to the viewer holding the buttons

class UIElements {
    // Function to check if the user is on a mobile device
    static isMobileDevice = () => {
        // Use modern User-Agent Client Hints API if available (Chrome 90+, Edge 90+)
        if (globalThis.navigator.userAgentData) {
            return globalThis.navigator.userAgentData.mobile === true;
        }
        // Fallback: combine touch-point detection with a concise UA check
        const isTouchDevice = globalThis.navigator.maxTouchPoints > 0;
        const mobileUA = /android|iphone|ipad|ipod|blackberry|windows phone/i.test(globalThis.navigator.userAgent);
        return isTouchDevice && mobileUA;
    }

    // JSON Code Block Element
    static jsonElement = () => {
        const wrapper = document.createElement('div');
        wrapper.className = 'json-wrapper';
        wrapper.style.display = 'none'; // Initially hidden

        // Container for the JSON code block
        const container = document.createElement('div');
        container.className = 'json-container';

        // Code block for JSON
        const codePre = document.createElement('pre');
        codePre.className = 'json-pre';

        const codeBlock = document.createElement('code');
        codeBlock.id = 'json-code';
        codeBlock.contentEditable = false;

        // Assemble the JSON code block
        codePre.appendChild(codeBlock);
        container.appendChild(codePre);
        wrapper.appendChild(container);
        return wrapper;
    };

    // Extract Key Value Pairs button
    static extractKeyValuePairsButton = (instance) => {
        // Spinner element to indicate loading
        const spinner = document.createElement('div');
        spinner.className = 'spinner';

        // Button element for extracting key-value pairs
        const button = document.createElement('button');
        button.className = 'btn extract-btn';
        button.textContent = 'Extract Key-Value Pairs';
        button.onclick = async () => {
            try {
                button.disabled = true;
                spinner.style.display = 'inline-block';
                await globalThis.extractKeyValuePairs(instance);
            } catch (e) {
                console.error(e);
                button.disabled = false;
            }
            finally {
                // Hide spinner when done
                spinner.style.display = 'none';

                // Display the extracted JSON data
                const jsonWrapper = document.querySelector('.json-wrapper');
                const jsonCodeBlock = document.getElementById('json-code');
                if (globalThis.resultData) {
                    jsonCodeBlock.textContent = JSON.stringify(JSON.parse(globalThis.resultData), null, 2);
                    jsonWrapper.style.display = 'flex';
                } else {
                    jsonCodeBlock.textContent = '';
                    jsonWrapper.style.display = 'none';
                }

                // Show color legend if extraction was successful
                const legendContainer = document.querySelector('.legend-container');
                if (globalThis.resultData) {
                    legendContainer.style.display = 'flex';
                } else {
                    legendContainer.style.display = 'none';
                }
            }
        };

        button.appendChild(spinner);

        return button;
    }

    // Legends for the Annotations
    static colorLegend = () => {
        const legendContainer = document.createElement('div');
        legendContainer.className = 'legend-container';

        const colors = ['rgb(255, 0, 0)', 'rgb(0, 0, 255)', 'rgb(0, 255, 0)'];
        const labels = ['Key', 'Value', 'Connector'];

        for (let i = 0; i < colors.length; i++) {
            const legendItem = document.createElement('div');
            legendItem.className = 'legend-item';

            const colorBox = document.createElement('span');
            colorBox.className = 'color-box';
            colorBox.style.backgroundColor = colors[i];

            const label = document.createElement('span');
            label.textContent = labels[i];

            legendItem.appendChild(colorBox);
            legendItem.appendChild(label);
            legendContainer.appendChild(legendItem);
        }

        return legendContainer;
    }

    // Reset JSON Code Block and Legend
    static resetUI = () => {
        // Hide JSON code block
        const jsonWrapper = document.querySelector('.json-wrapper');
        const jsonCodeBlock = document.getElementById('json-code');
        jsonCodeBlock.textContent = '';
        jsonWrapper.style.display = 'none';

        // Hide legend
        const legendContainer = document.querySelector('.legend-container');
        legendContainer.style.display = 'none';

        // Enable Extract button
        const extractButton = document.querySelector('.extract-btn');
        extractButton.disabled = false;
    }


    // Gallery Picker Element
    static galleryPicker = (instance) => {
        const galleryGrid = document.createElement('div');
        galleryGrid.className = 'gallery-grid';

        const sampleDocuments = globalThis.sampleDocuments;
        const filesArray = [
            { name: sampleDocuments[0].displayName, thumbnail: '/showcase-demos/key-value-extraction/client/gallery/thumbs/sample-license.png', url: sampleDocuments[0].path },
            { name: sampleDocuments[1].displayName, thumbnail: '/showcase-demos/key-value-extraction/client/gallery/thumbs/sales-invoice.png' , url: sampleDocuments[1].path }
        ];

        // Render the grid of thumbnails
        for (const file of filesArray) {
            const thumbDiv = document.createElement('div');
            thumbDiv.className = 'gallery-thumb';
            thumbDiv.title = file.name;
            thumbDiv.tabIndex = 0;
            thumbDiv.setAttribute('role', 'button');
            thumbDiv.setAttribute('aria-label', file.name);
            thumbDiv.onclick = () => handleClick(file);
            thumbDiv.onkeydown = (e) => {
                if (e.key === 'Enter' || e.key === ' ') {
                    handleClick(file);
                }
            };

            const img = document.createElement('img');
            img.src = file.thumbnail;
            img.alt = file.displayName;
            img.className = 'gallery-thumb-img';
            thumbDiv.appendChild(img);

            const label = document.createElement('div');
            label.className = 'gallery-thumb-label';
            label.textContent = `${file.name}`;
            label.onclick = (e) => {
                e.stopPropagation();
                fetch(file.url).then(response => {
                    if (!response.ok) {
                        throw new Error('Could not fetch file: ' + response.statusText);
                    }
                    return response.blob();
                }).then(blob => {
                    const url = globalThis.URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.style.display = 'none';
                    a.href = url;
                    a.download = file.name;
                    document.body.appendChild(a);
                    a.click();
                    globalThis.URL.revokeObjectURL(url);
                    a.remove();
                }).catch(error => {
                    console.error('There was a problem with the fetch operation:', error);
                });
            };
            thumbDiv.appendChild(label);

            galleryGrid.appendChild(thumbDiv);
        }

        const handleClick = (file) => {
            instance.UI.loadDocument(file.url, { filename: file.name, extension: file.extension });
        };

        return galleryGrid;
    };

    static createUIControls = (instance) => {
        // Create a container for all controls
        const controlsContainer = document.createElement('div');
        controlsContainer.className = 'button-container';

        // Add the extract button and the legend to the controls container
        controlsContainer.appendChild(this.extractKeyValuePairsButton(instance));
        controlsContainer.appendChild(this.colorLegend());

        // Insert the JSON code block and controls container into the playground viewer element (top bar)
        const playgroundViewerElement = document.getElementById('playground-viewer');
        playgroundViewerElement.insertBefore(this.jsonElement(), playgroundViewerElement.firstChild);
        playgroundViewerElement.insertBefore(controlsContainer, playgroundViewerElement.firstChild);

        // Create a gallery container
        const galleryContainer = document.createElement('div');
        galleryContainer.className = 'gallery-container';
        galleryContainer.appendChild(this.galleryPicker(instance));

        // Add the gallery container to the viewer element (left side)
        const viewerElement = document.getElementById('viewer');
        viewerElement.style.display = 'flex';
        viewerElement.style.flexDirection = 'row';
        viewerElement.insertBefore(galleryContainer, viewerElement.firstChild);
    };
}
```

{% endcode %}
{% endtab %}

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

```css
/* CSS standards Compliant Syntax */
/* GitHub Copilot v1.0, Claude Sonnet 4, October 22, 2025 */
/* File: index.css */

/* Button Styles */
.btn {
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #0056b3;
    margin: 10px;
    padding: 5px 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    font-weight: bold;
    transition: all 0.2s ease;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    color: white;
    width: 240px;
}

.btn:hover {
    background-color: #003d80;
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}

.btn:active {
    transform: translateY(1px);
    box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}

.btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    box-shadow: none;
    background-color: #E7EBEE;
    color: #5A6268;
}

.btn .spinner {
    display: none;
    border-top: 2px solid currentColor;
    border-right: 2px solid currentColor;
    border-bottom-style: solid;
    border-left-style: solid;
    border-radius: 99999px;
    border-bottom-width: 2px;
    border-left-width: 2px;
    border-bottom-color: transparent;
    border-left-color: transparent;
    animation: rotateBorder 0.45s linear 0s infinite;
    width: 1em;
    height: 1em;    
}

@keyframes rotateBorder {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(365deg);
    }
}

/* Button Container */
.button-container {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 15px;
    margin: 5px 0;
    padding: 16px;
    padding-bottom: 5px;
    border-bottom: 1px solid #DFE1E6;
    background-color: rgba(112, 198, 255, 0.2);
}

/* JSON Display Container */
.json-pre {
    height: 90%;
    font-family: monospace;
    white-space: pre-wrap;
    display: block;
    overflow: scroll;
    background-color: #f1f3f5;
}

.json-wrapper {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
}

.json-container {
    display: flex;
    min-height: 140px;
    max-height: 200px;
    width: 100%;
    max-width: 100%;
    border-radius: 2px;
    border: 1px solid rgba(0, 0, 0, 0.12);
    overflow-y: auto;
    overflow-x: auto;
    flex-grow: 1;
    position: relative;
    padding-bottom: 2px;
    background-color: rgb(244, 245, 247);
    box-sizing: border-box;
}

.json-container .json-pre {
    font-family: monospace;
    white-space: pre-wrap;
    width: 100%;
    max-width: 100%;
    margin: 0;
    padding: 8px;
    box-sizing: border-box;
    overflow-wrap: break-word;
}

#json-code {
    width: 100%;
    max-width: 100%;
    display: block;
    box-sizing: border-box;
    background: transparent;
    border: none;
    outline: none;
    resize: none;
    font-family: inherit;
}

/* Legend Container */
.legend-container {
    display: none;
    flex-direction: row;
    gap: 10px;
}

.legend-item {
    display: flex;
    align-items: center;
    gap: 5px;
}

.color-box {
    display: inline-block;
    width: 16px;
    height: 16px;
    border: 1px solid #ccc;
    border-radius: 3px;
}

/* Gallery Container on the left */
.gallery-container {
    width: 300px;
    max-width: 300px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    border-right: 1px solid #e0e0e0;
    background-color: rgba(112, 198, 255, 0.2);
    transition: margin 0.25s ease-in-out, opacity 0.25s ease-in-out;
    height: 100%;
}

/* Gallery Picker Styles */
.gallery-grid {
    display: grid;
    margin: 8px;
    margin-left: 8px;
}

.gallery-thumb {
    display: flex;
    align-items: center;
    flex-direction: column;
    margin-top: 10px;
    cursor: pointer;
}

.gallery-thumb-img {
    border-radius: 6px;
    height: 140px;
    max-width: 235px;
    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1), 0 3px 5px 0 rgba(0, 0, 0, 0.1);
    transition: transform 0.1s ease;
}

.gallery-thumb-img:hover,
.gallery-thumb-img:focus {
    transform: scale(1.05);
    border: 1px solid;
    border-color: #0206a8;
}

.gallery-thumb-label {
    font-size: 0.875rem;
    line-height: 20px;
    color: #0206A8;
    letter-spacing: -0.3px;
}

/* Responsive Design */
@media (max-width: 768px) {
    #viewer {
        flex-direction: column;
    }

    .gallery-container {
        width: 150px;
        height: auto;
        border-right: none;
        border-bottom: 1px solid #e0e0e0;
        align-items: center;
    }
}


```

{% endcode %}
{% endtab %}

{% tab title="package.json" %}
{% code title="package.json" lineNumbers="true" %}

```json
{
  "name": "cad-viewer-server",
  "version": "1.0.0",
  "description": "CAD Viewer Demo Server Component",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "node server.js"
  },
  "dependencies": {
    "@pdftron/data-extraction": "^11.8.0",
    "@pdftron/pdfnet-node": "^11.8.0",
    "body-parser": "^1.20.2",
    "express": "^4.18.2",
    "multer": "^1.4.4",
    "open": "^9.1.0"
  },
  "keywords": [
    "pdf",
    "server",
    "pdftron",
    "webviewer"
  ],
  "author": "Apryse",
  "license": "MIT"
}

```

{% 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-demos-key-value-extraction.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.
