I need your help guys, I am currently working on a PDF editor using pdf.js and pdf-lib, I am using the pdf.js to render the pdf on a page within my application and the pdf-lib strictly for modify and downloading the modified pdf document.
I have been able to add text to a loaded PDF with an editableContent and also able to download the pdf document with the modified content. But the problem is that the modified content on the downloaded pdf document is not properly positioned as of when it was added.
I have tried calculating the position of the editableContent on the pdf document by getting the x, y, width, and height of the pdf document by using getCropBox() function in pdf-lib but still can't get it working.
Here is the modification function in my code:
const { PDFDocument, rgb } = window.PDFLib;
let pdfBytes;
const editableContents = [];
async function loadAndDisplayPdf() {
const pdfUrl = 'software-development-proposal-template-1702352345704.pdf';
// Asynchronously download PDF as an ArrayBuffer
pdfBytes = await fetch(pdfUrl).then(response => response.arrayBuffer());
// Load PDF using pdf.js
const loadingTask = pdfjsLib.getDocument({ data: pdfBytes });
const pdf = await loadingTask.promise;
// Render the first page
const pageNumber = 1;
const page = await pdf.getPage(pageNumber);
// Get viewport
const scale = 1.5;
const viewport = page.getViewport({ scale });
// Prepare canvas
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = viewport.width;
canvas.height = viewport.height;
// Render PDF to canvas
await page.render({ canvasContext: context, viewport }).promise;
// Display the canvas
const pdfContainer = document.getElementById('pdf-container');
pdfContainer.innerHTML = '';
pdfContainer.appendChild(canvas);
// Add double-click event to canvas for adding editable content
canvas.addEventListener('dblclick', addEditableContent);
}
function addEditableContent(event) {
// Check if the double-click is on the canvas
if (event.target.tagName.toLowerCase() === 'canvas') {
// Get coordinates of double-click
const x = event.clientX + window.scrollX;
const y = event.clientY + window.scrollY;
let customFontSize = 6;
// Create an editable div
const editableContent = document.createElement('div');
editableContent.contentEditable = true;
editableContent.style.position = 'absolute'; // Keep it as 'absolute'
editableContent.style.left = `${x}px`;
editableContent.style.top = `${y}px`;
editableContent.style.fontSize = `${customFontSize}`
editableContent.innerText = 'Editable Text';
// Append the editable content to the container (same container as the canvas)
const canvasContainer = document.getElementById('pdf-container');
canvasContainer.appendChild(editableContent);
// Save the editable content and its position
editableContents.push({ element: editableContent, x, y });
// Focus on the editable content
editableContent.focus();
}
}
// Function to convert pixels to points
function pixelsToPoints(pixels, dpi = 96) {
const inchesX = pixels.x / dpi;
const inchesY = pixels.y / dpi;
const pointsX = inchesX * (72 / 1.4); // 1 inch = 72 points
const pointsY = inchesY * (72 / 1.4); // 1 inch = 72 points
return { x: pointsX, y: pointsY };
}
async function modifyAndDownloadPdf() {
if (!pdfBytes) {
console.error('PDF not loaded');
return;
}
// Use pdf-lib to modify the PDF
const pdfDoc = await PDFDocument.load(pdfBytes);
editableContents.forEach(({ element, x, y }) => {
const editText = element.innerText;
const firstPage = pdfDoc.getPages()[0];
// Dynamically set the position based on the editableContent position in points
const { x: pointsX, y: pointsY } = pixelsToPoints({ x, y });
firstPage.drawText(`${editText}`, {
x: pointsX - 85.1,
y: firstPage.getSize().height - pointsY, // Invert Y-axis for correct positioning
size: 6,
color: rgb(0.95, 0.1, 0.1),
});
});
// Save the modified PDF
const modifiedPdfBytes = await pdfDoc.save();
// Provide download link for the modified PDF
const downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(new Blob([modifiedPdfBytes], { type: 'application/pdf' }));
downloadLink.download = 'modified-pdf.pdf';
downloadLink.click();
}
const downloadBtn = document.getElementById('modifyAndDownloadPdf');
const loadPDFBtn = document.getElementById('loadAndDisplayPdf');
downloadBtn.addEventListener('click', () => {
modifyAndDownloadPdf();
});
loadPDFBtn.addEventListener('click', () => {
loadAndDisplayPdf();
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="module" src="https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.6.347/pdf.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.min.js"></script>
<title>PDF Viewer and Editor</title>
</head>
<body>
<nav style="background-color: #f1f1f1; padding: 10px; margin-bottom: 10px;">
<div class="nav-container" style="display: flex; justify-content: space-around;">
<div class="nav-left">
<div class="brand">JustDoc</div>
</div>
<div class="nav-middle">
<div class="brand">Others</div>
</div>
<div class="nav-right">
Right side
</div>
</div>
</nav>
<div class="container" style="background-color: #f2f2f2; display: flex; justify-content: space-between;">
<div class="tools" style="
height: 500px;
background-color: #ffff;
width: 150px;
padding: 10px;
margin: 10px;
border-radius: 5px;
">
My Tools
</div>
<div id="pdf-container"
style="
height: 550px;
overflow: auto;
overflow-y: scroll;
margin: 10px;
"></div>
<div class="tools" style="
height: 500px;
background-color: #ffff;
width: 150px;
padding: 10px;
margin: 10px;
border-radius: 5px;
">
<div class="title">
<h4>Editor Tool</h4>
</div>
<div class="tools-container">
<li>
<button id="loadAndDisplayPdf">Load and Display PDF</button>
</li>
<li>
<button id="modifyAndDownloadPdf">Download PDF</button>
</li>
</div>
</div>
</div>
<script type="module" src="./js/main.js"></script>
</body>
</html>