Add image support to multi-tool page (#1769)

* Add image support to multi-tool page

Related to #278

* changes to support image types

* final touches

---------

Co-authored-by: a <a>
This commit is contained in:
Anthony Stirling 2024-08-29 11:07:31 +02:00 committed by GitHub
parent 316021453f
commit 68bc4d82de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 140 additions and 22 deletions

View File

@ -1,4 +1,4 @@
class ImageHiglighter {
class ImageHighlighter {
imageHighlighter;
constructor(id) {
this.imageHighlighter = document.getElementById(id);
@ -41,6 +41,25 @@ class ImageHiglighter {
img.addEventListener("click", this.imageHighlightCallback);
return div;
}
async addImageFile(file, nextSiblingElement) {
const div = document.createElement("div");
div.classList.add("page-container");
var img = document.createElement("img");
img.classList.add("page-image");
img.src = URL.createObjectURL(file);
div.appendChild(img);
this.pdfAdapters.forEach((adapter) => {
adapter.adapt?.(div);
});
if (nextSiblingElement) {
this.pagesContainer.insertBefore(div, nextSiblingElement);
} else {
this.pagesContainer.appendChild(div);
}
}
}
export default ImageHiglighter;
export default ImageHighlighter;

View File

@ -10,27 +10,28 @@ class PdfContainer {
this.pagesContainerWrapper = document.getElementById(wrapperId);
this.downloadLink = null;
this.movePageTo = this.movePageTo.bind(this);
this.addPdfs = this.addPdfs.bind(this);
this.addPdfsFromFiles = this.addPdfsFromFiles.bind(this);
this.addFiles = this.addFiles.bind(this);
this.addFilesFromFiles = this.addFilesFromFiles.bind(this);
this.rotateElement = this.rotateElement.bind(this);
this.rotateAll = this.rotateAll.bind(this);
this.exportPdf = this.exportPdf.bind(this);
this.updateFilename = this.updateFilename.bind(this);
this.setDownloadAttribute = this.setDownloadAttribute.bind(this);
this.preventIllegalChars = this.preventIllegalChars.bind(this);
this.addImageFile = this.addImageFile.bind(this);
this.pdfAdapters = pdfAdapters;
this.pdfAdapters.forEach((adapter) => {
adapter.setActions({
movePageTo: this.movePageTo,
addPdfs: this.addPdfs,
addFiles: this.addFiles,
rotateElement: this.rotateElement,
updateFilename: this.updateFilename,
});
});
window.addPdfs = this.addPdfs;
window.addFiles = this.addFiles;
window.exportPdf = this.exportPdf;
window.rotateAll = this.rotateAll;
@ -65,25 +66,30 @@ class PdfContainer {
}
}
addPdfs(nextSiblingElement) {
addFiles(nextSiblingElement) {
var input = document.createElement("input");
input.type = "file";
input.multiple = true;
input.setAttribute("accept", "application/pdf");
input.setAttribute("accept", "application/pdf,image/*");
input.onchange = async (e) => {
const files = e.target.files;
this.addPdfsFromFiles(files, nextSiblingElement);
this.addFilesFromFiles(files, nextSiblingElement);
this.updateFilename(files ? files[0].name : "");
};
input.click();
}
async addPdfsFromFiles(files, nextSiblingElement) {
async addFilesFromFiles(files, nextSiblingElement) {
this.fileName = files[0].name;
for (var i = 0; i < files.length; i++) {
await this.addPdfFile(files[i], nextSiblingElement);
const file = files[i];
if (file.type === "application/pdf") {
await this.addPdfFile(file, nextSiblingElement);
} else if (file.type.startsWith("image/")) {
await this.addImageFile(file, nextSiblingElement);
}
}
document.querySelectorAll(".enable-on-file").forEach((element) => {
@ -130,6 +136,25 @@ class PdfContainer {
}
}
async addImageFile(file, nextSiblingElement) {
const div = document.createElement("div");
div.classList.add("page-container");
var img = document.createElement("img");
img.classList.add("page-image");
img.src = URL.createObjectURL(file);
div.appendChild(img);
this.pdfAdapters.forEach((adapter) => {
adapter.adapt?.(div);
});
if (nextSiblingElement) {
this.pagesContainer.insertBefore(div, nextSiblingElement);
} else {
this.pagesContainer.appendChild(div);
}
}
async loadFile(file) {
var objectUrl = URL.createObjectURL(file);
var pdfDocument = await this.toPdfLib(objectUrl);
@ -193,16 +218,47 @@ class PdfContainer {
for (var i = 0; i < pageContainers.length; i++) {
const img = pageContainers[i].querySelector("img"); // Find the img element within each .page-container
if (!img) continue;
const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx]);
const page = pages[0];
let page;
if (img.doc) {
const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx]);
page = pages[0];
pdfDoc.addPage(page);
} else {
page = pdfDoc.addPage([img.naturalWidth, img.naturalHeight]);
const imageBytes = await fetch(img.src).then((res) => res.arrayBuffer());
const uint8Array = new Uint8Array(imageBytes);
const imageType = detectImageType(uint8Array);
let image;
switch (imageType) {
case 'PNG':
image = await pdfDoc.embedPng(imageBytes);
break;
case 'JPEG':
image = await pdfDoc.embedJpg(imageBytes);
break;
case 'TIFF':
image = await pdfDoc.embedTiff(imageBytes);
break;
case 'GIF':
console.warn(`Unsupported image type: ${imageType}`);
continue; // Skip this image
default:
console.warn(`Unsupported image type: ${imageType}`);
continue; // Skip this image
}
page.drawImage(image, {
x: 0,
y: 0,
width: img.naturalWidth,
height: img.naturalHeight,
});
}
const rotation = img.style.rotate;
if (rotation) {
const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, ""));
page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle));
}
pdfDoc.addPage(page);
}
const pdfBytes = await pdfDoc.save();
const pdfBlob = new Blob([pdfBytes], { type: "application/pdf" });
@ -249,6 +305,7 @@ class PdfContainer {
}
}
setDownloadAttribute() {
this.downloadLink.setAttribute("download", this.fileName ? this.fileName : "managed.pdf");
}
@ -280,5 +337,28 @@ class PdfContainer {
// }
}
}
function detectImageType(uint8Array) {
// Check for PNG signature
if (uint8Array[0] === 137 && uint8Array[1] === 80 && uint8Array[2] === 78 && uint8Array[3] === 71) {
return 'PNG';
}
// Check for JPEG signature
if (uint8Array[0] === 255 && uint8Array[1] === 216 && uint8Array[2] === 255) {
return 'JPEG';
}
// Check for TIFF signature (little-endian and big-endian)
if ((uint8Array[0] === 73 && uint8Array[1] === 73 && uint8Array[2] === 42 && uint8Array[3] === 0) ||
(uint8Array[0] === 77 && uint8Array[1] === 77 && uint8Array[2] === 0 && uint8Array[3] === 42)) {
return 'TIFF';
}
// Check for GIF signature
if (uint8Array[0] === 71 && uint8Array[1] === 73 && uint8Array[2] === 70) {
return 'GIF';
}
return 'UNKNOWN';
}
export default PdfContainer;

