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:
parent
316021453f
commit
68bc4d82de
4 changed files with 140 additions and 22 deletions
|
@ -1,4 +1,4 @@
|
||||||
class ImageHiglighter {
|
class ImageHighlighter {
|
||||||
imageHighlighter;
|
imageHighlighter;
|
||||||
constructor(id) {
|
constructor(id) {
|
||||||
this.imageHighlighter = document.getElementById(id);
|
this.imageHighlighter = document.getElementById(id);
|
||||||
|
@ -41,6 +41,25 @@ class ImageHiglighter {
|
||||||
img.addEventListener("click", this.imageHighlightCallback);
|
img.addEventListener("click", this.imageHighlightCallback);
|
||||||
return div;
|
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;
|
||||||
|
|
|
@ -10,27 +10,28 @@ class PdfContainer {
|
||||||
this.pagesContainerWrapper = document.getElementById(wrapperId);
|
this.pagesContainerWrapper = document.getElementById(wrapperId);
|
||||||
this.downloadLink = null;
|
this.downloadLink = null;
|
||||||
this.movePageTo = this.movePageTo.bind(this);
|
this.movePageTo = this.movePageTo.bind(this);
|
||||||
this.addPdfs = this.addPdfs.bind(this);
|
this.addFiles = this.addFiles.bind(this);
|
||||||
this.addPdfsFromFiles = this.addPdfsFromFiles.bind(this);
|
this.addFilesFromFiles = this.addFilesFromFiles.bind(this);
|
||||||
this.rotateElement = this.rotateElement.bind(this);
|
this.rotateElement = this.rotateElement.bind(this);
|
||||||
this.rotateAll = this.rotateAll.bind(this);
|
this.rotateAll = this.rotateAll.bind(this);
|
||||||
this.exportPdf = this.exportPdf.bind(this);
|
this.exportPdf = this.exportPdf.bind(this);
|
||||||
this.updateFilename = this.updateFilename.bind(this);
|
this.updateFilename = this.updateFilename.bind(this);
|
||||||
this.setDownloadAttribute = this.setDownloadAttribute.bind(this);
|
this.setDownloadAttribute = this.setDownloadAttribute.bind(this);
|
||||||
this.preventIllegalChars = this.preventIllegalChars.bind(this);
|
this.preventIllegalChars = this.preventIllegalChars.bind(this);
|
||||||
|
this.addImageFile = this.addImageFile.bind(this);
|
||||||
|
|
||||||
this.pdfAdapters = pdfAdapters;
|
this.pdfAdapters = pdfAdapters;
|
||||||
|
|
||||||
this.pdfAdapters.forEach((adapter) => {
|
this.pdfAdapters.forEach((adapter) => {
|
||||||
adapter.setActions({
|
adapter.setActions({
|
||||||
movePageTo: this.movePageTo,
|
movePageTo: this.movePageTo,
|
||||||
addPdfs: this.addPdfs,
|
addFiles: this.addFiles,
|
||||||
rotateElement: this.rotateElement,
|
rotateElement: this.rotateElement,
|
||||||
updateFilename: this.updateFilename,
|
updateFilename: this.updateFilename,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addPdfs = this.addPdfs;
|
window.addFiles = this.addFiles;
|
||||||
window.exportPdf = this.exportPdf;
|
window.exportPdf = this.exportPdf;
|
||||||
window.rotateAll = this.rotateAll;
|
window.rotateAll = this.rotateAll;
|
||||||
|
|
||||||
|
@ -65,25 +66,30 @@ class PdfContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addPdfs(nextSiblingElement) {
|
addFiles(nextSiblingElement) {
|
||||||
var input = document.createElement("input");
|
var input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
input.multiple = true;
|
input.multiple = true;
|
||||||
input.setAttribute("accept", "application/pdf");
|
input.setAttribute("accept", "application/pdf,image/*");
|
||||||
input.onchange = async (e) => {
|
input.onchange = async (e) => {
|
||||||
const files = e.target.files;
|
const files = e.target.files;
|
||||||
|
|
||||||
this.addPdfsFromFiles(files, nextSiblingElement);
|
this.addFilesFromFiles(files, nextSiblingElement);
|
||||||
this.updateFilename(files ? files[0].name : "");
|
this.updateFilename(files ? files[0].name : "");
|
||||||
};
|
};
|
||||||
|
|
||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
async addPdfsFromFiles(files, nextSiblingElement) {
|
async addFilesFromFiles(files, nextSiblingElement) {
|
||||||
this.fileName = files[0].name;
|
this.fileName = files[0].name;
|
||||||
for (var i = 0; i < files.length; i++) {
|
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) => {
|
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) {
|
async loadFile(file) {
|
||||||
var objectUrl = URL.createObjectURL(file);
|
var objectUrl = URL.createObjectURL(file);
|
||||||
var pdfDocument = await this.toPdfLib(objectUrl);
|
var pdfDocument = await this.toPdfLib(objectUrl);
|
||||||
|
@ -193,16 +218,47 @@ class PdfContainer {
|
||||||
for (var i = 0; i < pageContainers.length; i++) {
|
for (var i = 0; i < pageContainers.length; i++) {
|
||||||
const img = pageContainers[i].querySelector("img"); // Find the img element within each .page-container
|
const img = pageContainers[i].querySelector("img"); // Find the img element within each .page-container
|
||||||
if (!img) continue;
|
if (!img) continue;
|
||||||
const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx]);
|
let page;
|
||||||
const page = pages[0];
|
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;
|
const rotation = img.style.rotate;
|
||||||
if (rotation) {
|
if (rotation) {
|
||||||
const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, ""));
|
const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, ""));
|
||||||
page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle));
|
page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle));
|
||||||
}
|
}
|
||||||
|
|
||||||
pdfDoc.addPage(page);
|
|
||||||
}
|
}
|
||||||
const pdfBytes = await pdfDoc.save();
|
const pdfBytes = await pdfDoc.save();
|
||||||
const pdfBlob = new Blob([pdfBytes], { type: "application/pdf" });
|
const pdfBlob = new Blob([pdfBytes], { type: "application/pdf" });
|
||||||
|
@ -249,6 +305,7 @@ class PdfContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setDownloadAttribute() {
|
setDownloadAttribute() {
|
||||||
this.downloadLink.setAttribute("download", this.fileName ? this.fileName : "managed.pdf");
|
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;
|
export default PdfContainer;
|
||||||
|
|
|
@ -90,6 +90,25 @@ class FileDragManager {
|
||||||
this.updateFilename(files ? files[0].name : "");
|
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;
|
export default FileDragManager;
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
th:placeholder="#{multiTool.uploadPrompts}">
|
th:placeholder="#{multiTool.uploadPrompts}">
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-action-btn">
|
<div class="mt-action-btn">
|
||||||
<button class="btn btn-primary" onclick="addPdfs()">
|
<button class="btn btn-primary" onclick="addFiles()">
|
||||||
<span class="material-symbols-rounded">
|
<span class="material-symbols-rounded">
|
||||||
add
|
add
|
||||||
</span>
|
</span>
|
||||||
|
@ -52,12 +52,12 @@
|
||||||
<div class="multi-tool-container">
|
<div class="multi-tool-container">
|
||||||
<div class="d-flex flex-wrap" id="pages-container-wrapper">
|
<div class="d-flex flex-wrap" id="pages-container-wrapper">
|
||||||
<div id="pages-container">
|
<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}">
|
th:id="'page-container-' + ${status.index}">
|
||||||
<div class="page-number-container">
|
<div class="page-number-container">
|
||||||
<span th:text="${status.index + 1}"></span>
|
<span th:text="${status.index + 1}"></span>
|
||||||
</div>
|
</div>
|
||||||
<img th:src="${pdf.imageUrl}" alt="PDF Page">
|
<img th:src="${file.imageUrl}" alt="File Page">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -82,14 +82,14 @@
|
||||||
const dragDropManager = new DragDropManager('drag-container', 'pages-container');
|
const dragDropManager = new DragDropManager('drag-container', 'pages-container');
|
||||||
// enables image highlight on click
|
// enables image highlight on click
|
||||||
const imageHighlighter = new ImageHighlighter('image-highlighter');
|
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 pdfActionsManager = new PdfActionsManager('pages-container');
|
||||||
const fileDragManager = new FileDragManager();
|
const fileDragManager = new FileDragManager();
|
||||||
|
|
||||||
// Scroll the wrapper horizontally
|
// Scroll the wrapper horizontally
|
||||||
scrollDivHorizontally('pages-container-wrapper');
|
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(
|
const pdfContainer = new PdfContainer(
|
||||||
'pages-container',
|
'pages-container',
|
||||||
'pages-container-wrapper',
|
'pages-container-wrapper',
|
||||||
|
@ -101,8 +101,8 @@
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
fileDragManager.setCallback(async (files) => pdfContainer.addPdfsFromFiles(files));
|
fileDragManager.setCallback(async (files) => pdfContainer.addFilesFromFiles(files));
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue