Add text layer support for PDF rendering and selection
Integrated PDF.js to enable text selection and copy-paste functionality in the PDF viewer. Updated `EnvelopeViewer.razor` to include the necessary scripts and styles, and modified the HTML structure to add a text layer container. Enhanced `envelope-viewer.css` with styles for the text layer and optimized canvas rendering. Added a `renderTextLayer` method in `pdf-viewer.js` to extract and render text content from PDF pages. Updated the rendering process to overlay the text layer on the canvas.
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||||
<link href="css/envelope-viewer.css" rel="stylesheet" />
|
<link href="css/envelope-viewer.css" rel="stylesheet" />
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf_viewer.min.css" rel="stylesheet" />
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
||||||
<script src="js/pdf-viewer.js"></script>
|
<script src="js/pdf-viewer.js"></script>
|
||||||
|
|
||||||
@@ -151,7 +152,10 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="pdf-canvas-wrapper">
|
<div class="pdf-canvas-wrapper">
|
||||||
<canvas id="pdf-canvas" class="pdf-canvas"></canvas>
|
<div class="pdf-page-container">
|
||||||
|
<canvas id="pdf-canvas" class="pdf-canvas"></canvas>
|
||||||
|
<div id="pdf-text-layer" class="pdf-text-layer"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -434,9 +434,14 @@ body.resizing {
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pdf-canvas {
|
.pdf-page-container {
|
||||||
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-canvas {
|
||||||
|
display: block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
image-rendering: -webkit-optimize-contrast;
|
image-rendering: -webkit-optimize-contrast;
|
||||||
image-rendering: crisp-edges;
|
image-rendering: crisp-edges;
|
||||||
@@ -447,6 +452,34 @@ body.resizing {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pdf-text-layer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 1;
|
||||||
|
line-height: 1.0;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-text-layer > span {
|
||||||
|
color: transparent;
|
||||||
|
position: absolute;
|
||||||
|
white-space: pre;
|
||||||
|
cursor: text;
|
||||||
|
transform-origin: 0% 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-text-layer ::selection {
|
||||||
|
background: rgba(126, 34, 206, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-text-layer ::-moz-selection {
|
||||||
|
background: rgba(126, 34, 206, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
.error-container {
|
.error-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -176,6 +176,9 @@ window.pdfViewer = {
|
|||||||
this.currentRenderTask = page.render(renderContext);
|
this.currentRenderTask = page.render(renderContext);
|
||||||
await this.currentRenderTask.promise;
|
await this.currentRenderTask.promise;
|
||||||
|
|
||||||
|
// Render text layer for copy-paste functionality
|
||||||
|
await this.renderTextLayer(page, viewport, dpr);
|
||||||
|
|
||||||
// Restore viewport center position AFTER rendering
|
// Restore viewport center position AFTER rendering
|
||||||
if (container && oldWidth > 0 && oldHeight > 0) {
|
if (container && oldWidth > 0 && oldHeight > 0) {
|
||||||
const scaleX = this.canvas.width / oldWidth;
|
const scaleX = this.canvas.width / oldWidth;
|
||||||
@@ -210,6 +213,41 @@ window.pdfViewer = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async renderTextLayer(page, viewport, dpr) {
|
||||||
|
try {
|
||||||
|
const textLayerDiv = document.getElementById('pdf-text-layer');
|
||||||
|
if (!textLayerDiv) {
|
||||||
|
console.warn('Text layer div not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear previous text layer
|
||||||
|
textLayerDiv.innerHTML = '';
|
||||||
|
|
||||||
|
// Set text layer dimensions to match canvas display size
|
||||||
|
textLayerDiv.style.width = `${viewport.width / dpr}px`;
|
||||||
|
textLayerDiv.style.height = `${viewport.height / dpr}px`;
|
||||||
|
|
||||||
|
// Get text content from PDF
|
||||||
|
const textContent = await page.getTextContent();
|
||||||
|
|
||||||
|
// Create viewport for text layer (without DPR scaling for positioning)
|
||||||
|
const textViewport = page.getViewport({ scale: this.scale });
|
||||||
|
|
||||||
|
// Render text layer using PDF.js built-in function
|
||||||
|
const pdfjsLib = window.pdfjsLib;
|
||||||
|
await pdfjsLib.renderTextLayer({
|
||||||
|
textContentSource: textContent,
|
||||||
|
container: textLayerDiv,
|
||||||
|
viewport: textViewport,
|
||||||
|
textDivs: []
|
||||||
|
}).promise;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error rendering text layer:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
queueRenderPage(num) {
|
queueRenderPage(num) {
|
||||||
if (this.pageRendering) {
|
if (this.pageRendering) {
|
||||||
this.pageNumPending = num;
|
this.pageNumPending = num;
|
||||||
|
|||||||
Reference in New Issue
Block a user