const DraggableUtils = { boxDragContainer: document.getElementById("box-drag-container"), pdfCanvas: document.getElementById("pdf-canvas"), nextId: 0, pdfDoc: null, pageIndex: 0, documentsMap: new Map(), init() { interact(".draggable-canvas") .draggable({ listeners: { move: (event) => { const target = event.target; const x = (parseFloat(target.getAttribute("data-bs-x")) || 0) + event.dx; const y = (parseFloat(target.getAttribute("data-bs-y")) || 0) + event.dy; target.style.transform = `translate(${x}px, ${y}px)`; target.setAttribute("data-bs-x", x); target.setAttribute("data-bs-y", y); this.onInteraction(target); }, }, }) .resizable({ edges: { left: true, right: true, bottom: true, top: true }, listeners: { move: (event) => { var target = event.target; var x = parseFloat(target.getAttribute("data-bs-x")) || 0; var y = parseFloat(target.getAttribute("data-bs-y")) || 0; // check if control key is pressed if (event.ctrlKey) { const aspectRatio = target.offsetWidth / target.offsetHeight; // preserve aspect ratio let width = event.rect.width; let height = event.rect.height; if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) { height = width / aspectRatio; } else { width = height * aspectRatio; } event.rect.width = width; event.rect.height = height; } target.style.width = event.rect.width + "px"; target.style.height = event.rect.height + "px"; // translate when resizing from top or left edges x += event.deltaRect.left; y += event.deltaRect.top; target.style.transform = "translate(" + x + "px," + y + "px)"; target.setAttribute("data-bs-x", x); target.setAttribute("data-bs-y", y); target.textContent = Math.round(event.rect.width) + "\u00D7" + Math.round(event.rect.height); this.onInteraction(target); }, }, modifiers: [ interact.modifiers.restrictSize({ min: { width: 5, height: 5 }, }), ], inertia: true, }); }, onInteraction(target) { this.boxDragContainer.appendChild(target); }, createDraggableCanvas() { const createdCanvas = document.createElement("canvas"); createdCanvas.id = `draggable-canvas-${this.nextId++}`; createdCanvas.classList.add("draggable-canvas"); const x = 0; const y = 20; createdCanvas.style.transform = `translate(${x}px, ${y}px)`; createdCanvas.setAttribute("data-bs-x", x); createdCanvas.setAttribute("data-bs-y", y); createdCanvas.onclick = (e) => this.onInteraction(e.target); this.boxDragContainer.appendChild(createdCanvas); return createdCanvas; }, createDraggableCanvasFromUrl(dataUrl) { return new Promise((resolve) => { var myImage = new Image(); myImage.src = dataUrl; myImage.onload = () => { var createdCanvas = this.createDraggableCanvas(); createdCanvas.width = myImage.width; createdCanvas.height = myImage.height; const imgAspect = myImage.width / myImage.height; const pdfAspect = this.boxDragContainer.offsetWidth / this.boxDragContainer.offsetHeight; var scaleMultiplier; if (imgAspect > pdfAspect) { scaleMultiplier = this.boxDragContainer.offsetWidth / myImage.width; } else { scaleMultiplier = this.boxDragContainer.offsetHeight / myImage.height; } var newWidth = createdCanvas.width; var newHeight = createdCanvas.height; if (scaleMultiplier < 1) { newWidth = newWidth * scaleMultiplier; newHeight = newHeight * scaleMultiplier; } createdCanvas.style.width = newWidth + "px"; createdCanvas.style.height = newHeight + "px"; var myContext = createdCanvas.getContext("2d"); myContext.drawImage(myImage, 0, 0); resolve(createdCanvas); }; }); }, deleteAllDraggableCanvases() { this.boxDragContainer.querySelectorAll(".draggable-canvas").forEach((el) => el.remove()); }, deleteDraggableCanvas(element) { if (element) { element.remove(); } }, getLastInteracted() { return this.boxDragContainer.querySelector(".draggable-canvas:last-of-type"); }, storePageContents() { var pagesMap = this.documentsMap.get(this.pdfDoc); if (!pagesMap) { pagesMap = {}; } const elements = [...this.boxDragContainer.querySelectorAll(".draggable-canvas")]; const draggablesData = elements.map((el) => { return { element: el, offsetWidth: el.offsetWidth, offsetHeight: el.offsetHeight, }; }); elements.forEach((el) => this.boxDragContainer.removeChild(el)); pagesMap[this.pageIndex] = draggablesData; pagesMap[this.pageIndex + "-offsetWidth"] = this.pdfCanvas.offsetWidth; pagesMap[this.pageIndex + "-offsetHeight"] = this.pdfCanvas.offsetHeight; this.documentsMap.set(this.pdfDoc, pagesMap); }, loadPageContents() { var pagesMap = this.documentsMap.get(this.pdfDoc); this.deleteAllDraggableCanvases(); if (!pagesMap) { return; } const draggablesData = pagesMap[this.pageIndex]; if (draggablesData) { draggablesData.forEach((draggableData) => this.boxDragContainer.appendChild(draggableData.element)); } this.documentsMap.set(this.pdfDoc, pagesMap); }, async renderPage(pdfDocument, pageIdx) { this.pdfDoc = pdfDocument ? pdfDocument : this.pdfDoc; this.pageIndex = pageIdx; // persist const page = await this.pdfDoc.getPage(this.pageIndex + 1); // set the canvas size to the size of the page if (page.rotate == 90 || page.rotate == 270) { this.pdfCanvas.width = page.view[3]; this.pdfCanvas.height = page.view[2]; } else { this.pdfCanvas.width = page.view[2]; this.pdfCanvas.height = page.view[3]; } // render the page onto the canvas var renderContext = { canvasContext: this.pdfCanvas.getContext("2d"), viewport: page.getViewport({ scale: 1 }), }; await page.render(renderContext).promise; //return pdfCanvas.toDataURL(); }, async incrementPage() { if (this.pageIndex < this.pdfDoc.numPages - 1) { this.storePageContents(); await this.renderPage(this.pdfDoc, this.pageIndex + 1); this.loadPageContents(); } }, async decrementPage() { if (this.pageIndex > 0) { this.storePageContents(); await this.renderPage(this.pdfDoc, this.pageIndex - 1); this.loadPageContents(); } }, parseTransform(element) {}, async getOverlayedPdfDocument() { const pdfBytes = await this.pdfDoc.getData(); const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, { ignoreEncryption: true, }); this.storePageContents(); const pagesMap = this.documentsMap.get(this.pdfDoc); for (let pageIdx in pagesMap) { if (pageIdx.includes("offset")) { continue; } console.log(typeof pageIdx); const page = pdfDocModified.getPage(parseInt(pageIdx)); const draggablesData = pagesMap[pageIdx]; const offsetWidth = pagesMap[pageIdx + "-offsetWidth"]; const offsetHeight = pagesMap[pageIdx + "-offsetHeight"]; for (const draggableData of draggablesData) { // embed the draggable canvas const draggableElement = draggableData.element; const response = await fetch(draggableElement.toDataURL()); const draggableImgBytes = await response.arrayBuffer(); const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes); // calculate the position in the pdf document const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, ""); const transformComponents = tansform.split(","); const draggablePositionPixels = { x: parseFloat(transformComponents[0]), y: parseFloat(transformComponents[1]), width: draggableData.offsetWidth, height: draggableData.offsetHeight, }; const draggablePositionRelative = { x: draggablePositionPixels.x / offsetWidth, y: draggablePositionPixels.y / offsetHeight, width: draggablePositionPixels.width / offsetWidth, height: draggablePositionPixels.height / offsetHeight, }; const draggablePositionPdf = { x: draggablePositionRelative.x * page.getWidth(), y: draggablePositionRelative.y * page.getHeight(), width: draggablePositionRelative.width * page.getWidth(), height: draggablePositionRelative.height * page.getHeight(), }; // draw the image page.drawImage(pdfImageObject, { x: draggablePositionPdf.x, y: page.getHeight() - draggablePositionPdf.y - draggablePositionPdf.height, width: draggablePositionPdf.width, height: draggablePositionPdf.height, }); } } this.loadPageContents(); return pdfDocModified; }, }; document.addEventListener("DOMContentLoaded", () => { DraggableUtils.init(); });