// PDF Viewer instance
let viewer;
// Stores search results returned by the Search API
let searchResults = [];
// Overlay container for custom highlights
let highlightLayer = null;
// Entry point
window.onload = async function () {
// DsPdfViewer.LicenseKey = "***key***";
viewer = new DsPdfViewer('#viewer', {
restoreViewStateOnLoad: false
});
viewer.addDefaultPanels();
const pdfUrl =
"/document-solutions/javascript-pdf-viewer/demos/product-bundles/assets/pdf/wetlands.pdf";
// Open document and fit width
await viewer.open(pdfUrl);
viewer.zoom = { mode: 1 };
createHighlightLayer();
updateLayerTransform();
await performSearch("wetlands");
renderHighlights();
// Re-render highlights when layout changes
viewer.scrollView.addEventListener("scroll", renderHighlights);
viewer.scrollView.addEventListener("pointerdown", renderHighlights);
viewer.scrollView.addEventListener("pointerup", () => setTimeout(renderHighlights));
window.addEventListener("resize", () => {
updateLayerTransform();
setTimeout(renderHighlights);
});
};
/**
* Performs text search and stores results.
*/
async function performSearch(text) {
searchResults = [];
const iterator = await viewer.searcher.search({
Text: text,
MatchCase: true
});
let result = await iterator.next();
while (!result.done) {
searchResults.push(result.value);
result = await iterator.next();
}
}
/**
* Creates a dedicated overlay layer inside the document view.
*/
function createHighlightLayer() {
if (highlightLayer)
return;
const documentView =
viewer.hostElement.querySelector(".gcv-document-view");
highlightLayer = document.createElement("div");
highlightLayer.className = "highlight-layer";
documentView.appendChild(highlightLayer);
}
/**
* Aligns overlay coordinate system with window coordinates.
* convertPdfRectToWindowCoordinates() returns window-based values,
* so we compensate by translating the entire layer.
*/
function updateLayerTransform() {
const documentView =
viewer.hostElement.querySelector(".gcv-document-view");
if (!documentView || !highlightLayer)
return;
const rect = documentView.getBoundingClientRect();
highlightLayer.style.transform =
`translate(${-rect.left}px, ${-rect.top}px)`;
}
/**
* Renders all highlight rectangles.
*/
function renderHighlights() {
clearHighlights();
for (const item of searchResults) {
const rect =
viewer.convertPdfRectToWindowCoordinates(
item.PageIndex,
item.ItemArea
);
addHighlight(rect, item.DisplayText);
}
}
/**
* Creates a single highlight rectangle.
*/
function addHighlight(rect, tooltipText) {
const highlight = document.createElement("div");
highlight.className = "highlight-box";
highlight.title = `Match: ${tooltipText}`;
const [x1, y1, x2, y2] = rect;
highlight.style.left = `${x1}px`;
highlight.style.top = `${y1}px`;
highlight.style.width = `${x2 - x1}px`;
highlight.style.height = `${y2 - y1}px`;
highlightLayer.appendChild(highlight);
}
/**
* Removes all highlights.
*/
function clearHighlights() {
if (!highlightLayer)
return;
highlightLayer.innerHTML = "";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Using Search API from Code</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./src/styles.css">
<script src="/document-solutions/javascript-pdf-viewer/demos/product-bundles/build/dspdfviewer.js"></script>
<script src="/document-solutions/javascript-pdf-viewer/demos/product-bundles/build/wasmSupportApi.js"></script>
<script src="/document-solutions/javascript-pdf-viewer/demos/resource/js/init.js"></script>
<script src="./src/app.js"></script>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
</head>
<body>
<div id="viewer"></div>
</body>
</html>
#viewer {
height: 100%;
}
/* Overlay layer */
.highlight-layer {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 5;
transform: translate3d(0, 0, 0);
}
/* Highlight rectangle container with border */
.highlight-box {
position: absolute;
pointer-events: none;
background-color: orange; /* fill */
border: 1px solid rgb(219, 127, 6); /* border */
opacity: 0.2;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
}
/* Animated wave inside highlight */
.highlight-box::after {
content: "";
position: absolute;
inset: 0;
background: yellow;
clip-path: inset(0 75% 0 0);
animation: wave-animation 1s infinite ease-in-out;
opacity: 0.7;
z-index: 0;
}
/* Wave animation keyframes */
@keyframes wave-animation {
0% { clip-path: inset(0 75% 0 0); }
100% { clip-path: inset(0 0 0 75%); }
}
body {
overflow: hidden;
}