View File

@ -90,6 +90,25 @@ class FileDragManager {
this.updateFilename(files ? files[0].name : "");
});
}
async addImageFile(file, nextSiblingElement) {
const div = document.createElement("div");
div.classList.add("page-container");
var img = document.createElement("img");
img.classList.add("page-image");
img.src = URL.createObjectURL(file);
div.appendChild(img);
this.pdfAdapters.forEach((adapter) => {
adapter.adapt?.(div);
});
if (nextSiblingElement) {
this.pagesContainer.insertBefore(div, nextSiblingElement);
} else {
this.pagesContainer.appendChild(div);
}
}
}
export default FileDragManager;

View File

@ -27,7 +27,7 @@
th:placeholder="#{multiTool.uploadPrompts}">
</div>
<div class="mt-action-btn">
<button class="btn btn-primary" onclick="addPdfs()">
<button class="btn btn-primary" onclick="addFiles()">
<span class="material-symbols-rounded">
add
</span>
@ -52,12 +52,12 @@
<div class="multi-tool-container">
<div class="d-flex flex-wrap" id="pages-container-wrapper">
<div id="pages-container">
<div class="page-container" th:each="pdf, status: ${pdfList}"
<div class="page-container" th:each="file, status: ${fileList}"
th:id="'page-container-' + ${status.index}">
<div class="page-number-container">
<span th:text="${status.index + 1}"></span>
</div>
<img th:src="${pdf.imageUrl}" alt="PDF Page">
<img th:src="${file.imageUrl}" alt="File Page">
</div>
</div>
</div>
@ -82,14 +82,14 @@
const dragDropManager = new DragDropManager('drag-container', 'pages-container');
// enables image highlight on click
const imageHighlighter = new ImageHighlighter('image-highlighter');
// enables the default action buttons on each pdf
// enables the default action buttons on each file
const pdfActionsManager = new PdfActionsManager('pages-container');
const fileDragManager = new FileDragManager();
// Scroll the wrapper horizontally
scrollDivHorizontally('pages-container-wrapper');
// Automatically exposes rotateAll, addPdfs and exportPdf to the window for the global buttons.
// Automatically exposes rotateAll, addFiles and exportPdf to the window for the global buttons.
const pdfContainer = new PdfContainer(
'pages-container',
'pages-container-wrapper',
@ -101,8 +101,8 @@
]
)
fileDragManager.setCallback(async (files) => pdfContainer.addPdfsFromFiles(files));
fileDragManager.setCallback(async (files) => pdfContainer.addFilesFromFiles(files));
</script>
</body>
</html>
</html>