From 78d3fd376891b0165bfa18aea258390c0f4bb9fd Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 22 Apr 2023 12:51:01 +0100 Subject: [PATCH] format and move everything, other in own folder --- .../software/SPDF/LibreOfficeListener.java | 76 +++--- .../software/SPDF/SPdfApplication.java | 2 +- .../software/SPDF/config/AppConfig.java | 51 ++-- .../stirling/software/SPDF/config/Beans.java | 26 +- .../SPDF/controller/MergeController.java | 32 +-- .../SPDF/controller/MultiToolController.java | 12 +- .../SPDF/controller/OCRController.java | 180 ------------- .../SPDF/controller/PdfController.java | 10 +- .../RearrangePagesPDFController.java | 24 +- .../SPDF/controller/RotationController.java | 12 +- .../SPDF/controller/SplitPDFController.java | 17 +- .../converters/ConvertImgPDFController.java | 48 ++-- .../converters/ConvertOfficeController.java | 85 +++--- .../converters/ConvertPDFToOffice.java | 71 +++-- .../converters/ConvertPDFToPDFA.java | 21 +- .../{ => other}/CompressController.java | 19 +- .../{ => other}/ExtractImagesController.java | 54 ++-- .../MetadataController.java | 26 +- .../SPDF/controller/other/OCRController.java | 168 ++++++++++++ .../{ => other}/OverlayImageController.java | 4 +- .../security/PasswordController.java | 28 +- .../security/WatermarkController.java | 51 ++-- .../software/SPDF/utils/ErrorUtils.java | 2 +- .../software/SPDF/utils/PDFToFile.java | 18 +- .../software/SPDF/utils/PdfUtils.java | 253 +++++++++--------- .../software/SPDF/utils/ProcessExecutor.java | 154 ++++++----- .../software/SPDF/utils/WatermarkRemover.java | 8 +- .../resources/templates/fragments/navbar.html | 10 +- .../templates/{ => other}/add-image.html | 0 .../{security => other}/change-metadata.html | 0 .../templates/{ => other}/compress-pdf.html | 0 .../templates/{ => other}/extract-images.html | 0 .../templates/{ => other}/ocr-pdf.html | 3 +- 33 files changed, 702 insertions(+), 763 deletions(-) delete mode 100644 src/main/java/stirling/software/SPDF/controller/OCRController.java rename src/main/java/stirling/software/SPDF/controller/{ => other}/CompressController.java (85%) rename src/main/java/stirling/software/SPDF/controller/{ => other}/ExtractImagesController.java (84%) rename src/main/java/stirling/software/SPDF/controller/{security => other}/MetadataController.java (94%) create mode 100644 src/main/java/stirling/software/SPDF/controller/other/OCRController.java rename src/main/java/stirling/software/SPDF/controller/{ => other}/OverlayImageController.java (92%) rename src/main/resources/templates/{ => other}/add-image.html (100%) rename src/main/resources/templates/{security => other}/change-metadata.html (100%) rename src/main/resources/templates/{ => other}/compress-pdf.html (100%) rename src/main/resources/templates/{ => other}/extract-images.html (100%) rename src/main/resources/templates/{ => other}/ocr-pdf.html (96%) diff --git a/src/main/java/stirling/software/SPDF/LibreOfficeListener.java b/src/main/java/stirling/software/SPDF/LibreOfficeListener.java index d2aff904..7f4fc160 100644 --- a/src/main/java/stirling/software/SPDF/LibreOfficeListener.java +++ b/src/main/java/stirling/software/SPDF/LibreOfficeListener.java @@ -1,4 +1,5 @@ package stirling.software.SPDF; + import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; @@ -6,32 +7,46 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class LibreOfficeListener { - - private static final LibreOfficeListener INSTANCE = new LibreOfficeListener(); - + private static final long ACTIVITY_TIMEOUT = 20 * 60 * 1000; // 20 minutes + + private static final LibreOfficeListener INSTANCE = new LibreOfficeListener(); private static final int LISTENER_PORT = 2002; - - private ExecutorService executorService; - private Process process; - private long lastActivityTime; - - private LibreOfficeListener() {} - + public static LibreOfficeListener getInstance() { return INSTANCE; } - + + private ExecutorService executorService; + private long lastActivityTime; + + private Process process; + + private LibreOfficeListener() { + } + + private boolean isListenerRunning() { + try { + System.out.println("waiting for listener to start"); + Socket socket = new Socket(); + socket.connect(new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second + socket.close(); + return true; + } catch (IOException e) { + return false; + } + } + public void start() throws IOException { // Check if the listener is already running if (process != null && process.isAlive()) { return; } - + // Start the listener process process = Runtime.getRuntime().exec("unoconv --listener"); lastActivityTime = System.currentTimeMillis(); - + // Start a background thread to monitor the activity timeout executorService = Executors.newSingleThreadExecutor(); executorService.submit(() -> { @@ -49,46 +64,33 @@ public class LibreOfficeListener { } } }); - - - // Wait for the listener to start up + + // Wait for the listener to start up long startTime = System.currentTimeMillis(); long timeout = 30000; // Timeout after 30 seconds while (System.currentTimeMillis() - startTime < timeout) { if (isListenerRunning()) { - + lastActivityTime = System.currentTimeMillis(); return; } try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } // Check every 1 second + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } // Check every 1 second } } - - private boolean isListenerRunning() { - try { - System.out.println("waiting for listener to start"); - Socket socket = new Socket(); - socket.connect(new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second - socket.close(); - return true; - } catch (IOException e) { - return false; - } - } - + public synchronized void stop() { // Stop the activity timeout monitor thread executorService.shutdownNow(); - + // Stop the listener process if (process != null && process.isAlive()) { process.destroy(); } } - + } diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index 7a0fe145..b91f3cb8 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -6,6 +6,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SPdfApplication { public static void main(String[] args) { - SpringApplication.run(SPdfApplication.class, args); + SpringApplication.run(SPdfApplication.class, args); } } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java index 3b551cea..ddffd045 100644 --- a/src/main/java/stirling/software/SPDF/config/AppConfig.java +++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java @@ -3,41 +3,40 @@ package stirling.software.SPDF.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - @Configuration -public class AppConfig { +public class AppConfig { + @Bean(name = "appName") + public String appName() { + String appName = System.getProperty("APP_HOME_NAME"); + if (appName == null) + appName = System.getenv("APP_HOME_NAME"); + return (appName != null) ? appName : "Stirling PDF"; + } + @Bean(name = "appVersion") public String appVersion() { String version = getClass().getPackage().getImplementationVersion(); return (version != null) ? version : "0.3.3"; } - - @Bean(name = "appName") - public String appName() { - String appName = System.getProperty("APP_HOME_NAME"); - if(appName == null) - appName = System.getenv("APP_HOME_NAME"); - return (appName != null) ? appName : "Stirling PDF"; - } - - @Bean(name = "navBarText") - public String navBarText() { - String navBarText = System.getProperty("APP_NAVBAR_NAME"); - if(navBarText == null) - navBarText = System.getenv("APP_NAVBAR_NAME"); - if(navBarText == null) - navBarText = System.getProperty("APP_HOME_NAME"); - if(navBarText == null) - navBarText = System.getenv("APP_HOME_NAME"); - - return (navBarText != null) ? navBarText : "Stirling PDF"; - } - + @Bean(name = "homeText") public String homeText() { String homeText = System.getProperty("APP_HOME_DESCRIPTION"); - if(homeText == null) - homeText = System.getenv("APP_HOME_DESCRIPTION"); + if (homeText == null) + homeText = System.getenv("APP_HOME_DESCRIPTION"); return (homeText != null) ? homeText : "null"; } + + @Bean(name = "navBarText") + public String navBarText() { + String navBarText = System.getProperty("APP_NAVBAR_NAME"); + if (navBarText == null) + navBarText = System.getenv("APP_NAVBAR_NAME"); + if (navBarText == null) + navBarText = System.getProperty("APP_HOME_NAME"); + if (navBarText == null) + navBarText = System.getenv("APP_HOME_NAME"); + + return (navBarText != null) ? navBarText : "Stirling PDF"; + } } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/Beans.java index ce5a12f3..a35796ea 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/Beans.java @@ -13,12 +13,24 @@ import org.springframework.web.servlet.i18n.SessionLocaleResolver; @Configuration public class Beans implements WebMvcConfigurer { + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(localeChangeInterceptor()); + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() { + LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); + lci.setParamName("lang"); + return lci; + } + @Bean public LocaleResolver localeResolver() { SessionLocaleResolver slr = new SessionLocaleResolver(); String appLocaleEnv = System.getProperty("APP_LOCALE"); - if(appLocaleEnv == null) + if (appLocaleEnv == null) appLocaleEnv = System.getenv("APP_LOCALE"); Locale defaultLocale = Locale.UK; // Fallback to UK locale if environment variable is not set @@ -37,16 +49,4 @@ public class Beans implements WebMvcConfigurer { return slr; } - @Bean - public LocaleChangeInterceptor localeChangeInterceptor() { - LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); - lci.setParamName("lang"); - return lci; - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(localeChangeInterceptor()); - } - } diff --git a/src/main/java/stirling/software/SPDF/controller/MergeController.java b/src/main/java/stirling/software/SPDF/controller/MergeController.java index b07cf668..c261c8e5 100644 --- a/src/main/java/stirling/software/SPDF/controller/MergeController.java +++ b/src/main/java/stirling/software/SPDF/controller/MergeController.java @@ -30,22 +30,6 @@ public class MergeController { return "merge-pdfs"; } - @PostMapping("/merge-pdfs") - public ResponseEntity mergePdfs(@RequestParam("fileInput") MultipartFile[] files) throws IOException { - // Read the input PDF files into PDDocument objects - List documents = new ArrayList<>(); - - // Loop through the files array and read each file into a PDDocument - for (MultipartFile file : files) { - documents.add(PDDocument.load(file.getInputStream())); - } - - PDDocument mergedDoc = mergeDocuments(documents); - - // Return the merged PDF as a response - return PdfUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_merged.pdf"); - } - private PDDocument mergeDocuments(List documents) throws IOException { // Create a new empty document PDDocument mergedDoc = new PDDocument(); @@ -64,4 +48,20 @@ public class MergeController { return mergedDoc; } + @PostMapping("/merge-pdfs") + public ResponseEntity mergePdfs(@RequestParam("fileInput") MultipartFile[] files) throws IOException { + // Read the input PDF files into PDDocument objects + List documents = new ArrayList<>(); + + // Loop through the files array and read each file into a PDDocument + for (MultipartFile file : files) { + documents.add(PDDocument.load(file.getInputStream())); + } + + PDDocument mergedDoc = mergeDocuments(documents); + + // Return the merged PDF as a response + return PdfUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf"); + } + } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/controller/MultiToolController.java b/src/main/java/stirling/software/SPDF/controller/MultiToolController.java index 4d3c1f88..cfe0ba72 100644 --- a/src/main/java/stirling/software/SPDF/controller/MultiToolController.java +++ b/src/main/java/stirling/software/SPDF/controller/MultiToolController.java @@ -9,12 +9,12 @@ import org.springframework.web.bind.annotation.GetMapping; @Controller public class MultiToolController { - private static final Logger logger = LoggerFactory.getLogger(MultiToolController.class); + private static final Logger logger = LoggerFactory.getLogger(MultiToolController.class); - @GetMapping("/multi-tool") - public String multiToolForm(Model model) { - model.addAttribute("currentPage", "multi-tool"); - return "multi-tool"; - } + @GetMapping("/multi-tool") + public String multiToolForm(Model model) { + model.addAttribute("currentPage", "multi-tool"); + return "multi-tool"; + } } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/controller/OCRController.java b/src/main/java/stirling/software/SPDF/controller/OCRController.java deleted file mode 100644 index 98f2767f..00000000 --- a/src/main/java/stirling/software/SPDF/controller/OCRController.java +++ /dev/null @@ -1,180 +0,0 @@ -package stirling.software.SPDF.controller; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.ModelAndView; - -import stirling.software.SPDF.utils.ProcessExecutor; -@Controller -public class OCRController { - - private static final Logger logger = LoggerFactory.getLogger(OCRController.class); - - @GetMapping("/ocr-pdf") - public ModelAndView ocrPdfPage() { - ModelAndView modelAndView = new ModelAndView("ocr-pdf"); - modelAndView.addObject("languages", getAvailableTesseractLanguages()); - modelAndView.addObject("currentPage", "ocr-pdf"); - return modelAndView; - } - - @PostMapping("/ocr-pdf") - public ResponseEntity processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile, - @RequestParam("languages") List selectedLanguages, - @RequestParam(name = "sidecar", required = false) Boolean sidecar, - @RequestParam(name = "deskew", required = false) Boolean deskew, - @RequestParam(name = "clean", required = false) Boolean clean, - @RequestParam(name = "clean-final", required = false) Boolean cleanFinal, - @RequestParam(name = "ocrType", required = false) String ocrType) throws IOException, InterruptedException { - - - //--output-type pdfa - if (selectedLanguages == null || selectedLanguages.size() < 1) { - throw new IOException("Please select at least one language."); - } - - // Validate and sanitize selected languages using regex - String languagePattern = "^[a-zA-Z]{3}$"; // Regex pattern for three-letter language codes - selectedLanguages = selectedLanguages.stream() - .filter(lang -> Pattern.matches(languagePattern, lang)) - .collect(Collectors.toList()); - - - if (selectedLanguages.isEmpty()) { - throw new IOException("None of the selected languages are valid."); - } - // Save the uploaded file to a temporary location - Path tempInputFile = Files.createTempFile("input_", ".pdf"); - Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); - - // Prepare the output file path - Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - - // Prepare the output file path - Path sidecarTextPath = null; - - // Run OCR Command - String languageOption = String.join("+", selectedLanguages); - - List command = new ArrayList<>(Arrays.asList("ocrmypdf","--verbose", "2", "--output-type", "pdf")); - - - if (sidecar != null && sidecar) { - sidecarTextPath = Files.createTempFile("sidecar", ".txt"); - command.add("--sidecar"); - command.add(sidecarTextPath.toString()); - } - - if (deskew != null && deskew) { - command.add("--deskew"); - } - if (clean != null && clean) { - command.add("--clean"); - } - if (cleanFinal != null && cleanFinal) { - command.add("--clean-final"); - } - if (ocrType != null && !ocrType.equals("")) { - if("skip-text".equals(ocrType)) { - command.add("--skip-text"); - } else if("force-ocr".equals(ocrType)) { - command.add("--force-ocr"); - } else if("Normal".equals(ocrType)) { - - } - } - - command.addAll(Arrays.asList("--language", languageOption, - tempInputFile.toString(), tempOutputFile.toString())); - - //Run CLI command - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); - - // Read the OCR processed PDF file - byte[] pdfBytes = Files.readAllBytes(tempOutputFile); - - - // Clean up the temporary files - Files.delete(tempInputFile); - // Return the OCR processed PDF as a response - String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf"; - - HttpHeaders headers = new HttpHeaders(); - - if (sidecar != null && sidecar) { - // Create a zip file containing both the PDF and the text file - String outputZipFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip"; - Path tempZipFile = Files.createTempFile("output_", ".zip"); - - try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { - // Add PDF file to the zip - ZipEntry pdfEntry = new ZipEntry(outputFilename); - zipOut.putNextEntry(pdfEntry); - Files.copy(tempOutputFile, zipOut); - zipOut.closeEntry(); - - // Add text file to the zip - ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt")); - zipOut.putNextEntry(txtEntry); - Files.copy(sidecarTextPath, zipOut); - zipOut.closeEntry(); - } - - byte[] zipBytes = Files.readAllBytes(tempZipFile); - - // Clean up the temporary zip file - Files.delete(tempZipFile); - Files.delete(tempOutputFile); - Files.delete(sidecarTextPath); - - // Return the zip file containing both the PDF and the text file - headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); - headers.setContentDispositionFormData("attachment", outputZipFilename); - return ResponseEntity.ok().headers(headers).body(zipBytes); - } else { - // Return the OCR processed PDF as a response - Files.delete(tempOutputFile); - headers.setContentType(MediaType.APPLICATION_PDF); - headers.setContentDispositionFormData("attachment", outputFilename); - return ResponseEntity.ok().headers(headers).body(pdfBytes); - } - - } - - public List getAvailableTesseractLanguages() { - String tessdataDir = "/usr/share/tesseract-ocr/4.00/tessdata"; - File[] files = new File(tessdataDir).listFiles(); - if (files == null) { - return Collections.emptyList(); - } - return Arrays.stream(files) - .filter(file -> file.getName().endsWith(".traineddata")) - .map(file -> file.getName().replace(".traineddata", "")) - .filter(lang -> !lang.equalsIgnoreCase("osd")) - .collect(Collectors.toList()); - } - -} diff --git a/src/main/java/stirling/software/SPDF/controller/PdfController.java b/src/main/java/stirling/software/SPDF/controller/PdfController.java index 2114c178..90dc8229 100644 --- a/src/main/java/stirling/software/SPDF/controller/PdfController.java +++ b/src/main/java/stirling/software/SPDF/controller/PdfController.java @@ -10,11 +10,6 @@ import org.springframework.web.bind.annotation.GetMapping; public class PdfController { private static final Logger logger = LoggerFactory.getLogger(PdfController.class); - - @GetMapping("/home") - public String root(Model model) { - return "redirect:/"; - } @GetMapping("/") public String home(Model model) { @@ -22,6 +17,9 @@ public class PdfController { return "home"; } + @GetMapping("/home") + public String root(Model model) { + return "redirect:/"; + } - } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/controller/RearrangePagesPDFController.java b/src/main/java/stirling/software/SPDF/controller/RearrangePagesPDFController.java index 7c107c1b..053f3e26 100644 --- a/src/main/java/stirling/software/SPDF/controller/RearrangePagesPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/RearrangePagesPDFController.java @@ -23,18 +23,6 @@ public class RearrangePagesPDFController { private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); - @GetMapping("/pdf-organizer") - public String pageOrganizer(Model model) { - model.addAttribute("currentPage", "pdf-organizer"); - return "pdf-organizer"; - } - - @GetMapping("/remove-pages") - public String pageDeleter(Model model) { - model.addAttribute("currentPage", "remove-pages"); - return "remove-pages"; - } - @PostMapping("/remove-pages") public ResponseEntity deletePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pagesToDelete") String pagesToDelete) throws IOException { @@ -53,6 +41,12 @@ public class RearrangePagesPDFController { } + @GetMapping("/remove-pages") + public String pageDeleter(Model model) { + model.addAttribute("currentPage", "remove-pages"); + return "remove-pages"; + } + private List pageOrderToString(String[] pageOrderArr, int totalPages) { List newPageOrder = new ArrayList<>(); // loop through the page order array @@ -81,6 +75,12 @@ public class RearrangePagesPDFController { return newPageOrder; } + @GetMapping("/pdf-organizer") + public String pageOrganizer(Model model) { + model.addAttribute("currentPage", "pdf-organizer"); + return "pdf-organizer"; + } + @PostMapping("/rearrange-pages") public ResponseEntity rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) { try { diff --git a/src/main/java/stirling/software/SPDF/controller/RotationController.java b/src/main/java/stirling/software/SPDF/controller/RotationController.java index cd91330f..f61fe40f 100644 --- a/src/main/java/stirling/software/SPDF/controller/RotationController.java +++ b/src/main/java/stirling/software/SPDF/controller/RotationController.java @@ -22,12 +22,6 @@ public class RotationController { private static final Logger logger = LoggerFactory.getLogger(RotationController.class); - @GetMapping("/rotate-pdf") - public String rotatePdfForm(Model model) { - model.addAttribute("currentPage", "rotate-pdf"); - return "rotate-pdf"; - } - @PostMapping("/rotate-pdf") public ResponseEntity rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("angle") Integer angle) throws IOException { @@ -45,4 +39,10 @@ public class RotationController { } + @GetMapping("/rotate-pdf") + public String rotatePdfForm(Model model) { + model.addAttribute("currentPage", "rotate-pdf"); + return "rotate-pdf"; + } + } diff --git a/src/main/java/stirling/software/SPDF/controller/SplitPDFController.java b/src/main/java/stirling/software/SPDF/controller/SplitPDFController.java index 05b00399..37d49d63 100644 --- a/src/main/java/stirling/software/SPDF/controller/SplitPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/SplitPDFController.java @@ -27,17 +27,12 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; + @Controller public class SplitPDFController { private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class); - @GetMapping("/split-pdfs") - public String splitPdfForm(Model model) { - model.addAttribute("currentPage", "split-pdfs"); - return "split-pdfs"; - } - @PostMapping("/split-pages") public ResponseEntity splitPdf(@RequestParam("fileInput") MultipartFile file, @RequestParam("pages") String pages) throws IOException { // parse user input @@ -128,7 +123,13 @@ public class SplitPDFController { Files.delete(zipFile); // return the Resource in the response - return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_split.zip").contentType(MediaType.APPLICATION_OCTET_STREAM) - .contentLength(resource.contentLength()).body(resource); + return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_split.zip") + .contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource); + } + + @GetMapping("/split-pdfs") + public String splitPdfForm(Model model) { + model.addAttribute("currentPage", "split-pdfs"); + return "split-pdfs"; } } diff --git a/src/main/java/stirling/software/SPDF/controller/converters/ConvertImgPDFController.java b/src/main/java/stirling/software/SPDF/controller/converters/ConvertImgPDFController.java index 5a57cc7d..f473b60d 100644 --- a/src/main/java/stirling/software/SPDF/controller/converters/ConvertImgPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/converters/ConvertImgPDFController.java @@ -25,28 +25,6 @@ public class ConvertImgPDFController { private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class); - @GetMapping("/img-to-pdf") - public String convertToPdfForm(Model model) { - model.addAttribute("currentPage", "img-to-pdf"); - return "convert/img-to-pdf"; - } - - @GetMapping("/pdf-to-img") - public String pdfToimgForm(Model model) { - model.addAttribute("currentPage", "pdf-to-img"); - return "convert/pdf-to-img"; - } - - @PostMapping("/img-to-pdf") - public ResponseEntity convertToPdf(@RequestParam("fileInput") MultipartFile[] file, - @RequestParam(defaultValue = "false", name = "stretchToFit") boolean stretchToFit, - @RequestParam(defaultValue = "true", name = "autoRotate") boolean autoRotate) throws IOException { - // Convert the file to PDF and get the resulting bytes - System.out.println(stretchToFit); - byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate); - return PdfUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_coverted.pdf"); - } - @PostMapping("/pdf-to-img") public ResponseEntity convertToImage(@RequestParam("fileInput") MultipartFile file, @RequestParam("imageFormat") String imageFormat, @RequestParam("singleOrMultiple") String singleOrMultiple, @RequestParam("colorType") String colorType, @RequestParam("dpi") String dpi) throws IOException { @@ -78,11 +56,27 @@ public class ConvertImgPDFController { } else { ByteArrayResource resource = new ByteArrayResource(result); // return the Resource in the response - return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="+ file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToImages.zip").contentType(MediaType.APPLICATION_OCTET_STREAM) - .contentLength(resource.contentLength()).body(resource); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToImages.zip") + .contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource); } } + @PostMapping("/img-to-pdf") + public ResponseEntity convertToPdf(@RequestParam("fileInput") MultipartFile[] file, @RequestParam(defaultValue = "false", name = "stretchToFit") boolean stretchToFit, + @RequestParam(defaultValue = "true", name = "autoRotate") boolean autoRotate) throws IOException { + // Convert the file to PDF and get the resulting bytes + System.out.println(stretchToFit); + byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate); + return PdfUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_coverted.pdf"); + } + + @GetMapping("/img-to-pdf") + public String convertToPdfForm(Model model) { + model.addAttribute("currentPage", "img-to-pdf"); + return "convert/img-to-pdf"; + } + private String getMediaType(String imageFormat) { if (imageFormat.equalsIgnoreCase("PNG")) return "image/png"; @@ -94,4 +88,10 @@ public class ConvertImgPDFController { return "application/octet-stream"; } + @GetMapping("/pdf-to-img") + public String pdfToimgForm(Model model) { + model.addAttribute("currentPage", "pdf-to-img"); + return "convert/pdf-to-img"; + } + } diff --git a/src/main/java/stirling/software/SPDF/controller/converters/ConvertOfficeController.java b/src/main/java/stirling/software/SPDF/controller/converters/ConvertOfficeController.java index 794ffd80..03f04acd 100644 --- a/src/main/java/stirling/software/SPDF/controller/converters/ConvertOfficeController.java +++ b/src/main/java/stirling/software/SPDF/controller/converters/ConvertOfficeController.java @@ -19,62 +19,57 @@ import org.springframework.web.multipart.MultipartFile; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.ProcessExecutor; + @Controller public class ConvertOfficeController { - - @GetMapping("/file-to-pdf") + public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { + // Check for valid file extension + String originalFilename = inputFile.getOriginalFilename(); + if (originalFilename == null || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) { + throw new IllegalArgumentException("Invalid file extension"); + } + + // Save the uploaded file to a temporary location + Path tempInputFile = Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename)); + Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + + // Prepare the output file path + Path tempOutputFile = Files.createTempFile("output_", ".pdf"); + + // Run the LibreOffice command + List command = new ArrayList<>(Arrays.asList("unoconv", "-vvv", "-f", "pdf", "-o", tempOutputFile.toString(), tempInputFile.toString())); + int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command); + + // Read the converted PDF file + byte[] pdfBytes = Files.readAllBytes(tempOutputFile); + + // Clean up the temporary files + Files.delete(tempInputFile); + Files.delete(tempOutputFile); + + return pdfBytes; + } + + @GetMapping("/file-to-pdf") public String convertToPdfForm(Model model) { model.addAttribute("currentPage", "file-to-pdf"); return "convert/file-to-pdf"; } - @PostMapping("/file-to-pdf") - public ResponseEntity processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException { - - //unused but can start server instance if startup time is to long - //LibreOfficeListener.getInstance().start(); - - byte[] pdfByteArray = convertToPdf(inputFile); - return PdfUtils.bytesToWebResponse(pdfByteArray, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToPDF.pdf"); - } - - -public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { - // Check for valid file extension - String originalFilename = inputFile.getOriginalFilename(); - if (originalFilename == null || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) { - throw new IllegalArgumentException("Invalid file extension"); + private boolean isValidFileExtension(String fileExtension) { + String extensionPattern = "^(?i)[a-z0-9]{2,4}$"; + return fileExtension.matches(extensionPattern); } - // Save the uploaded file to a temporary location - Path tempInputFile = Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename)); - Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + @PostMapping("/file-to-pdf") + public ResponseEntity processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException { - // Prepare the output file path - Path tempOutputFile = Files.createTempFile("output_", ".pdf"); + // unused but can start server instance if startup time is to long + // LibreOfficeListener.getInstance().start(); - // Run the LibreOffice command - List command = new ArrayList<>(Arrays.asList("unoconv", "-vvv", - "-f", - "pdf", - "-o", - tempOutputFile.toString(), - tempInputFile.toString())); - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command); - - // Read the converted PDF file - byte[] pdfBytes = Files.readAllBytes(tempOutputFile); - - // Clean up the temporary files - Files.delete(tempInputFile); - Files.delete(tempOutputFile); - - return pdfBytes; -} -private boolean isValidFileExtension(String fileExtension) { - String extensionPattern = "^(?i)[a-z0-9]{2,4}$"; - return fileExtension.matches(extensionPattern); -} + byte[] pdfByteArray = convertToPdf(inputFile); + return PdfUtils.bytesToWebResponse(pdfByteArray, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToPDF.pdf"); + } } diff --git a/src/main/java/stirling/software/SPDF/controller/converters/ConvertPDFToOffice.java b/src/main/java/stirling/software/SPDF/controller/converters/ConvertPDFToOffice.java index d07846fa..dd0b9d5f 100644 --- a/src/main/java/stirling/software/SPDF/controller/converters/ConvertPDFToOffice.java +++ b/src/main/java/stirling/software/SPDF/controller/converters/ConvertPDFToOffice.java @@ -15,12 +15,10 @@ import stirling.software.SPDF.utils.PDFToFile; @Controller public class ConvertPDFToOffice { - - - @GetMapping("/pdf-to-word") - public ModelAndView pdfToWord() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word"); - modelAndView.addObject("currentPage", "pdf-to-word"); + @GetMapping("/pdf-to-html") + public ModelAndView pdfToHTML() { + ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html"); + modelAndView.addObject("currentPage", "pdf-to-html"); return modelAndView; } @@ -38,10 +36,10 @@ public class ConvertPDFToOffice { return modelAndView; } - @GetMapping("/pdf-to-html") - public ModelAndView pdfToHTML() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html"); - modelAndView.addObject("currentPage", "pdf-to-html"); + @GetMapping("/pdf-to-word") + public ModelAndView pdfToWord() { + ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word"); + modelAndView.addObject("currentPage", "pdf-to-word"); return modelAndView; } @@ -52,46 +50,37 @@ public class ConvertPDFToOffice { return modelAndView; } - - @PostMapping("/pdf-to-word") - public ResponseEntity processPdfToWord(@RequestParam("fileInput") MultipartFile inputFile, - @RequestParam("outputFormat") String outputFormat) throws IOException, InterruptedException { - PDFToFile pdfToFile = new PDFToFile(); - return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); - } - - @PostMapping("/pdf-to-presentation") - public ResponseEntity processPdfToPresentation(@RequestParam("fileInput") MultipartFile inputFile, - @RequestParam("outputFormat") String outputFormat) throws IOException, InterruptedException { - PDFToFile pdfToFile = new PDFToFile(); - return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import"); - } - - @PostMapping("/pdf-to-text") - public ResponseEntity processPdfToRTForTXT(@RequestParam("fileInput") MultipartFile inputFile, - @RequestParam("outputFormat") String outputFormat) throws IOException, InterruptedException { - PDFToFile pdfToFile = new PDFToFile(); - return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); - } - - @PostMapping("/pdf-to-html") public ResponseEntity processPdfToHTML(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException { PDFToFile pdfToFile = new PDFToFile(); return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import"); } + @PostMapping("/pdf-to-presentation") + public ResponseEntity processPdfToPresentation(@RequestParam("fileInput") MultipartFile inputFile, @RequestParam("outputFormat") String outputFormat) + throws IOException, InterruptedException { + PDFToFile pdfToFile = new PDFToFile(); + return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import"); + } + + @PostMapping("/pdf-to-text") + public ResponseEntity processPdfToRTForTXT(@RequestParam("fileInput") MultipartFile inputFile, @RequestParam("outputFormat") String outputFormat) + throws IOException, InterruptedException { + PDFToFile pdfToFile = new PDFToFile(); + return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); + } + + @PostMapping("/pdf-to-word") + public ResponseEntity processPdfToWord(@RequestParam("fileInput") MultipartFile inputFile, @RequestParam("outputFormat") String outputFormat) + throws IOException, InterruptedException { + PDFToFile pdfToFile = new PDFToFile(); + return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); + } + @PostMapping("/pdf-to-xml") public ResponseEntity processPdfToXML(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException { PDFToFile pdfToFile = new PDFToFile(); return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import"); } - - - - - - - - + } diff --git a/src/main/java/stirling/software/SPDF/controller/converters/ConvertPDFToPDFA.java b/src/main/java/stirling/software/SPDF/controller/converters/ConvertPDFToPDFA.java index 3c8319c9..2a9af96d 100644 --- a/src/main/java/stirling/software/SPDF/controller/converters/ConvertPDFToPDFA.java +++ b/src/main/java/stirling/software/SPDF/controller/converters/ConvertPDFToPDFA.java @@ -17,21 +17,13 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import stirling.software.SPDF.utils.ProcessExecutor; + @Controller public class ConvertPDFToPDFA { - @GetMapping("/pdf-to-pdfa") - public String pdfToPdfAForm(Model model) { - model.addAttribute("currentPage", "pdf-to-pdfa"); - return "convert/pdf-to-pdfa"; - } - - @PostMapping("/pdf-to-pdfa") - public ResponseEntity pdfToPdfA( - @RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException { + public ResponseEntity pdfToPdfA(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException { - // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); inputFile.transferTo(tempInputFile.toFile()); @@ -50,7 +42,7 @@ public class ConvertPDFToPDFA { 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); @@ -64,7 +56,12 @@ public class ConvertPDFToPDFA { headers.setContentType(MediaType.APPLICATION_PDF); headers.setContentDispositionFormData("attachment", outputFilename); return ResponseEntity.ok().headers(headers).body(pdfBytes); -} + } + @GetMapping("/pdf-to-pdfa") + public String pdfToPdfAForm(Model model) { + model.addAttribute("currentPage", "pdf-to-pdfa"); + return "convert/pdf-to-pdfa"; + } } diff --git a/src/main/java/stirling/software/SPDF/controller/CompressController.java b/src/main/java/stirling/software/SPDF/controller/other/CompressController.java similarity index 85% rename from src/main/java/stirling/software/SPDF/controller/CompressController.java rename to src/main/java/stirling/software/SPDF/controller/other/CompressController.java index b14e0721..9f35ad57 100644 --- a/src/main/java/stirling/software/SPDF/controller/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/other/CompressController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller; +package stirling.software.SPDF.controller.other; import java.io.IOException; import java.nio.file.Files; @@ -20,7 +20,6 @@ import org.springframework.web.multipart.MultipartFile; import stirling.software.SPDF.utils.ProcessExecutor; - @Controller public class CompressController { @@ -29,16 +28,13 @@ public class CompressController { @GetMapping("/compress-pdf") public String compressPdfForm(Model model) { model.addAttribute("currentPage", "compress-pdf"); - return "compress-pdf"; + return "other/compress-pdf"; } - @PostMapping("/compress-pdf") - public ResponseEntity optimizePdf( - @RequestParam("fileInput") MultipartFile inputFile, - @RequestParam("optimizeLevel") int optimizeLevel, - @RequestParam(name = "fastWebView", required = false) Boolean fastWebView, - @RequestParam(name = "jbig2Lossy", required = false) Boolean jbig2Lossy) throws IOException, InterruptedException { + public ResponseEntity optimizePdf(@RequestParam("fileInput") MultipartFile inputFile, @RequestParam("optimizeLevel") int optimizeLevel, + @RequestParam(name = "fastWebView", required = false) Boolean fastWebView, @RequestParam(name = "jbig2Lossy", required = false) Boolean jbig2Lossy) + throws IOException, InterruptedException { // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); @@ -57,7 +53,6 @@ public class CompressController { command.add("--output-type"); command.add("pdf"); - if (fastWebView != null && fastWebView) { long fileSize = inputFile.getSize(); long fastWebViewSize = (long) (fileSize * 1.25); // 25% higher than file size @@ -73,7 +68,7 @@ public class CompressController { 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); @@ -87,6 +82,6 @@ public class CompressController { headers.setContentType(MediaType.APPLICATION_PDF); headers.setContentDispositionFormData("attachment", outputFilename); return ResponseEntity.ok().headers(headers).body(pdfBytes); -} + } } diff --git a/src/main/java/stirling/software/SPDF/controller/ExtractImagesController.java b/src/main/java/stirling/software/SPDF/controller/other/ExtractImagesController.java similarity index 84% rename from src/main/java/stirling/software/SPDF/controller/ExtractImagesController.java rename to src/main/java/stirling/software/SPDF/controller/other/ExtractImagesController.java index c6d5a750..10c2f1f5 100644 --- a/src/main/java/stirling/software/SPDF/controller/ExtractImagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/other/ExtractImagesController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller; +package stirling.software.SPDF.controller.other; import java.awt.Graphics2D; import java.awt.Image; @@ -36,15 +36,9 @@ public class ExtractImagesController { private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class); - @GetMapping("/extract-images") - public String extractImagesForm(Model model) { - model.addAttribute("currentPage", "extract-images"); - return "extract-images"; - } - @PostMapping("/extract-images") public ResponseEntity extractImages(@RequestParam("fileInput") MultipartFile file, @RequestParam("format") String format) throws IOException { - + System.out.println(System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format); PDDocument document = PDDocument.load(file.getBytes()); @@ -58,7 +52,7 @@ public class ExtractImagesController { zos.setLevel(Deflater.BEST_COMPRESSION); int imageIndex = 1; - + int pageNum = 1; // Iterate over each page for (PDPage page : document.getPages()) { @@ -72,21 +66,18 @@ public class ExtractImagesController { RenderedImage renderedImage = image.getImage(); BufferedImage bufferedImage = null; if (format.equalsIgnoreCase("png")) { - bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), - BufferedImage.TYPE_INT_ARGB); + bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_INT_ARGB); } else if (format.equalsIgnoreCase("jpeg") || format.equalsIgnoreCase("jpg")) { - bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), - BufferedImage.TYPE_INT_RGB); + bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_INT_RGB); } else if (format.equalsIgnoreCase("gif")) { - bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), - BufferedImage.TYPE_BYTE_INDEXED); - } + bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_BYTE_INDEXED); + } // Write image to zip file String imageName = "Image " + imageIndex + " (Page " + pageNum + ")." + format; ZipEntry zipEntry = new ZipEntry(imageName); zos.putNextEntry(zipEntry); - + Graphics2D g = bufferedImage.createGraphics(); g.drawImage((Image) renderedImage, 0, 0, null); g.dispose(); @@ -94,12 +85,11 @@ public class ExtractImagesController { ByteArrayOutputStream imageBaos = new ByteArrayOutputStream(); ImageIO.write(bufferedImage, format, imageBaos); zos.write(imageBaos.toByteArray()); - - + zos.closeEntry(); imageIndex++; } - } + } } // Close ZipOutputStream and PDDocument @@ -110,20 +100,22 @@ public class ExtractImagesController { byte[] zipContents = baos.toByteArray(); ByteArrayResource resource = new ByteArrayResource(zipContents); - // Set content disposition header to indicate that the response should be downloaded as a file + // Set content disposition header to indicate that the response should be + // downloaded as a file HttpHeaders headers = new HttpHeaders(); headers.setContentLength(zipContents.length); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted-images.zip"); - + // Return ResponseEntity with ByteArrayResource and headers - return ResponseEntity - .status(HttpStatus.OK) - .headers(headers) - - .header("Cache-Control", "no-cache") - .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(resource); + return ResponseEntity.status(HttpStatus.OK).headers(headers) + + .header("Cache-Control", "no-cache").contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource); } - - + + @GetMapping("/extract-images") + public String extractImagesForm(Model model) { + model.addAttribute("currentPage", "extract-images"); + return "other/extract-images"; + } + } diff --git a/src/main/java/stirling/software/SPDF/controller/security/MetadataController.java b/src/main/java/stirling/software/SPDF/controller/other/MetadataController.java similarity index 94% rename from src/main/java/stirling/software/SPDF/controller/security/MetadataController.java rename to src/main/java/stirling/software/SPDF/controller/other/MetadataController.java index cececd3a..c139b885 100644 --- a/src/main/java/stirling/software/SPDF/controller/security/MetadataController.java +++ b/src/main/java/stirling/software/SPDF/controller/other/MetadataController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller.security; +package stirling.software.SPDF.controller.other; import java.io.IOException; import java.text.ParseException; @@ -26,19 +26,20 @@ public class MetadataController { @GetMapping("/change-metadata") public String addWatermarkForm(Model model) { model.addAttribute("currentPage", "change-metadata"); - return "security/change-metadata"; + return "other/change-metadata"; } private String checkUndefined(String entry) { // Check if the string is "undefined" - if("undefined".equals(entry)) { + if ("undefined".equals(entry)) { // Return null if it is return null; } // Return the original string if it's not "undefined" return entry; - + } + @PostMapping("/update-metadata") public ResponseEntity metadata(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam(value = "deleteAll", required = false, defaultValue = "false") Boolean deleteAll, @RequestParam(value = "author", required = false) String author, @@ -50,10 +51,10 @@ public class MetadataController { // Load the PDF file into a PDDocument PDDocument document = PDDocument.load(pdfFile.getBytes()); - + // Get the document information from the PDF PDDocumentInformation info = document.getDocumentInformation(); - + // Check if each metadata value is "undefined" and set it to null if it is author = checkUndefined(author); creationDate = checkUndefined(creationDate); @@ -64,8 +65,9 @@ public class MetadataController { subject = checkUndefined(subject); title = checkUndefined(title); trapped = checkUndefined(trapped); - - // If the "deleteAll" flag is set, remove all metadata from the document information + + // If the "deleteAll" flag is set, remove all metadata from the document + // information if (deleteAll) { for (String key : info.getMetadataKeys()) { info.setCustomMetadataValue(key, null); @@ -83,7 +85,7 @@ public class MetadataController { title = null; trapped = null; } else { - // Iterate through the request parameters and set the metadata values + // Iterate through the request parameters and set the metadata values for (Entry entry : allRequestParams.entrySet()) { String key = entry.getKey(); // Check if the key is a standard metadata key @@ -128,11 +130,9 @@ public class MetadataController { info.setSubject(subject); info.setTitle(title); info.setTrapped(trapped); - + document.setDocumentInformation(info); return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_metadata.pdf"); } - - - + } diff --git a/src/main/java/stirling/software/SPDF/controller/other/OCRController.java b/src/main/java/stirling/software/SPDF/controller/other/OCRController.java new file mode 100644 index 00000000..a18adc76 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/other/OCRController.java @@ -0,0 +1,168 @@ +package stirling.software.SPDF.controller.other; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.ModelAndView; + +import stirling.software.SPDF.utils.ProcessExecutor; + +@Controller +public class OCRController { + + private static final Logger logger = LoggerFactory.getLogger(OCRController.class); + + public List getAvailableTesseractLanguages() { + String tessdataDir = "/usr/share/tesseract-ocr/4.00/tessdata"; + File[] files = new File(tessdataDir).listFiles(); + if (files == null) { + return Collections.emptyList(); + } + return Arrays.stream(files).filter(file -> file.getName().endsWith(".traineddata")).map(file -> file.getName().replace(".traineddata", "")) + .filter(lang -> !lang.equalsIgnoreCase("osd")).collect(Collectors.toList()); + } + + @GetMapping("/ocr-pdf") + public ModelAndView ocrPdfPage() { + ModelAndView modelAndView = new ModelAndView("other/ocr-pdf"); + modelAndView.addObject("languages", getAvailableTesseractLanguages()); + modelAndView.addObject("currentPage", "ocr-pdf"); + return modelAndView; + } + + @PostMapping("/ocr-pdf") + public ResponseEntity processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile, @RequestParam("languages") List selectedLanguages, + @RequestParam(name = "sidecar", required = false) Boolean sidecar, @RequestParam(name = "deskew", required = false) Boolean deskew, + @RequestParam(name = "clean", required = false) Boolean clean, @RequestParam(name = "clean-final", required = false) Boolean cleanFinal, + @RequestParam(name = "ocrType", required = false) String ocrType) throws IOException, InterruptedException { + + // --output-type pdfa + if (selectedLanguages == null || selectedLanguages.size() < 1) { + throw new IOException("Please select at least one language."); + } + + // Validate and sanitize selected languages using regex + String languagePattern = "^[a-zA-Z]{3}$"; // Regex pattern for three-letter language codes + selectedLanguages = selectedLanguages.stream().filter(lang -> Pattern.matches(languagePattern, lang)).collect(Collectors.toList()); + + if (selectedLanguages.isEmpty()) { + throw new IOException("None of the selected languages are valid."); + } + // Save the uploaded file to a temporary location + Path tempInputFile = Files.createTempFile("input_", ".pdf"); + Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + + // Prepare the output file path + Path tempOutputFile = Files.createTempFile("output_", ".pdf"); + + // Prepare the output file path + Path sidecarTextPath = null; + + // Run OCR Command + String languageOption = String.join("+", selectedLanguages); + + List command = new ArrayList<>(Arrays.asList("ocrmypdf", "--verbose", "2", "--output-type", "pdf")); + + if (sidecar != null && sidecar) { + sidecarTextPath = Files.createTempFile("sidecar", ".txt"); + command.add("--sidecar"); + command.add(sidecarTextPath.toString()); + } + + if (deskew != null && deskew) { + command.add("--deskew"); + } + if (clean != null && clean) { + command.add("--clean"); + } + if (cleanFinal != null && cleanFinal) { + command.add("--clean-final"); + } + if (ocrType != null && !ocrType.equals("")) { + if ("skip-text".equals(ocrType)) { + command.add("--skip-text"); + } else if ("force-ocr".equals(ocrType)) { + command.add("--force-ocr"); + } else if ("Normal".equals(ocrType)) { + + } + } + + command.addAll(Arrays.asList("--language", languageOption, tempInputFile.toString(), tempOutputFile.toString())); + + // Run CLI command + int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); + + // Read the OCR processed PDF file + byte[] pdfBytes = Files.readAllBytes(tempOutputFile); + + // Clean up the temporary files + Files.delete(tempInputFile); + // Return the OCR processed PDF as a response + String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf"; + + HttpHeaders headers = new HttpHeaders(); + + if (sidecar != null && sidecar) { + // Create a zip file containing both the PDF and the text file + String outputZipFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip"; + Path tempZipFile = Files.createTempFile("output_", ".zip"); + + try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { + // Add PDF file to the zip + ZipEntry pdfEntry = new ZipEntry(outputFilename); + zipOut.putNextEntry(pdfEntry); + Files.copy(tempOutputFile, zipOut); + zipOut.closeEntry(); + + // Add text file to the zip + ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt")); + zipOut.putNextEntry(txtEntry); + Files.copy(sidecarTextPath, zipOut); + zipOut.closeEntry(); + } + + byte[] zipBytes = Files.readAllBytes(tempZipFile); + + // Clean up the temporary zip file + Files.delete(tempZipFile); + Files.delete(tempOutputFile); + Files.delete(sidecarTextPath); + + // Return the zip file containing both the PDF and the text file + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + headers.setContentDispositionFormData("attachment", outputZipFilename); + return ResponseEntity.ok().headers(headers).body(zipBytes); + } else { + // Return the OCR processed PDF as a response + Files.delete(tempOutputFile); + headers.setContentType(MediaType.APPLICATION_PDF); + headers.setContentDispositionFormData("attachment", outputFilename); + return ResponseEntity.ok().headers(headers).body(pdfBytes); + } + + } + +} diff --git a/src/main/java/stirling/software/SPDF/controller/OverlayImageController.java b/src/main/java/stirling/software/SPDF/controller/other/OverlayImageController.java similarity index 92% rename from src/main/java/stirling/software/SPDF/controller/OverlayImageController.java rename to src/main/java/stirling/software/SPDF/controller/other/OverlayImageController.java index 54ccd5a1..bf0f2283 100644 --- a/src/main/java/stirling/software/SPDF/controller/OverlayImageController.java +++ b/src/main/java/stirling/software/SPDF/controller/other/OverlayImageController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller; +package stirling.software.SPDF.controller.other; import java.io.IOException; @@ -23,7 +23,7 @@ public class OverlayImageController { @GetMapping("/add-image") public String overlayImage(Model model) { model.addAttribute("currentPage", "add-image"); - return "add-image"; + return "other/add-image"; } @PostMapping("/add-image") diff --git a/src/main/java/stirling/software/SPDF/controller/security/PasswordController.java b/src/main/java/stirling/software/SPDF/controller/security/PasswordController.java index 09457fd9..f86bcc06 100644 --- a/src/main/java/stirling/software/SPDF/controller/security/PasswordController.java +++ b/src/main/java/stirling/software/SPDF/controller/security/PasswordController.java @@ -28,23 +28,11 @@ public class PasswordController { return "security/add-password"; } - @GetMapping("/remove-password") - public String removePasswordForm(Model model) { - model.addAttribute("currentPage", "remove-password"); - return "security/remove-password"; - } - - @GetMapping("/change-permissions") - public String permissionsForm(Model model) { - model.addAttribute("currentPage", "change-permissions"); - return "security/change-permissions"; - } - @PostMapping("/remove-password") public ResponseEntity compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(name = "password") String password) throws IOException { PDDocument document = PDDocument.load(fileInput.getBytes(), password); document.setAllSecurityToBeRemoved(true); - return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_password_removed.pdf"); + return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_password_removed.pdf"); } @PostMapping("/add-password") @@ -75,7 +63,19 @@ public class PasswordController { document.protect(spp); - return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_passworded.pdf"); + return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf"); + } + + @GetMapping("/change-permissions") + public String permissionsForm(Model model) { + model.addAttribute("currentPage", "change-permissions"); + return "security/change-permissions"; + } + + @GetMapping("/remove-password") + public String removePasswordForm(Model model) { + model.addAttribute("currentPage", "remove-password"); + return "security/remove-password"; } } diff --git a/src/main/java/stirling/software/SPDF/controller/security/WatermarkController.java b/src/main/java/stirling/software/SPDF/controller/security/WatermarkController.java index 78c9011d..74c952a9 100644 --- a/src/main/java/stirling/software/SPDF/controller/security/WatermarkController.java +++ b/src/main/java/stirling/software/SPDF/controller/security/WatermarkController.java @@ -31,34 +31,18 @@ import stirling.software.SPDF.utils.WatermarkRemover; @Controller public class WatermarkController { - @GetMapping("/add-watermark") - public String addWatermarkForm(Model model) { - model.addAttribute("currentPage", "add-watermark"); - return "security/add-watermark"; - } - - @GetMapping("/remove-watermark") - public String removeWatermarkForm(Model model) { - model.addAttribute("currentPage", "remove-watermark"); - return "security/remove-watermark"; - } - @PostMapping("/add-watermark") public ResponseEntity addWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText, @RequestParam(defaultValue = "30", name = "fontSize") float fontSize, @RequestParam(defaultValue = "0", name = "rotation") float rotation, - @RequestParam(defaultValue = "0.5", name = "opacity") float opacity, - @RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer, @RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer) - throws IOException { + @RequestParam(defaultValue = "0.5", name = "opacity") float opacity, @RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer, + @RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer) throws IOException { // Load the input PDF PDDocument document = PDDocument.load(pdfFile.getInputStream()); // Create a page in the document for (PDPage page : document.getPages()) { - - - // Get the page's content stream PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true); @@ -66,7 +50,7 @@ public class WatermarkController { PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState(); graphicsState.setNonStrokingAlphaConstant(opacity); contentStream.setGraphicsStateParameters(graphicsState); - + // Set font of watermark PDFont font = PDType1Font.HELVETICA_BOLD; contentStream.beginText(); @@ -94,19 +78,22 @@ public class WatermarkController { // Close the content stream contentStream.close(); } - return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf"); + return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf"); } - - - - + + @GetMapping("/add-watermark") + public String addWatermarkForm(Model model) { + model.addAttribute("currentPage", "add-watermark"); + return "security/add-watermark"; + } + @PostMapping("/remove-watermark") public ResponseEntity removeWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText) throws Exception { - + // Load the input PDF PDDocument document = PDDocument.load(pdfFile.getInputStream()); - // Create a new PDF document for the output + // Create a new PDF document for the output PDDocument outputDocument = new PDDocument(); // Loop through the pages @@ -115,7 +102,8 @@ public class WatermarkController { PDPage page = document.getPage(i); // Process the content stream to remove the watermark text - WatermarkRemover editor = new WatermarkRemover(watermarkText) {}; + WatermarkRemover editor = new WatermarkRemover(watermarkText) { + }; editor.processPage(page); editor.processPage(page); // Add the page to the output document @@ -149,9 +137,14 @@ public class WatermarkController { } } } - + return PdfUtils.pdfDocToWebResponse(outputDocument, "removed.pdf"); } - + @GetMapping("/remove-watermark") + public String removeWatermarkForm(Model model) { + model.addAttribute("currentPage", "remove-watermark"); + return "security/remove-watermark"; + } + } diff --git a/src/main/java/stirling/software/SPDF/utils/ErrorUtils.java b/src/main/java/stirling/software/SPDF/utils/ErrorUtils.java index 2c328a6a..493ce63e 100644 --- a/src/main/java/stirling/software/SPDF/utils/ErrorUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/ErrorUtils.java @@ -28,5 +28,5 @@ public class ErrorUtils { modelAndView.addObject("stackTrace", stackTrace); return modelAndView; } - + } diff --git a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java index 7821d47c..450f8192 100644 --- a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java +++ b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.utils; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -19,9 +20,9 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; + public class PDFToFile { - public ResponseEntity processPdfToOfficeFormat(MultipartFile inputFile, String outputFormat, String libreOfficeFilter) - throws IOException, InterruptedException { + public ResponseEntity processPdfToOfficeFormat(MultipartFile inputFile, String outputFormat, String libreOfficeFilter) throws IOException, InterruptedException { if (!"application/pdf".equals(inputFile.getContentType())) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); @@ -32,11 +33,11 @@ public class PDFToFile { String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.')); // Validate output format - List allowedFormats = Arrays.asList("doc", "docx", "odt", "ppt", "pptx", "odp", "rtf", "html","xml","txt:Text"); + List allowedFormats = Arrays.asList("doc", "docx", "odt", "ppt", "pptx", "odp", "rtf", "html", "xml", "txt:Text"); if (!allowedFormats.contains(outputFormat)) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } - + Path tempInputFile = null; Path tempOutputDir = null; byte[] fileBytes; @@ -52,9 +53,8 @@ public class PDFToFile { tempOutputDir = Files.createTempDirectory("output_"); // Run the LibreOffice command - List command = new ArrayList<>(Arrays.asList( - "soffice", "--infilter=" + libreOfficeFilter, "--convert-to", outputFormat, "--outdir", tempOutputDir.toString(), tempInputFile.toString() - )); + List command = new ArrayList<>( + Arrays.asList("soffice", "--infilter=" + libreOfficeFilter, "--convert-to", outputFormat, "--outdir", tempOutputDir.toString(), tempInputFile.toString())); int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command); // Get output files @@ -64,8 +64,8 @@ public class PDFToFile { // Return single output file File outputFile = outputFiles.get(0); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); - if(outputFormat.equals("txt:Text")) { - outputFormat="txt"; + if (outputFormat.equals("txt:Text")) { + outputFormat = "txt"; } headers.setContentDispositionFormData("attachment", pdfBaseName + "." + outputFormat); fileBytes = FileUtils.readFileToByteArray(outputFile); diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index f1f05c83..d083121d 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -9,6 +9,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -31,18 +37,79 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; -import java.io.InputStream; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; - public class PdfUtils { private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class); + public static ResponseEntity boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException { + return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName); + + } + + public static ResponseEntity bytesToWebResponse(byte[] bytes, String docName) throws IOException { + + // Return the PDF as a response + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_PDF); + headers.setContentLength(bytes.length); + headers.setContentDispositionFormData("attachment", docName); + return new ResponseEntity<>(bytes, headers, HttpStatus.OK); + } + + public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI) throws IOException, Exception { + try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) { + PDFRenderer pdfRenderer = new PDFRenderer(document); + int pageCount = document.getNumberOfPages(); + List images = new ArrayList<>(); + + // Create images of all pages + for (int i = 0; i < pageCount; i++) { + images.add(pdfRenderer.renderImageWithDPI(i, 300, colorType)); + } + + if (singleImage) { + // Combine all images into a single big image + BufferedImage combined = new BufferedImage(images.get(0).getWidth(), images.get(0).getHeight() * pageCount, BufferedImage.TYPE_INT_RGB); + Graphics g = combined.getGraphics(); + for (int i = 0; i < images.size(); i++) { + g.drawImage(images.get(i), 0, i * images.get(0).getHeight(), null); + } + images = Arrays.asList(combined); + } + + // Create a ByteArrayOutputStream to save the image(s) to + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if (singleImage) { + // Write the image to the output stream + ImageIO.write(images.get(0), imageType, baos); + + // Log that the image was successfully written to the byte array + logger.info("Image successfully written to byte array"); + } else { + // Zip the images and return as byte array + try (ZipOutputStream zos = new ZipOutputStream(baos)) { + for (int i = 0; i < images.size(); i++) { + BufferedImage image = images.get(i); + try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) { + ImageIO.write(image, imageType, baosImage); + + // Add the image to the zip file + zos.putNextEntry(new ZipEntry(String.format("page_%d.%s", i + 1, imageType.toLowerCase()))); + zos.write(baosImage.toByteArray()); + } + } + // Log that the images were successfully written to the byte array + logger.info("Images successfully written to byte array as a zip"); + } + } + return baos.toByteArray(); + } catch (IOException e) { + // Log an error message if there is an issue converting the PDF to an image + logger.error("Error converting PDF to image", e); + throw e; + } + } + public static byte[] imageToPdf(MultipartFile[] files, boolean stretchToFit, boolean autoRotate) throws IOException { try (PDDocument doc = new PDDocument()) { for (MultipartFile file : files) { @@ -73,7 +140,8 @@ public class PdfUtils { float pageHeight = page.getMediaBox().getHeight(); if (autoRotate && ((image.getWidth() > image.getHeight() && pageHeight > pageWidth) || (image.getWidth() < image.getHeight() && pageWidth > pageHeight))) { - // Rotate the page 90 degrees if the image better fits the page in landscape orientation + // Rotate the page 90 degrees if the image better fits the page in landscape + // orientation page.setRotation(90); pageWidth = page.getMediaBox().getHeight(); pageHeight = page.getMediaBox().getWidth(); @@ -136,123 +204,21 @@ public class PdfUtils { } + public static X509Certificate[] loadCertificateChainFromKeystore(InputStream keystoreInputStream, String keystorePassword) throws Exception { + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + keystore.load(keystoreInputStream, keystorePassword.toCharArray()); - public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI) - throws IOException, Exception { - try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) { - PDFRenderer pdfRenderer = new PDFRenderer(document); - int pageCount = document.getNumberOfPages(); - List images = new ArrayList<>(); + String alias = keystore.aliases().nextElement(); + Certificate[] certChain = keystore.getCertificateChain(alias); + X509Certificate[] x509CertChain = new X509Certificate[certChain.length]; - // Create images of all pages - for (int i = 0; i < pageCount; i++) { - images.add(pdfRenderer.renderImageWithDPI(i, 300, colorType)); - } - - if (singleImage) { - // Combine all images into a single big image - BufferedImage combined = new BufferedImage(images.get(0).getWidth(), images.get(0).getHeight() * pageCount, BufferedImage.TYPE_INT_RGB); - Graphics g = combined.getGraphics(); - for (int i = 0; i < images.size(); i++) { - g.drawImage(images.get(i), 0, i * images.get(0).getHeight(), null); - } - images = Arrays.asList(combined); - } - - // Create a ByteArrayOutputStream to save the image(s) to - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - if (singleImage) { - // Write the image to the output stream - ImageIO.write(images.get(0), imageType, baos); - - // Log that the image was successfully written to the byte array - logger.info("Image successfully written to byte array"); - } else { - // Zip the images and return as byte array - try (ZipOutputStream zos = new ZipOutputStream(baos)) { - for (int i = 0; i < images.size(); i++) { - BufferedImage image = images.get(i); - try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) { - ImageIO.write(image, imageType, baosImage); - - // Add the image to the zip file - zos.putNextEntry(new ZipEntry(String.format("page_%d.%s", i + 1, imageType.toLowerCase()))); - zos.write(baosImage.toByteArray()); - } - } - // Log that the images were successfully written to the byte array - logger.info("Images successfully written to byte array as a zip"); - } - } - return baos.toByteArray(); - } catch (IOException e) { - // Log an error message if there is an issue converting the PDF to an image - logger.error("Error converting PDF to image", e); - throw e; - } - } - - public static byte[] overlayImage(byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) throws IOException { - - PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes)); - - // Get the first page of the PDF - int pages = document.getNumberOfPages(); - for (int i = 0; i < pages; i++) { - PDPage page = document.getPage(i); - try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true)) { - // Create an image object from the image bytes - PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, ""); - // Draw the image onto the page at the specified x and y coordinates - contentStream.drawImage(image, x, y); - logger.info("Image successfully overlayed onto PDF"); - if (everyPage == false && i == 0) { - break; - } - } catch (IOException e) { - // Log an error message if there is an issue overlaying the image onto the PDF - logger.error("Error overlaying image onto PDF", e); - throw e; - } - - } - // Create a ByteArrayOutputStream to save the PDF to - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - document.save(baos); - logger.info("PDF successfully saved to byte array"); - return baos.toByteArray(); + for (int i = 0; i < certChain.length; i++) { + x509CertChain[i] = (X509Certificate) certChain[i]; } - - - - public static ResponseEntity pdfDocToWebResponse(PDDocument document, String docName) throws IOException { - - // Open Byte Array and save document to it - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - document.save(baos); - // Close the document - document.close(); - - return PdfUtils.boasToWebResponse(baos, docName); + return x509CertChain; } - public static ResponseEntity boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException { - return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName); - - } - - public static ResponseEntity bytesToWebResponse(byte[] bytes, String docName) throws IOException { - - // Return the PDF as a response - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_PDF); - headers.setContentLength(bytes.length); - headers.setContentDispositionFormData("attachment", docName); - return new ResponseEntity<>(bytes, headers, HttpStatus.OK); - } - - public static KeyPair loadKeyPairFromKeystore(InputStream keystoreInputStream, String keystorePassword) throws Exception { KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); keystore.load(keystoreInputStream, keystorePassword.toCharArray()); @@ -265,18 +231,45 @@ public class PdfUtils { return new KeyPair(publicKey, privateKey); } - public static X509Certificate[] loadCertificateChainFromKeystore(InputStream keystoreInputStream, String keystorePassword) throws Exception { - KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - keystore.load(keystoreInputStream, keystorePassword.toCharArray()); + public static byte[] overlayImage(byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) throws IOException { + + PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes)); + + // Get the first page of the PDF + int pages = document.getNumberOfPages(); + for (int i = 0; i < pages; i++) { + PDPage page = document.getPage(i); + try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true)) { + // Create an image object from the image bytes + PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, ""); + // Draw the image onto the page at the specified x and y coordinates + contentStream.drawImage(image, x, y); + logger.info("Image successfully overlayed onto PDF"); + if (!everyPage && i == 0) { + break; + } + } catch (IOException e) { + // Log an error message if there is an issue overlaying the image onto the PDF + logger.error("Error overlaying image onto PDF", e); + throw e; + } - String alias = keystore.aliases().nextElement(); - Certificate[] certChain = keystore.getCertificateChain(alias); - X509Certificate[] x509CertChain = new X509Certificate[certChain.length]; - - for (int i = 0; i < certChain.length; i++) { - x509CertChain[i] = (X509Certificate) certChain[i]; } + // Create a ByteArrayOutputStream to save the PDF to + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + logger.info("PDF successfully saved to byte array"); + return baos.toByteArray(); + } - return x509CertChain; + public static ResponseEntity pdfDocToWebResponse(PDDocument document, String docName) throws IOException { + + // Open Byte Array and save document to it + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + // Close the document + document.close(); + + return PdfUtils.boasToWebResponse(baos, docName); } } diff --git a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java index 8e9065c9..c8744c52 100644 --- a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java +++ b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java @@ -9,15 +9,24 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; + public class ProcessExecutor { - - public enum Processes { - LIBRE_OFFICE, - OCR_MY_PDF + + public enum Processes { + LIBRE_OFFICE, OCR_MY_PDF } - private static final Map instances = new ConcurrentHashMap<>(); + private static final Map instances = new ConcurrentHashMap<>(); + public static ProcessExecutor getInstance(Processes processType) { + return instances.computeIfAbsent(processType, key -> { + int semaphoreLimit = switch (key) { + case LIBRE_OFFICE -> 1; + case OCR_MY_PDF -> 2; + }; + return new ProcessExecutor(semaphoreLimit); + }); + } private final Semaphore semaphore; @@ -25,78 +34,67 @@ public class ProcessExecutor { this.semaphore = new Semaphore(semaphoreLimit); } - public static ProcessExecutor getInstance(Processes processType) { - return instances.computeIfAbsent(processType, key -> { - int semaphoreLimit = switch (key) { - case LIBRE_OFFICE -> 1; - case OCR_MY_PDF -> 2; - }; - return new ProcessExecutor(semaphoreLimit); - }); + public int runCommandWithOutputHandling(List command) throws IOException, InterruptedException { + int exitCode = 1; + semaphore.acquire(); + try { + + System.out.print("Running command: " + String.join(" ", command)); + ProcessBuilder processBuilder = new ProcessBuilder(command); + Process process = processBuilder.start(); + + // Read the error stream and standard output stream concurrently + List errorLines = new ArrayList<>(); + List outputLines = new ArrayList<>(); + + Thread errorReaderThread = new Thread(() -> { + try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = errorReader.readLine()) != null) { + errorLines.add(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + }); + + Thread outputReaderThread = new Thread(() -> { + try (BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = outputReader.readLine()) != null) { + outputLines.add(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + }); + + errorReaderThread.start(); + outputReaderThread.start(); + + // Wait for the conversion process to complete + exitCode = process.waitFor(); + + // Wait for the reader threads to finish + errorReaderThread.join(); + outputReaderThread.join(); + + if (outputLines.size() > 0) { + String outputMessage = String.join("\n", outputLines); + System.out.println("Command output:\n" + outputMessage); + } + + if (errorLines.size() > 0) { + String errorMessage = String.join("\n", errorLines); + System.out.println("Command error output:\n" + errorMessage); + if (exitCode != 0) { + throw new IOException("Command process failed with exit code " + exitCode + ". Error message: " + errorMessage); + } + } + } finally { + semaphore.release(); + } + return exitCode; } - - public int runCommandWithOutputHandling(List command) throws IOException, InterruptedException { - int exitCode = 1; - semaphore.acquire(); - try { - - System.out.print("Running command: " + String.join(" ", command)); - ProcessBuilder processBuilder = new ProcessBuilder(command); - Process process = processBuilder.start(); - - // Read the error stream and standard output stream concurrently - List errorLines = new ArrayList<>(); - List outputLines = new ArrayList<>(); - - Thread errorReaderThread = new Thread(() -> { - try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) { - String line; - while ((line = errorReader.readLine()) != null) { - errorLines.add(line); - } - } catch (IOException e) { - e.printStackTrace(); - } - }); - - Thread outputReaderThread = new Thread(() -> { - try (BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { - String line; - while ((line = outputReader.readLine()) != null) { - outputLines.add(line); - } - } catch (IOException e) { - e.printStackTrace(); - } - }); - - errorReaderThread.start(); - outputReaderThread.start(); - - // Wait for the conversion process to complete - exitCode = process.waitFor(); - - // Wait for the reader threads to finish - errorReaderThread.join(); - outputReaderThread.join(); - - if (outputLines.size() > 0) { - String outputMessage = String.join("\n", outputLines); - System.out.println("Command output:\n" + outputMessage); - } - - if (errorLines.size() > 0) { - String errorMessage = String.join("\n", errorLines); - System.out.println("Command error output:\n" + errorMessage); - if (exitCode != 0) { - throw new IOException("Command process failed with exit code " + exitCode + ". Error message: " + errorMessage); - } - } - } finally { - semaphore.release(); - } - return exitCode; - } - - + } diff --git a/src/main/java/stirling/software/SPDF/utils/WatermarkRemover.java b/src/main/java/stirling/software/SPDF/utils/WatermarkRemover.java index 8b4871c7..90b44e53 100644 --- a/src/main/java/stirling/software/SPDF/utils/WatermarkRemover.java +++ b/src/main/java/stirling/software/SPDF/utils/WatermarkRemover.java @@ -1,4 +1,5 @@ package stirling.software.SPDF.utils; + import java.io.IOException; import java.util.List; import java.util.regex.Matcher; @@ -12,8 +13,8 @@ import org.apache.pdfbox.cos.COSString; public class WatermarkRemover extends PDFStreamEngine { - private final String watermarkText; private final Pattern pattern; + private final String watermarkText; public WatermarkRemover(String watermarkText) { this.watermarkText = watermarkText; @@ -30,7 +31,7 @@ public class WatermarkRemover extends PDFStreamEngine { } if (processText) { - for(int j = 0 ; j < operands.size(); ++j) { + for (int j = 0; j < operands.size(); ++j) { COSBase operand = operands.get(j); if (operand instanceof COSString) { COSString cosString = (COSString) operand; @@ -56,11 +57,10 @@ public class WatermarkRemover extends PDFStreamEngine { array.set(i, cosString); operands.set(j, array); } - + } } } - } } diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index fbb3574b..ccc663cb 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -238,7 +238,7 @@ function compareVersions(version1, version2) { - - diff --git a/src/main/resources/templates/add-image.html b/src/main/resources/templates/other/add-image.html similarity index 100% rename from src/main/resources/templates/add-image.html rename to src/main/resources/templates/other/add-image.html diff --git a/src/main/resources/templates/security/change-metadata.html b/src/main/resources/templates/other/change-metadata.html similarity index 100% rename from src/main/resources/templates/security/change-metadata.html rename to src/main/resources/templates/other/change-metadata.html diff --git a/src/main/resources/templates/compress-pdf.html b/src/main/resources/templates/other/compress-pdf.html similarity index 100% rename from src/main/resources/templates/compress-pdf.html rename to src/main/resources/templates/other/compress-pdf.html diff --git a/src/main/resources/templates/extract-images.html b/src/main/resources/templates/other/extract-images.html similarity index 100% rename from src/main/resources/templates/extract-images.html rename to src/main/resources/templates/other/extract-images.html diff --git a/src/main/resources/templates/ocr-pdf.html b/src/main/resources/templates/other/ocr-pdf.html similarity index 96% rename from src/main/resources/templates/ocr-pdf.html rename to src/main/resources/templates/other/ocr-pdf.html index e2daa67c..a1470ac9 100644 --- a/src/main/resources/templates/ocr-pdf.html +++ b/src/main/resources/templates/other/ocr-pdf.html @@ -14,8 +14,7 @@

- -
+