From 749461334dde443525d3ab3d48580f583cd8b987 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 22 Jul 2023 13:17:24 +0100 Subject: [PATCH] pdfjs worker changes and crop fix --- .../SPDF/controller/api/CropController.java | 2 +- .../api/converters/ConvertHtmlToPDF.java | 66 +++ src/main/resources/messages_en_GB.properties | 6 +- src/main/resources/messages_ko_KR.properties | 2 +- src/main/resources/messages_zh_CN.properties | 4 +- src/main/resources/static/css/general.css | 10 + .../static/js/multitool/PdfContainer.js | 413 +++++++------- .../templates/convert/pdf-to-text.html | 67 ++- src/main/resources/templates/crop.html | 6 +- .../resources/templates/other/add-image.html | 279 +++++----- .../templates/other/adjust-contrast.html | 67 ++- .../templates/other/change-metadata.html | 526 +++++++++--------- .../resources/templates/other/compare.html | 378 ++++++------- src/main/resources/templates/rotate-pdf.html | 262 ++++----- src/main/resources/templates/sign.html | 1 + 15 files changed, 1093 insertions(+), 996 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java diff --git a/src/main/java/stirling/software/SPDF/controller/api/CropController.java b/src/main/java/stirling/software/SPDF/controller/api/CropController.java index e56c1b40..670ef39e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/CropController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/CropController.java @@ -90,7 +90,7 @@ public class CropController { @PostMapping(value = "/crop", consumes = "multipart/form-data") @Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO") public ResponseEntity cropPdf( - @Parameter(description = "The input PDF file", required = true) @RequestParam("file") MultipartFile file, + @Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file, @Parameter(description = "The x-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("x") float x, @Parameter(description = "The y-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("y") float y, @Parameter(description = "The width of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("width") float width, diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java new file mode 100644 index 00000000..6b5ecb95 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java @@ -0,0 +1,66 @@ +package stirling.software.SPDF.controller.api.converters; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +@Tag(name = "Convert", description = "Convert APIs") +public class ConvertHtmlToPDF { + + @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa") + @Operation( + summary = "Convert a PDF to a PDF/A", + description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO" + ) + public ResponseEntity pdfToPdfA( + @RequestPart(required = true, value = "fileInput") + @Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true) + MultipartFile inputFile) throws IOException, InterruptedException { + + // Save the uploaded file to a temporary location + Path tempInputFile = Files.createTempFile("input_", ".pdf"); + inputFile.transferTo(tempInputFile.toFile()); + + // Prepare the output file path + Path tempOutputFile = Files.createTempFile("output_", ".pdf"); + + // Prepare the OCRmyPDF command + List command = new ArrayList<>(); + command.add("ocrmypdf"); + command.add("--skip-text"); + command.add("--tesseract-timeout=0"); + command.add("--output-type"); + command.add("pdfa"); + command.add(tempInputFile.toString()); + command.add(tempOutputFile.toString()); + + int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); + + // Read the optimized PDF file + byte[] pdfBytes = Files.readAllBytes(tempOutputFile); + + // Clean up the temporary files + Files.delete(tempInputFile); + Files.delete(tempOutputFile); + + // Return the optimized PDF as a response + String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf"; + return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } + +} diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 50f3f816..98ab5d03 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -126,7 +126,7 @@ home.PDFToWord.desc=Convert PDF to Word formats (DOC, DOCX and ODT) home.PDFToPresentation.title=PDF to Presentation home.PDFToPresentation.desc=Convert PDF to Presentation formats (PPT, PPTX and ODP) -home.PDFToText.title=PDF to Text/RTF +home.PDFToText.title=PDF to RTF (Text) home.PDFToText.desc=Convert PDF to Text or RTF format home.PDFToHTML.title=PDF to HTML @@ -582,8 +582,8 @@ PDFToPresentation.submit=Convert #PDFToText -PDFToText.title=PDF to Text/RTF -PDFToText.header=PDF to Text/RTF +PDFToText.title=PDF to RTF (Text) +PDFToText.header=PDF to RTF (Text) PDFToText.selectText.1=Output file format PDFToText.credit=This service uses LibreOffice for file conversion. PDFToText.submit=Convert diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index 53a65c8d..a34aca6a 100644 --- a/src/main/resources/messages_ko_KR.properties +++ b/src/main/resources/messages_ko_KR.properties @@ -594,7 +594,7 @@ PDFToPresentation.submit=변환 #PDFToText -PDFToText.title=PDF to Text/RTF +PDFToText.title=PDF to RTF (Text) PDFToText.header=PDF를 텍스트/RTF로 변환 PDFToText.selectText.1=출력 파일 형식 PDFToText.credit=이 서비스는 파일 변환을 위해 LibreOffice를 사용합니다. diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index dde5854c..f371f299 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -135,7 +135,7 @@ home.PDFToWord.desc=将PDF转换为Word格式(DOC、DOCX和ODT)。 home.PDFToPresentation.title=PDF To Presentation home.PDFToPresentation.desc=将PDF转换成演示文稿格式(PPT、PPTX和ODP)。 -home.PDFToText.title=PDF To Text/RTF +home.PDFToText.title=PDF to RTF (Text) home.PDFToText.desc=将PDF转换为文本或RTF格式 home.PDFToHTML.title=PDF To HTML @@ -594,7 +594,7 @@ PDFToPresentation.submit=转换 #PDFToText -PDFToText.title=PDF To Text/RTF +PDFToText.title=PDF to RTF (Text) PDFToText.header=将PDF转换成文本/RTF PDFToText.selectText.1=输出文件格式 PDFToText.credit=该服务使用LibreOffice进行文件转换。 diff --git a/src/main/resources/static/css/general.css b/src/main/resources/static/css/general.css index b3861577..3185a0df 100644 --- a/src/main/resources/static/css/general.css +++ b/src/main/resources/static/css/general.css @@ -69,4 +69,14 @@ html[lang-direction="rtl"] label.form-check-label { #pdf-canvas { box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384); width: 100%; +} +.fixed-shadow-canvas { + box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384); + width: 100%; +} +.shadow-canvas { + box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384); +} +.hidden { + display: none; } \ No newline at end of file diff --git a/src/main/resources/static/js/multitool/PdfContainer.js b/src/main/resources/static/js/multitool/PdfContainer.js index 2ce87cb2..bd150ac6 100644 --- a/src/main/resources/static/js/multitool/PdfContainer.js +++ b/src/main/resources/static/js/multitool/PdfContainer.js @@ -1,206 +1,207 @@ -class PdfContainer { - fileName; - pagesContainer; - pagesContainerWrapper; - pdfAdapters; - - constructor(id, wrapperId, pdfAdapters) { - this.fileName = null; - this.pagesContainer = document.getElementById(id) - this.pagesContainerWrapper = document.getElementById(wrapperId); - this.movePageTo = this.movePageTo.bind(this); - this.addPdfs = this.addPdfs.bind(this); - this.rotateElement = this.rotateElement.bind(this); - this.rotateAll = this.rotateAll.bind(this); - this.exportPdf = this.exportPdf.bind(this); - - this.pdfAdapters = pdfAdapters; - - this.pdfAdapters.forEach(adapter => { - adapter.setActions({ - movePageTo: this.movePageTo, - addPdfs: this.addPdfs, - rotateElement: this.rotateElement, - }) - }) - - window.addPdfs = this.addPdfs; - window.exportPdf = this.exportPdf; - window.rotateAll = this.rotateAll; - } - - movePageTo(startElement, endElement, scrollTo = false) { - const childArray = Array.from(this.pagesContainer.childNodes); - const startIndex = childArray.indexOf(startElement); - const endIndex = childArray.indexOf(endElement); - this.pagesContainer.removeChild(startElement); - if(!endElement) { - this.pagesContainer.append(startElement); - } else { - this.pagesContainer.insertBefore(startElement, endElement); - } - - if(scrollTo) { - const { width } = startElement.getBoundingClientRect(); - const vector = (endIndex !== -1 && startIndex > endIndex) - ? 0-width - : width; - - this.pagesContainerWrapper.scroll({ - left: this.pagesContainerWrapper.scrollLeft + vector, - }) - } - } - - addPdfs(nextSiblingElement) { - var input = document.createElement('input'); - input.type = 'file'; - input.multiple = true; - input.setAttribute("accept", "application/pdf"); - - input.onchange = async(e) => { - const files = e.target.files; - this.fileName = files[0].name; - for (var i=0; i < files.length; i++) { - await this.addPdfFile(files[i], nextSiblingElement); - } - - document.querySelectorAll(".enable-on-file").forEach(element => { - element.disabled = false; - }); - } - - input.click(); - } - - rotateElement(element, deg) { - var lastTransform = element.style.rotate; - if (!lastTransform) { - lastTransform = "0"; - } - const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, '')); - const newAngle = lastAngle + deg; - - element.style.rotate = newAngle + "deg"; - } - - async addPdfFile(file, nextSiblingElement) { - const { renderer, pdfDocument } = await this.loadFile(file); - - for (var i=0; i < renderer.pageCount; i++) { - const div = document.createElement('div'); - - div.classList.add("page-container"); - - var img = document.createElement('img'); - img.classList.add('page-image') - const imageSrc = await renderer.renderPage(i) - img.src = imageSrc; - img.pageIdx = i; - img.rend = renderer; - img.doc = pdfDocument; - 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); - var renderer = await this.toRenderer(objectUrl); - return { renderer, pdfDocument }; - } - - async toRenderer(objectUrl) { - const pdf = await pdfjsLib.getDocument(objectUrl).promise; - return { - document: pdf, - pageCount: pdf.numPages, - renderPage: async function(pageIdx) { - const page = await this.document.getPage(pageIdx+1); - - const canvas = document.createElement("canvas"); - - // set the canvas size to the size of the page - if (page.rotate == 90 || page.rotate == 270) { - canvas.width = page.view[3]; - canvas.height = page.view[2]; - } else { - canvas.width = page.view[2]; - canvas.height = page.view[3]; - } - - // render the page onto the canvas - var renderContext = { - canvasContext: canvas.getContext("2d"), - viewport: page.getViewport({ scale: 1 }) - }; - - await page.render(renderContext).promise; - return canvas.toDataURL(); - } - }; - } - - async toPdfLib(objectUrl) { - const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer()); - const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, { ignoreEncryption: true }); - return pdfDoc; - } - - - - rotateAll(deg) { - for (var i=0; i { + adapter.setActions({ + movePageTo: this.movePageTo, + addPdfs: this.addPdfs, + rotateElement: this.rotateElement, + }) + }) + + window.addPdfs = this.addPdfs; + window.exportPdf = this.exportPdf; + window.rotateAll = this.rotateAll; + } + + movePageTo(startElement, endElement, scrollTo = false) { + const childArray = Array.from(this.pagesContainer.childNodes); + const startIndex = childArray.indexOf(startElement); + const endIndex = childArray.indexOf(endElement); + this.pagesContainer.removeChild(startElement); + if(!endElement) { + this.pagesContainer.append(startElement); + } else { + this.pagesContainer.insertBefore(startElement, endElement); + } + + if(scrollTo) { + const { width } = startElement.getBoundingClientRect(); + const vector = (endIndex !== -1 && startIndex > endIndex) + ? 0-width + : width; + + this.pagesContainerWrapper.scroll({ + left: this.pagesContainerWrapper.scrollLeft + vector, + }) + } + } + + addPdfs(nextSiblingElement) { + var input = document.createElement('input'); + input.type = 'file'; + input.multiple = true; + input.setAttribute("accept", "application/pdf"); + + input.onchange = async(e) => { + const files = e.target.files; + this.fileName = files[0].name; + for (var i=0; i < files.length; i++) { + await this.addPdfFile(files[i], nextSiblingElement); + } + + document.querySelectorAll(".enable-on-file").forEach(element => { + element.disabled = false; + }); + } + + input.click(); + } + + rotateElement(element, deg) { + var lastTransform = element.style.rotate; + if (!lastTransform) { + lastTransform = "0"; + } + const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, '')); + const newAngle = lastAngle + deg; + + element.style.rotate = newAngle + "deg"; + } + + async addPdfFile(file, nextSiblingElement) { + const { renderer, pdfDocument } = await this.loadFile(file); + + for (var i=0; i < renderer.pageCount; i++) { + const div = document.createElement('div'); + + div.classList.add("page-container"); + + var img = document.createElement('img'); + img.classList.add('page-image') + const imageSrc = await renderer.renderPage(i) + img.src = imageSrc; + img.pageIdx = i; + img.rend = renderer; + img.doc = pdfDocument; + 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); + var renderer = await this.toRenderer(objectUrl); + return { renderer, pdfDocument }; + } + + async toRenderer(objectUrl) { + pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js' + const pdf = await pdfjsLib.getDocument(objectUrl).promise; + return { + document: pdf, + pageCount: pdf.numPages, + renderPage: async function(pageIdx) { + const page = await this.document.getPage(pageIdx+1); + + const canvas = document.createElement("canvas"); + + // set the canvas size to the size of the page + if (page.rotate == 90 || page.rotate == 270) { + canvas.width = page.view[3]; + canvas.height = page.view[2]; + } else { + canvas.width = page.view[2]; + canvas.height = page.view[3]; + } + + // render the page onto the canvas + var renderContext = { + canvasContext: canvas.getContext("2d"), + viewport: page.getViewport({ scale: 1 }) + }; + + await page.render(renderContext).promise; + return canvas.toDataURL(); + } + }; + } + + async toPdfLib(objectUrl) { + const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer()); + const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, { ignoreEncryption: true }); + return pdfDoc; + } + + + + rotateAll(deg) { + for (var i=0; i - - - - - -
-
-
-

-
-
-
-

-
-
- -
- - -
-
- - -
-

-
-
-
-
-
+ + + + + + +
+
+
+

+
+
+
+

+
+
+ +
+ + +
+
+ + +
+

+
+
+
+
+
\ No newline at end of file diff --git a/src/main/resources/templates/crop.html b/src/main/resources/templates/crop.html index 7d4725be..911a8f32 100644 --- a/src/main/resources/templates/crop.html +++ b/src/main/resources/templates/crop.html @@ -22,13 +22,13 @@
- +
- - - -
-
-
-

-
-
-
-

- - -
- - -
-
-
- -
-
- - -
- - -
- - - -
- -
- - -
- -
- -
-
-
-
-
-
- + + + + + + + + + +
+
+
+

+
+
+
+

+ + +
+ + +
+
+
+ +
+
+ + +
+ + +
+ + + +
+ +
+ + +
+ +
+ +
+
+
+
+
+
+ \ No newline at end of file diff --git a/src/main/resources/templates/other/adjust-contrast.html b/src/main/resources/templates/other/adjust-contrast.html index b1c0e692..90c31df3 100644 --- a/src/main/resources/templates/other/adjust-contrast.html +++ b/src/main/resources/templates/other/adjust-contrast.html @@ -14,37 +14,50 @@

-
-

-
-

- 100% -

- +
+
+
+ +
+
+

+
+
+
+
+ + +
+
+ - - - - - -
-
-
-
-
-
- - + + + + + + +
+
+
+

+
+
+
+

+ +
+
+

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ + +
+ +
+
+
+
+
+
+ + diff --git a/src/main/resources/templates/other/compare.html b/src/main/resources/templates/other/compare.html index 042af9b7..7c06062a 100644 --- a/src/main/resources/templates/other/compare.html +++ b/src/main/resources/templates/other/compare.html @@ -1,190 +1,190 @@ - - - - - - - - -
-
-
-

-
-
-
-

- -
-
- - - -
-
-

-
-
-
-

-
-
-
- - -
-
-
-
-
+ + + + + + + + +
+
+
+

+
+
+
+

+ +
+
+ + + +
+
+

+
+
+
+

+
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/src/main/resources/templates/rotate-pdf.html b/src/main/resources/templates/rotate-pdf.html index 54bbb29c..9485efc8 100644 --- a/src/main/resources/templates/rotate-pdf.html +++ b/src/main/resources/templates/rotate-pdf.html @@ -1,132 +1,132 @@ - - - - - - - - -
-
-
-

-
-
-
-

- -
-
- - - -
- -
-
-
-
-
-
- - - - - + + + + + + + + +
+
+
+

+
+
+
+

+ +
+
+ + + +
+ +
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/sign.html b/src/main/resources/templates/sign.html index 8537ea40..3f06f5d5 100644 --- a/src/main/resources/templates/sign.html +++ b/src/main/resources/templates/sign.html @@ -47,6 +47,7 @@ select#font-select, select#font-select option { if (file) { originalFileName = file.name.replace(/\.[^/.]+$/, ""); const pdfData = await file.arrayBuffer(); + pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js' const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise; await DraggableUtils.renderPage(pdfDoc, 0);