Metadata editting and local only JS and pdf to image change and format pages (#44)

* Formatting

* changeMeta

* pdf to img fix

* foramtting

* new image

* lang changes
This commit is contained in:
Anthony Stirling 2023-02-11 14:27:15 +00:00 committed by GitHub
parent aa9f8329d5
commit c739c9dd2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 11691 additions and 1409 deletions

View file

@ -7,9 +7,8 @@
# ******** NOTE ******** # ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check # We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of # the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
# name: "Build repo"
name: "CodeQL"
on: on:
push: push:
@ -42,15 +41,15 @@ jobs:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
- name: Initialize CodeQL # - name: Initialize CodeQL
uses: github/codeql-action/init@v2 # uses: github/codeql-action/init@v2
with: # with:
languages: java # languages: java
- uses: gradle/gradle-build-action@v2.3.3 - uses: gradle/gradle-build-action@v2.3.3
with: with:
gradle-version: 7.6 gradle-version: 7.6
arguments: assemble --no-build-cache arguments: assemble --no-build-cache
- name: Perform CodeQL analysis #- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v2 # uses: github/codeql-action/analyze@v2

BIN
docs/stirling-pdf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View file

@ -6,8 +6,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class SPdfApplication { public class SPdfApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SPdfApplication.class, args); SpringApplication.run(SPdfApplication.class, args);
} }
} }

View file

@ -1,5 +1,7 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.util.Locale;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.LocaleResolver;
@ -8,8 +10,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver; import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import java.util.Locale;
@Configuration @Configuration
public class Beans implements WebMvcConfigurer { public class Beans implements WebMvcConfigurer {

View file

@ -1,15 +1,9 @@
package stirling.software.SPDF.controller; package stirling.software.SPDF.controller;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@ -30,45 +24,45 @@ import stirling.software.SPDF.utils.PdfUtils;
@Controller @Controller
public class CompressController { public class CompressController {
private static final Logger logger = LoggerFactory.getLogger(CompressController.class); private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
@GetMapping("/compress-pdf") @GetMapping("/compress-pdf")
public String compressPdfForm(Model model) { public String compressPdfForm(Model model) {
model.addAttribute("currentPage", "compress-pdf"); model.addAttribute("currentPage", "compress-pdf");
return "compress-pdf"; return "compress-pdf";
} }
@PostMapping("/compress-pdf") @PostMapping("/compress-pdf")
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile pdfFile, public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("imageCompressionLevel") String imageCompressionLevel)
@RequestParam("imageCompressionLevel") String imageCompressionLevel) throws IOException { throws IOException {
// Load a sample PDF document // Load a sample PDF document
PdfDocument document = new PdfDocument(); PdfDocument document = new PdfDocument();
document.loadFromBytes(pdfFile.getBytes()); document.loadFromBytes(pdfFile.getBytes());
// Compress PDF // Compress PDF
document.getFileInfo().setIncrementalUpdate(false); document.getFileInfo().setIncrementalUpdate(false);
document.setCompressionLevel(PdfCompressionLevel.Best); document.setCompressionLevel(PdfCompressionLevel.Best);
// compress PDF Images // compress PDF Images
for (int i = 0; i < document.getPages().getCount(); i++) { for (int i = 0; i < document.getPages().getCount(); i++) {
PdfPageBase page = document.getPages().get(i); PdfPageBase page = document.getPages().get(i);
PdfImageInfo[] images = page.getImagesInfo(); PdfImageInfo[] images = page.getImagesInfo();
if (images != null && images.length > 0) if (images != null && images.length > 0)
for (int j = 0; j < images.length; j++) { for (int j = 0; j < images.length; j++) {
PdfImageInfo image = images[j]; PdfImageInfo image = images[j];
PdfBitmap bp = new PdfBitmap(image.getImage()); PdfBitmap bp = new PdfBitmap(image.getImage());
// bp.setPngDirectToJpeg(true); // bp.setPngDirectToJpeg(true);
bp.setQuality(Integer.valueOf(imageCompressionLevel)); bp.setQuality(Integer.valueOf(imageCompressionLevel));
page.replaceImage(j, bp); page.replaceImage(j, bp);
} }
} }
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_compressed.pdf"); return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_compressed.pdf");
} }
} }

View file

@ -24,54 +24,52 @@ import org.springframework.web.multipart.MultipartFile;
@Controller @Controller
public class MergeController { public class MergeController {
private static final Logger logger = LoggerFactory.getLogger(MergeController.class); private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
@GetMapping("/merge-pdfs") @GetMapping("/merge-pdfs")
public String hello(Model model) { public String hello(Model model) {
model.addAttribute("currentPage", "merge-pdfs"); model.addAttribute("currentPage", "merge-pdfs");
return "merge-pdfs"; return "merge-pdfs";
} }
@PostMapping("/merge-pdfs") @PostMapping("/merge-pdfs")
public ResponseEntity<InputStreamResource> mergePdfs(@RequestParam("fileInput") MultipartFile[] files) public ResponseEntity<InputStreamResource> mergePdfs(@RequestParam("fileInput") MultipartFile[] files) throws IOException {
throws IOException { // Read the input PDF files into PDDocument objects
// Read the input PDF files into PDDocument objects List<PDDocument> documents = new ArrayList<>();
List<PDDocument> documents = new ArrayList<>();
// Loop through the files array and read each file into a PDDocument // Loop through the files array and read each file into a PDDocument
for (MultipartFile file : files) { for (MultipartFile file : files) {
documents.add(PDDocument.load(file.getInputStream())); documents.add(PDDocument.load(file.getInputStream()));
} }
PDDocument mergedDoc = mergeDocuments(documents); PDDocument mergedDoc = mergeDocuments(documents);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
mergedDoc.save(byteArrayOutputStream); mergedDoc.save(byteArrayOutputStream);
mergedDoc.close(); mergedDoc.close();
// Create an InputStreamResource from the merged PDF // Create an InputStreamResource from the merged PDF
InputStreamResource resource = new InputStreamResource( InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
// Return the merged PDF as a response // Return the merged PDF as a response
return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).body(resource); return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).body(resource);
} }
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException { private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
// Create a new empty document // Create a new empty document
PDDocument mergedDoc = new PDDocument(); PDDocument mergedDoc = new PDDocument();
// Iterate over the list of documents and add their pages to the merged document // Iterate over the list of documents and add their pages to the merged document
for (PDDocument doc : documents) { for (PDDocument doc : documents) {
// Get all pages from the current document // Get all pages from the current document
PDPageTree pages = doc.getPages(); PDPageTree pages = doc.getPages();
// Iterate over the pages and add them to the merged document // Iterate over the pages and add them to the merged document
for (PDPage page : pages) { for (PDPage page : pages) {
mergedDoc.addPage(page); mergedDoc.addPage(page);
} }
} }
// Return the merged document // Return the merged document
return mergedDoc; return mergedDoc;
} }
} }

View file

@ -4,9 +4,7 @@ import java.io.IOException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@ -20,27 +18,26 @@ import stirling.software.SPDF.utils.PdfUtils;
@Controller @Controller
public class OverlayImageController { public class OverlayImageController {
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class); private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
@GetMapping("/add-image") @GetMapping("/add-image")
public String overlayImage(Model model) { public String overlayImage(Model model) {
model.addAttribute("currentPage", "add-image"); model.addAttribute("currentPage", "add-image");
return "add-image"; return "add-image";
} }
@PostMapping("/add-image") @PostMapping("/add-image")
public ResponseEntity<byte[]> overlayImage(@RequestParam("fileInput") MultipartFile pdfFile, public ResponseEntity<byte[]> overlayImage(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("fileInput2") MultipartFile imageFile, @RequestParam("x") float x,
@RequestParam("fileInput2") MultipartFile imageFile, @RequestParam("x") float x, @RequestParam("y") float y) {
@RequestParam("y") float y) { try {
try { byte[] pdfBytes = pdfFile.getBytes();
byte[] pdfBytes = pdfFile.getBytes(); byte[] imageBytes = imageFile.getBytes();
byte[] imageBytes = imageFile.getBytes(); byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y);
byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y);
return PdfUtils.bytesToWebResponse(result, pdfFile.getName() + "_overlayed.pdf"); return PdfUtils.bytesToWebResponse(result, pdfFile.getName() + "_overlayed.pdf");
} catch (IOException e) { } catch (IOException e) {
logger.error("Failed to add image to PDF", e); logger.error("Failed to add image to PDF", e);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} }
} }
} }

View file

@ -1,43 +1,25 @@
package stirling.software.SPDF.controller; package stirling.software.SPDF.controller;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; 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 @Controller
public class PdfController { public class PdfController {
private static final Logger logger = LoggerFactory.getLogger(PdfController.class); private static final Logger logger = LoggerFactory.getLogger(PdfController.class);
@GetMapping("/home") @GetMapping("/home")
public String root(Model model) { public String root(Model model) {
return "redirect:/"; return "redirect:/";
} }
@GetMapping("/")
@GetMapping("/") public String home(Model model) {
public String home(Model model) { model.addAttribute("currentPage", "home");
model.addAttribute("currentPage", "home"); return "home";
return "home"; }
}
} }

View file

@ -1,6 +1,5 @@
package stirling.software.SPDF.controller; package stirling.software.SPDF.controller;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -9,9 +8,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@ -25,104 +21,102 @@ import stirling.software.SPDF.utils.PdfUtils;
@Controller @Controller
public class RearrangePagesPDFController { public class RearrangePagesPDFController {
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
@GetMapping("/pdf-organizer") @GetMapping("/pdf-organizer")
public String pageOrganizer(Model model) { public String pageOrganizer(Model model) {
model.addAttribute("currentPage", "pdf-organizer"); model.addAttribute("currentPage", "pdf-organizer");
return "pdf-organizer"; return "pdf-organizer";
} }
@GetMapping("/remove-pages") @GetMapping("/remove-pages")
public String pageDeleter(Model model) { public String pageDeleter(Model model) {
model.addAttribute("currentPage", "remove-pages"); model.addAttribute("currentPage", "remove-pages");
return "remove-pages"; return "remove-pages";
} }
@PostMapping("/remove-pages") @PostMapping("/remove-pages")
public ResponseEntity<byte[]> deletePages(@RequestParam("fileInput") MultipartFile pdfFile, public ResponseEntity<byte[]> deletePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pagesToDelete") String pagesToDelete) throws IOException {
@RequestParam("pagesToDelete") String pagesToDelete) throws IOException {
PDDocument document = PDDocument.load(pdfFile.getBytes());
// Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pagesToDelete.split(",");
List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages());
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
int pageIndex = pagesToRemove.get(i);
document.removePage(pageIndex);
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_removed_pages.pdf");
} PDDocument document = PDDocument.load(pdfFile.getBytes());
private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) { // Split the page order string into an array of page numbers or range of numbers
List<Integer> newPageOrder = new ArrayList<Integer>(); String[] pageOrderArr = pagesToDelete.split(",");
// loop through the page order array
for (String element : pageOrderArr) {
// check if the element contains a range of pages
if (element.contains("-")) {
// split the range into start and end page
String[] range = element.split("-");
int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]);
// check if the end page is greater than total pages
if (end > totalPages) {
end = totalPages;
}
// loop through the range of pages
for (int j = start; j <= end; j++) {
// print the current index
newPageOrder.add(j - 1);
}
} else {
// if the element is a single page
newPageOrder.add(Integer.parseInt(element) - 1);
}
}
return newPageOrder; List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages());
}
@PostMapping("/rearrange-pages") for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, int pageIndex = pagesToRemove.get(i);
@RequestParam("pageOrder") String pageOrder) { document.removePage(pageIndex);
try { }
// Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream());
// Split the page order string into an array of page numbers or range of numbers return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_removed_pages.pdf");
String[] pageOrderArr = pageOrder.split(",");
// int[] newPageOrder = new int[pageOrderArr.length];
int totalPages = document.getNumberOfPages();
List<Integer> newPageOrder = pageOrderToString(pageOrderArr, totalPages); }
// Create a new list to hold the pages in the new order
List<PDPage> newPages = new ArrayList<>();
for (int i = 0; i < newPageOrder.size(); i++) {
newPages.add(document.getPage(newPageOrder.get(i)));
}
// Remove all the pages from the original document private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) {
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) { List<Integer> newPageOrder = new ArrayList<>();
document.removePage(i); // loop through the page order array
} for (String element : pageOrderArr) {
// check if the element contains a range of pages
if (element.contains("-")) {
// split the range into start and end page
String[] range = element.split("-");
int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]);
// check if the end page is greater than total pages
if (end > totalPages) {
end = totalPages;
}
// loop through the range of pages
for (int j = start; j <= end; j++) {
// print the current index
newPageOrder.add(j - 1);
}
} else {
// if the element is a single page
newPageOrder.add(Integer.parseInt(element) - 1);
}
}
// Add the pages in the new order return newPageOrder;
for (PDPage page : newPages) { }
document.addPage(page);
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_rearranged.pdf"); @PostMapping("/rearrange-pages")
} catch (IOException e) { public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) {
try {
// Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream());
logger.error("Failed rearranging documents", e); // Split the page order string into an array of page numbers or range of numbers
return null; String[] pageOrderArr = pageOrder.split(",");
} // int[] newPageOrder = new int[pageOrderArr.length];
} int totalPages = document.getNumberOfPages();
List<Integer> newPageOrder = pageOrderToString(pageOrderArr, totalPages);
// Create a new list to hold the pages in the new order
List<PDPage> newPages = new ArrayList<>();
for (int i = 0; i < newPageOrder.size(); i++) {
newPages.add(document.getPage(newPageOrder.get(i)));
}
// Remove all the pages from the original document
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
document.removePage(i);
}
// Add the pages in the new order
for (PDPage page : newPages) {
document.addPage(page);
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_rearranged.pdf");
} catch (IOException e) {
logger.error("Failed rearranging documents", e);
return null;
}
}
} }

View file

@ -1,19 +1,12 @@
package stirling.software.SPDF.controller; package stirling.software.SPDF.controller;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Iterator;
import java.util.ListIterator;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree; import org.apache.pdfbox.pdmodel.PDPageTree;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@ -27,34 +20,29 @@ import stirling.software.SPDF.utils.PdfUtils;
@Controller @Controller
public class RotationController { public class RotationController {
private static final Logger logger = LoggerFactory.getLogger(RotationController.class); private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
@GetMapping("/rotate-pdf") @GetMapping("/rotate-pdf")
public String rotatePdfForm(Model model) { public String rotatePdfForm(Model model) {
model.addAttribute("currentPage", "rotate-pdf"); model.addAttribute("currentPage", "rotate-pdf");
return "rotate-pdf"; return "rotate-pdf";
} }
@PostMapping("/rotate-pdf") @PostMapping("/rotate-pdf")
public ResponseEntity<byte[]> rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile, public ResponseEntity<byte[]> rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("angle") Integer angle) throws IOException {
@RequestParam("angle") Integer angle) throws IOException {
// Load the PDF document // Load the PDF document
PDDocument document = PDDocument.load(pdfFile.getBytes()); PDDocument document = PDDocument.load(pdfFile.getBytes());
// Get the list of pages in the document // Get the list of pages in the document
PDPageTree pages = document.getPages(); PDPageTree pages = document.getPages();
// Rotate all pages by the specified angle for (PDPage page : pages) {
Iterator<PDPage> iterPage = pages.iterator(); page.setRotation(page.getRotation() + angle);
}
while (iterPage.hasNext()) { return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_rotated.pdf");
PDPage page = iterPage.next();
page.setRotation(page.getRotation() + angle);
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_rotated.pdf"); }
}
} }

View file

@ -37,106 +37,104 @@ import org.springframework.web.multipart.MultipartFile;
@Controller @Controller
public class SplitPDFController { public class SplitPDFController {
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class); private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
@GetMapping("/split-pdfs") @GetMapping("/split-pdfs")
public String splitPdfForm(Model model) { public String splitPdfForm(Model model) {
model.addAttribute("currentPage", "split-pdfs"); model.addAttribute("currentPage", "split-pdfs");
return "split-pdfs"; return "split-pdfs";
} }
@PostMapping("/split-pages") @PostMapping("/split-pages")
public ResponseEntity<Resource> splitPdf(@RequestParam("fileInput") MultipartFile file, public ResponseEntity<Resource> splitPdf(@RequestParam("fileInput") MultipartFile file, @RequestParam("pages") String pages) throws IOException {
@RequestParam("pages") String pages) throws IOException { // parse user input
// parse user input
// open the pdf document // open the pdf document
InputStream inputStream = file.getInputStream(); InputStream inputStream = file.getInputStream();
PDDocument document = PDDocument.load(inputStream); PDDocument document = PDDocument.load(inputStream);
List<Integer> pageNumbers = new ArrayList<>(); List<Integer> pageNumbers = new ArrayList<>();
pages = pages.replaceAll("\\s+", ""); // remove whitespaces pages = pages.replaceAll("\\s+", ""); // remove whitespaces
if (pages.toLowerCase().equals("all")) { if (pages.toLowerCase().equals("all")) {
for (int i = 0; i < document.getNumberOfPages(); i++) { for (int i = 0; i < document.getNumberOfPages(); i++) {
pageNumbers.add(i); pageNumbers.add(i);
} }
} else { } else {
List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(","))); List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(",")));
if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) { if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) {
String lastpage = String.valueOf(document.getNumberOfPages()); String lastpage = String.valueOf(document.getNumberOfPages());
pageNumbersStr.add(lastpage); pageNumbersStr.add(lastpage);
} }
for (String page : pageNumbersStr) { for (String page : pageNumbersStr) {
if (page.contains("-")) { if (page.contains("-")) {
String[] range = page.split("-"); String[] range = page.split("-");
int start = Integer.parseInt(range[0]); int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]); int end = Integer.parseInt(range[1]);
for (int i = start; i <= end; i++) { for (int i = start; i <= end; i++) {
pageNumbers.add(i); pageNumbers.add(i);
} }
} else { } else {
pageNumbers.add(Integer.parseInt(page)); pageNumbers.add(Integer.parseInt(page));
} }
} }
} }
logger.info("Splitting PDF into pages: {}", logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document // split the document
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
int currentPage = 0; int currentPage = 0;
for (int pageNumber : pageNumbers) { for (int pageNumber : pageNumbers) {
try (PDDocument splitDocument = new PDDocument()) { try (PDDocument splitDocument = new PDDocument()) {
for (int i = currentPage; i < pageNumber; i++) { for (int i = currentPage; i < pageNumber; i++) {
PDPage page = document.getPage(i); PDPage page = document.getPage(i);
splitDocument.addPage(page); splitDocument.addPage(page);
logger.debug("Adding page {} to split document", i); logger.debug("Adding page {} to split document", i);
} }
currentPage = pageNumber; currentPage = pageNumber;
logger.debug("Setting current page to {}", currentPage); logger.debug("Setting current page to {}", currentPage);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos); splitDocument.save(baos);
splitDocumentsBoas.add(baos); splitDocumentsBoas.add(baos);
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed splitting documents and saving them", e); logger.error("Failed splitting documents and saving them", e);
throw e; throw e;
} }
} }
// closing the original document // closing the original document
document.close(); document.close();
// create the zip file // create the zip file
Path zipFile = Paths.get("split_documents.zip"); Path zipFile = Paths.get("split_documents.zip");
URI uri = URI.create("jar:file:" + zipFile.toUri().getPath()); URI uri = URI.create("jar:file:" + zipFile.toUri().getPath());
Map<String, String> env = new HashMap<>(); Map<String, String> env = new HashMap<>();
env.put("create", "true"); env.put("create", "true");
FileSystem zipfs = FileSystems.newFileSystem(uri, env); FileSystem zipfs = FileSystems.newFileSystem(uri, env);
// loop through the split documents and write them to the zip file // loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) { for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = "split_document_" + (i + 1) + ".pdf"; String fileName = "split_document_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i); ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray(); byte[] pdf = baos.toByteArray();
Path pathInZipfile = zipfs.getPath(fileName); Path pathInZipfile = zipfs.getPath(fileName);
try (OutputStream os = Files.newOutputStream(pathInZipfile)) { try (OutputStream os = Files.newOutputStream(pathInZipfile)) {
os.write(pdf); os.write(pdf);
logger.info("Wrote split document {} to zip file", fileName); logger.info("Wrote split document {} to zip file", fileName);
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed writing to zip", e); logger.error("Failed writing to zip", e);
throw e; throw e;
} }
} }
zipfs.close(); zipfs.close();
logger.info("Successfully created zip file with split documents: {}", zipFile.toString()); logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
byte[] data = Files.readAllBytes(zipFile); byte[] data = Files.readAllBytes(zipFile);
ByteArrayResource resource = new ByteArrayResource(data); ByteArrayResource resource = new ByteArrayResource(data);
new File("split_documents.zip").delete(); new File("split_documents.zip").delete();
// return the Resource in the response // return the Resource in the response
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=split_documents.zip") return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=split_documents.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource); .contentLength(resource.contentLength()).body(resource);
} }
} }

View file

@ -2,8 +2,11 @@ package stirling.software.SPDF.controller.converters;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.rendering.ImageType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -20,51 +23,75 @@ import stirling.software.SPDF.utils.PdfUtils;
@Controller @Controller
public class ConvertImgPDFController { public class ConvertImgPDFController {
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class); private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
@GetMapping("/img-to-pdf") @GetMapping("/img-to-pdf")
public String convertToPdfForm(Model model) { public String convertToPdfForm(Model model) {
model.addAttribute("currentPage", "img-to-pdf"); model.addAttribute("currentPage", "img-to-pdf");
return "convert/img-to-pdf"; return "convert/img-to-pdf";
} }
@GetMapping("/pdf-to-img") @GetMapping("/pdf-to-img")
public String pdfToimgForm(Model model) { public String pdfToimgForm(Model model) {
model.addAttribute("currentPage", "pdf-to-img"); model.addAttribute("currentPage", "pdf-to-img");
return "convert/pdf-to-img"; return "convert/pdf-to-img";
} }
@PostMapping("/img-to-pdf") @PostMapping("/img-to-pdf")
public ResponseEntity<byte[]> convertToPdf(@RequestParam("fileInput") MultipartFile file) throws IOException { public ResponseEntity<byte[]> convertToPdf(@RequestParam("fileInput") MultipartFile file) throws IOException {
// Convert the file to PDF and get the resulting bytes // Convert the file to PDF and get the resulting bytes
byte[] bytes = PdfUtils.convertToPdf(file.getInputStream()); byte[] bytes = PdfUtils.convertToPdf(file.getInputStream());
logger.info("File {} successfully converted to pdf", file.getOriginalFilename()); logger.info("File {} successfully converted to pdf", file.getOriginalFilename());
return PdfUtils.bytesToWebResponse(bytes, file.getName() + "_coverted.pdf"); return PdfUtils.bytesToWebResponse(bytes, file.getName() + "_coverted.pdf");
} }
@PostMapping("/pdf-to-img") @PostMapping("/pdf-to-img")
public ResponseEntity<byte[]> convertToImage(@RequestParam("fileInput") MultipartFile file, public ResponseEntity<Resource> convertToImage(@RequestParam("fileInput") MultipartFile file, @RequestParam("imageFormat") String imageFormat,
@RequestParam("imageFormat") String imageFormat) throws IOException { @RequestParam("singleOrMultiple") String singleOrMultiple, @RequestParam("colorType") String colorType, @RequestParam("dpi") String dpi) throws IOException {
byte[] pdfBytes = file.getBytes();
// returns bytes for image
byte[] result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toLowerCase());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
ResponseEntity<byte[]> response = new ResponseEntity<>(result, headers, HttpStatus.OK);
return response;
}
private String getMediaType(String imageFormat) { byte[] pdfBytes = file.getBytes();
if (imageFormat.equalsIgnoreCase("PNG")) ImageType colorTypeResult = ImageType.RGB;
return "image/png"; if ("greyscale".equals(colorType)) {
else if (imageFormat.equalsIgnoreCase("JPEG") || imageFormat.equalsIgnoreCase("JPG")) colorTypeResult = ImageType.GRAY;
return "image/jpeg"; } else if ("blackwhite".equals(colorType)) {
else if (imageFormat.equalsIgnoreCase("GIF")) colorTypeResult = ImageType.BINARY;
return "image/gif"; }
else // returns bytes for image
return "application/octet-stream"; boolean singleImage = singleOrMultiple.equals("single");
} byte[] result = null;
try {
result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toLowerCase(), colorTypeResult, singleImage, Integer.valueOf(dpi));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (singleImage) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
ResponseEntity<Resource> response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
return response;
} else {
ByteArrayResource resource = new ByteArrayResource(result);
// return the Resource in the response
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=converted_documents.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(resource.contentLength()).body(resource);
}
}
private String getMediaType(String imageFormat) {
if (imageFormat.equalsIgnoreCase("PNG"))
return "image/png";
else if (imageFormat.equalsIgnoreCase("JPEG") || imageFormat.equalsIgnoreCase("JPG"))
return "image/jpeg";
else if (imageFormat.equalsIgnoreCase("GIF"))
return "image/gif";
else
return "application/octet-stream";
}
} }

View file

@ -0,0 +1,134 @@
package stirling.software.SPDF.controller.security;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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 stirling.software.SPDF.utils.PdfUtils;
@Controller
public class MetadataController {
@GetMapping("/change-metadata")
public String addWatermarkForm(Model model) {
model.addAttribute("currentPage", "change-metadata");
return "security/change-metadata";
}
private String checkUndefined(String entry) {
// Check if the string is "undefined"
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<byte[]> metadata(@RequestParam("fileInput") MultipartFile pdfFile,
@RequestParam(value = "deleteAll", required = false, defaultValue = "false") Boolean deleteAll, @RequestParam(value = "author", required = false) String author,
@RequestParam(value = "creationDate", required = false) String creationDate, @RequestParam(value = "creator", required = false) String creator,
@RequestParam(value = "keywords", required = false) String keywords, @RequestParam(value = "modificationDate", required = false) String modificationDate,
@RequestParam(value = "producer", required = false) String producer, @RequestParam(value = "subject", required = false) String subject,
@RequestParam(value = "title", required = false) String title, @RequestParam(value = "trapped", required = false) String trapped,
@RequestParam Map<String, String> allRequestParams) throws IOException {
// 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);
creator = checkUndefined(creator);
keywords = checkUndefined(keywords);
modificationDate = checkUndefined(modificationDate);
producer = checkUndefined(producer);
subject = checkUndefined(subject);
title = checkUndefined(title);
trapped = checkUndefined(trapped);
// If the "deleteAll" flag is set, remove all metadata from the document information
if (deleteAll) {
for (String key : info.getMetadataKeys()) {
info.setCustomMetadataValue(key, null);
}
author = null;
creationDate = null;
creator = null;
keywords = null;
modificationDate = null;
producer = null;
subject = null;
title = null;
trapped = null;
} else {
// Iterate through the request parameters and set the metadata values
for (Entry<String, String> entry : allRequestParams.entrySet()) {
String key = entry.getKey();
// Check if the key is a standard metadata key
if (!key.equalsIgnoreCase("Author") && !key.equalsIgnoreCase("CreationDate") && !key.equalsIgnoreCase("Creator") && !key.equalsIgnoreCase("Keywords")
&& !key.equalsIgnoreCase("modificationDate") && !key.equalsIgnoreCase("Producer") && !key.equalsIgnoreCase("Subject") && !key.equalsIgnoreCase("Title")
&& !key.equalsIgnoreCase("Trapped") && !key.contains("customKey") && !key.contains("customValue")) {
info.setCustomMetadataValue(key, entry.getValue());
} else if (key.contains("customKey")) {
int number = Integer.parseInt(key.replaceAll("\\D", ""));
String customKey = entry.getValue();
String customValue = allRequestParams.get("customValue" + number);
info.setCustomMetadataValue(customKey, customValue);
}
}
}
if (creationDate != null && creationDate.length() > 0) {
Calendar creationDateCal = Calendar.getInstance();
try {
creationDateCal.setTime(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate));
} catch (ParseException e) {
e.printStackTrace();
}
info.setCreationDate(creationDateCal);
} else {
info.setCreationDate(null);
}
if (modificationDate != null && modificationDate.length() > 0) {
Calendar modificationDateCal = Calendar.getInstance();
try {
modificationDateCal.setTime(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate));
} catch (ParseException e) {
e.printStackTrace();
}
info.setModificationDate(modificationDateCal);
} else {
info.setModificationDate(null);
}
info.setCreator(creator);
info.setKeywords(keywords);
info.setAuthor(author);
info.setProducer(producer);
info.setSubject(subject);
info.setTitle(title);
info.setTrapped(trapped);
document.setDocumentInformation(info);
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_metadata.pdf");
}
}

View file

@ -1,18 +1,12 @@
package stirling.software.SPDF.controller.security; package stirling.software.SPDF.controller.security;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission; import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@ -26,67 +20,62 @@ import stirling.software.SPDF.utils.PdfUtils;
@Controller @Controller
public class PasswordController { public class PasswordController {
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class); private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
@GetMapping("/add-password") @GetMapping("/add-password")
public String addPasswordForm(Model model) { public String addPasswordForm(Model model) {
model.addAttribute("currentPage", "add-password"); model.addAttribute("currentPage", "add-password");
return "security/add-password"; return "security/add-password";
} }
@GetMapping("/remove-password") @GetMapping("/remove-password")
public String removePasswordForm(Model model) { public String removePasswordForm(Model model) {
model.addAttribute("currentPage", "remove-password"); model.addAttribute("currentPage", "remove-password");
return "security/remove-password"; return "security/remove-password";
} }
@GetMapping("/change-permissions") @GetMapping("/change-permissions")
public String permissionsForm(Model model) { public String permissionsForm(Model model) {
model.addAttribute("currentPage", "change-permissions"); model.addAttribute("currentPage", "change-permissions");
return "security/change-permissions"; return "security/change-permissions";
} }
@PostMapping("/remove-password") @PostMapping("/remove-password")
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(name = "password") String password) throws IOException {
@RequestParam(name = "password") String password) throws IOException { PDDocument document = PDDocument.load(fileInput.getBytes(), password);
PDDocument document = PDDocument.load(fileInput.getBytes(), password); document.setAllSecurityToBeRemoved(true);
document.setAllSecurityToBeRemoved(true); return PdfUtils.pdfDocToWebResponse(document, fileInput.getName() + "_password_removed.pdf");
return PdfUtils.pdfDocToWebResponse(document, fileInput.getName() + "_password_removed.pdf"); }
}
@PostMapping("/add-password") @PostMapping("/add-password")
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(defaultValue = "", name = "password") String password,
@RequestParam(defaultValue = "", name = "password") String password, @RequestParam(defaultValue = "128", name = "keyLength") int keyLength, @RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument,
@RequestParam(defaultValue = "128", name = "keyLength") int keyLength, @RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent,
@RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument, @RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility,
@RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent, @RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
@RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility, @RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations,
@RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, @RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
@RequestParam(defaultValue = "false", name = "canModify") boolean canModify, throws IOException {
@RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations,
@RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint,
@RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
throws IOException {
PDDocument document = PDDocument.load(fileInput.getBytes()); PDDocument document = PDDocument.load(fileInput.getBytes());
AccessPermission ap = new AccessPermission(); AccessPermission ap = new AccessPermission();
ap.setCanAssembleDocument(!canAssembleDocument); ap.setCanAssembleDocument(!canAssembleDocument);
ap.setCanExtractContent(!canExtractContent); ap.setCanExtractContent(!canExtractContent);
ap.setCanExtractForAccessibility(!canExtractForAccessibility); ap.setCanExtractForAccessibility(!canExtractForAccessibility);
ap.setCanFillInForm(!canFillInForm); ap.setCanFillInForm(!canFillInForm);
ap.setCanModify(!canModify); ap.setCanModify(!canModify);
ap.setCanModifyAnnotations(!canModifyAnnotations); ap.setCanModifyAnnotations(!canModifyAnnotations);
ap.setCanPrint(!canPrint); ap.setCanPrint(!canPrint);
ap.setCanPrintFaithful(!canPrintFaithful); ap.setCanPrintFaithful(!canPrintFaithful);
StandardProtectionPolicy spp = new StandardProtectionPolicy(password, password, ap); StandardProtectionPolicy spp = new StandardProtectionPolicy(password, password, ap);
spp.setEncryptionKeyLength(keyLength); spp.setEncryptionKeyLength(keyLength);
spp.setPermissions(ap); spp.setPermissions(ap);
document.protect(spp); document.protect(spp);
return PdfUtils.pdfDocToWebResponse(document, fileInput.getName() + "_passworded.pdf"); return PdfUtils.pdfDocToWebResponse(document, fileInput.getName() + "_passworded.pdf");
} }
} }

View file

@ -1,7 +1,6 @@
package stirling.software.SPDF.controller.security; package stirling.software.SPDF.controller.security;
import java.awt.Color; import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@ -9,11 +8,7 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@ -27,57 +22,53 @@ import stirling.software.SPDF.utils.PdfUtils;
@Controller @Controller
public class WatermarkController { public class WatermarkController {
@GetMapping("/add-watermark") @GetMapping("/add-watermark")
public String addWatermarkForm(Model model) { public String addWatermarkForm(Model model) {
model.addAttribute("currentPage", "add-watermark"); model.addAttribute("currentPage", "add-watermark");
return "security/add-watermark"; return "security/add-watermark";
} }
@PostMapping("/add-watermark") @PostMapping("/add-watermark")
public ResponseEntity<byte[]> addWatermark(@RequestParam("fileInput") MultipartFile pdfFile, public ResponseEntity<byte[]> addWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText,
@RequestParam("watermarkText") String watermarkText, @RequestParam(defaultValue = "30", name = "fontSize") float fontSize, @RequestParam(defaultValue = "0", name = "rotation") float rotation,
@RequestParam(defaultValue = "30", name = "fontSize") float fontSize, @RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer, @RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer)
@RequestParam(defaultValue = "0", name = "rotation") float rotation, throws IOException {
@RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer,
@RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer) throws IOException {
// Load the input PDF // Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream()); PDDocument document = PDDocument.load(pdfFile.getInputStream());
// Create a page in the document // Create a page in the document
for (PDPage page : document.getPages()) { for (PDPage page : document.getPages()) {
// Get the page's content stream // Get the page's content stream
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
PDPageContentStream.AppendMode.APPEND, true);
// Set font of watermark // Set font of watermark
PDFont font = PDType1Font.HELVETICA_BOLD; PDFont font = PDType1Font.HELVETICA_BOLD;
contentStream.beginText(); contentStream.beginText();
contentStream.setFont(font, fontSize); contentStream.setFont(font, fontSize);
contentStream.setNonStrokingColor(Color.LIGHT_GRAY); contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
// Set size and location of watermark // Set size and location of watermark
float pageWidth = page.getMediaBox().getWidth(); float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight(); float pageHeight = page.getMediaBox().getHeight();
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000; float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
float watermarkHeight = heightSpacer + fontSize; float watermarkHeight = heightSpacer + fontSize;
int watermarkRows = (int) (pageHeight / watermarkHeight + 1); int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
int watermarkCols = (int) (pageWidth / watermarkWidth + 1); int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
// Add the watermark text // Add the watermark text
for (int i = 0; i < watermarkRows; i++) { for (int i = 0; i < watermarkRows; i++) {
for (int j = 0; j < watermarkCols; j++) { for (int j = 0; j < watermarkCols; j++) {
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), j * watermarkWidth, i * watermarkHeight));
j * watermarkWidth, i * watermarkHeight)); contentStream.showTextWithPositioning(new Object[] { watermarkText });
contentStream.showTextWithPositioning(new Object[] { watermarkText }); }
} }
}
contentStream.endText(); contentStream.endText();
// Close the content stream // Close the content stream
contentStream.close(); contentStream.close();
} }
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_watermarked.pdf"); return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_watermarked.pdf");
} }
} }

View file

@ -1,5 +1,6 @@
package stirling.software.SPDF.utils; package stirling.software.SPDF.utils;
import java.awt.Graphics;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -7,12 +8,13 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Iterator; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@ -31,138 +33,164 @@ import com.spire.pdf.PdfDocument;
public class PdfUtils { public class PdfUtils {
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class); private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
public static byte[] convertToPdf(InputStream imageStream) throws IOException { public static byte[] convertToPdf(InputStream imageStream) throws IOException {
// Create a File object for the image // Create a File object for the image
File imageFile = new File("image.jpg"); File imageFile = new File("image.jpg");
try (FileOutputStream fos = new FileOutputStream(imageFile); InputStream input = imageStream) { try (FileOutputStream fos = new FileOutputStream(imageFile); InputStream input = imageStream) {
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
int len; int len;
// Read from the input stream and write to the file // Read from the input stream and write to the file
while ((len = input.read(buffer)) != -1) { while ((len = input.read(buffer)) != -1) {
fos.write(buffer, 0, len); fos.write(buffer, 0, len);
} }
logger.info("Image successfully written to file: {}", imageFile.getAbsolutePath()); logger.info("Image successfully written to file: {}", imageFile.getAbsolutePath());
} catch (IOException e) { } catch (IOException e) {
logger.error("Error writing image to file: {}", imageFile.getAbsolutePath(), e); logger.error("Error writing image to file: {}", imageFile.getAbsolutePath(), e);
throw e; throw e;
} }
try (PDDocument doc = new PDDocument()) { try (PDDocument doc = new PDDocument()) {
// Create a new PDF page // Create a new PDF page
PDPage page = new PDPage(); PDPage page = new PDPage();
doc.addPage(page); doc.addPage(page);
// Create an image object from the image file // Create an image object from the image file
PDImageXObject image = PDImageXObject.createFromFileByContent(imageFile, doc); PDImageXObject image = PDImageXObject.createFromFileByContent(imageFile, doc);
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) { try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
// Draw the image onto the page // Draw the image onto the page
contentStream.drawImage(image, 0, 0); contentStream.drawImage(image, 0, 0);
logger.info("Image successfully added to PDF"); logger.info("Image successfully added to PDF");
} catch (IOException e) { } catch (IOException e) {
logger.error("Error adding image to PDF", e); logger.error("Error adding image to PDF", e);
throw e; throw e;
} }
// Create a ByteArrayOutputStream to save the PDF to // Create a ByteArrayOutputStream to save the PDF to
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
doc.save(byteArrayOutputStream); doc.save(byteArrayOutputStream);
logger.info("PDF successfully saved to byte array"); logger.info("PDF successfully saved to byte array");
return byteArrayOutputStream.toByteArray(); return byteArrayOutputStream.toByteArray();
} }
} }
public static byte[] convertFromPdf(byte[] inputStream, String imageType) throws IOException { public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI)
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) { throws IOException, Exception {
// Create a PDFRenderer to convert the PDF to an image try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
BufferedImage bim = pdfRenderer.renderImageWithDPI(0, 300, ImageType.RGB); int pageCount = document.getNumberOfPages();
List<BufferedImage> images = new ArrayList<>();
// Get an ImageWriter for the specified image type // Create images of all pages
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(imageType); for (int i = 0; i < pageCount; i++) {
ImageWriter writer = writers.next(); 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 to // Create a ByteArrayOutputStream to save the image(s) to
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) { if (singleImage) {
writer.setOutput(ios); // Write the image to the output stream
// Write the image to the output stream ImageIO.write(images.get(0), "PNG", baos);
writer.write(new IIOImage(bim, null, null));
// Log that the image was successfully written to the byte array
logger.info("Image successfully written to byte array");
}
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) throws IOException { // 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, "PNG", baosImage);
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) { // Add the image to the zip file
// Get the first page of the PDF zos.putNextEntry(new ZipEntry(String.format("page_%d.%s", i + 1, "png")));
PDPage page = document.getPage(0); zos.write(baosImage.toByteArray());
try (PDPageContentStream contentStream = new PDPageContentStream(document, page, }
PDPageContentStream.AppendMode.APPEND, true)) { }
// Create an image object from the image bytes // Log that the images were successfully written to the byte array
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, ""); logger.info("Images successfully written to byte array as a zip");
// 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"); return baos.toByteArray();
} } catch (IOException e) {
// Create a ByteArrayOutputStream to save the PDF to // Log an error message if there is an issue converting the PDF to an image
ByteArrayOutputStream baos = new ByteArrayOutputStream(); logger.error("Error converting PDF to image", e);
document.save(baos); throw e;
logger.info("PDF successfully saved to byte array"); }
return baos.toByteArray(); }
} 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;
}
}
public static ResponseEntity<byte[]> pdfDocToWebResponse(PdfDocument document, String docName) throws IOException { public static byte[] overlayImage(byte[] pdfBytes, byte[] imageBytes, float x, float y) throws IOException {
// Open Byte Array and save document to it try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Get the first page of the PDF
document.saveToStream(baos); PDPage page = document.getPage(0);
// Close the document try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true)) {
document.close(); // 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");
}
// 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();
} 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;
}
}
return PdfUtils.boasToWebResponse(baos, docName); public static ResponseEntity<byte[]> pdfDocToWebResponse(PdfDocument document, String docName) throws IOException {
}
public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName) throws IOException { // Open Byte Array and save document to it
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.saveToStream(baos);
// Close the document
document.close();
// Open Byte Array and save document to it return PdfUtils.boasToWebResponse(baos, docName);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); }
document.save(baos);
// Close the document
document.close();
return PdfUtils.boasToWebResponse(baos, docName); public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName) throws IOException {
}
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) // Open Byte Array and save document to it
throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream();
return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName); document.save(baos);
// Close the document
document.close();
} return PdfUtils.boasToWebResponse(baos, docName);
}
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) throws IOException { public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException {
return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName);
// Return the PDF as a response }
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF); public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) throws IOException {
headers.setContentLength(bytes.length);
headers.setContentDispositionFormData("attachment", docName); // Return the PDF as a response
return new ResponseEntity<>(bytes, headers, HttpStatus.OK); HttpHeaders headers = new HttpHeaders();
} headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentLength(bytes.length);
headers.setContentDispositionFormData("attachment", docName);
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
}
} }

View file

@ -16,6 +16,10 @@ genericSubmit=إرسال
processTimeWarning=تحذير: يمكن أن تستغرق هذه العملية ما يصل إلى دقيقة حسب حجم الملف processTimeWarning=تحذير: يمكن أن تستغرق هذه العملية ما يصل إلى دقيقة حسب حجم الملف
pageOrderPrompt=ترتيب الصفحات (أدخل قائمة بأرقام الصفحات مفصولة بفواصل): pageOrderPrompt=ترتيب الصفحات (أدخل قائمة بأرقام الصفحات مفصولة بفواصل):
goToPage=اذهب goToPage=اذهب
true=\u0635\u062D\u064A\u062D
false=\u062E\u0637\u0623
unknown=\u063A\u064A\u0631 \u0645\u0639\u0631\u0648\u0641
############# #############
# HOME-PAGE # # HOME-PAGE #
############# #############
@ -65,7 +69,8 @@ home.removePassword.desc=إزالة الحماية بكلمة مرور من مس
home.compressPdfs.title=ضغط ملفات PDF home.compressPdfs.title=ضغط ملفات PDF
home.compressPdfs.desc=ضغط ملفات PDF لتقليل حجم الملف. home.compressPdfs.desc=ضغط ملفات PDF لتقليل حجم الملف.
home.changeMetadata.title = \u062A\u063A\u064A\u064A\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0648\u0635\u0641\u064A\u0629
home.changeMetadata.desc = \u062A\u063A\u064A\u064A\u0631 / \u0625\u0632\u0627\u0644\u0629 / \u0625\u0636\u0627\u0641\u0629 \u0628\u064A\u0627\u0646\u0627\u062A \u0623\u0648\u0644\u064A\u0629 \u0645\u0646 \u0645\u0633\u062A\u0646\u062F PDF
#Add image #Add image
addImage.title=إضافة صورة addImage.title=إضافة صورة
@ -129,6 +134,13 @@ imageToPDF.submit=تحول
pdfToImage.title=تحويل PDF إلى صورة pdfToImage.title=تحويل PDF إلى صورة
pdfToImage.header=تحويل PDF إلى صورة pdfToImage.header=تحويل PDF إلى صورة
pdfToImage.selectText=تنسيق الصورة pdfToImage.selectText=تنسيق الصورة
pdfToImage.singleOrMultiple = \u0646\u0648\u0639 \u0646\u062A\u064A\u062C\u0629 \u0627\u0644\u0635\u0648\u0631\u0629
pdfToImage.single = \u0635\u0648\u0631\u0629 \u0648\u0627\u062D\u062F\u0629 \u0643\u0628\u064A\u0631\u0629
pdfToImage.multi = \u0635\u0648\u0631 \u0645\u062A\u0639\u062F\u062F\u0629
pdfToImage.colorType = \u0646\u0648\u0639 \u0627\u0644\u0644\u0648\u0646
pdfToImage.color = \u0627\u0644\u0644\u0648\u0646
pdfToImage.grey = \u062A\u062F\u0631\u062C \u0627\u0644\u0631\u0645\u0627\u062F\u064A
pdfToImage.blackwhite = \u0623\u0628\u064A\u0636 \u0648\u0623\u0633\u0648\u062F (\u0642\u062F \u064A\u0641\u0642\u062F \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A!)
pdfToImage.submit=تحول pdfToImage.submit=تحول
#addPassword #addPassword
@ -182,4 +194,22 @@ removePassword.title=إزالة كلمة المرور
removePassword.header=إزالة كلمة المرور (فك التشفير) removePassword.header=إزالة كلمة المرور (فك التشفير)
removePassword.selectText.1=حدد PDF لفك التشفير removePassword.selectText.1=حدد PDF لفك التشفير
removePassword.selectText.2=كلمة المرور removePassword.selectText.2=كلمة المرور
removePassword.submit=إزالة removePassword.submit=إزالة
changeMetadata.title = \u062A\u063A\u064A\u064A\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0648\u0635\u0641\u064A\u0629
changeMetadata.header = \u062A\u063A\u064A\u064A\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0648\u0635\u0641\u064A\u0629
changeMetadata.selectText.1 = \u064A\u0631\u062C\u0649 \u062A\u0639\u062F\u064A\u0644 \u0627\u0644\u0645\u062A\u063A\u064A\u0631\u0627\u062A \u0627\u0644\u062A\u064A \u062A\u0631\u063A\u0628 \u0641\u064A \u062A\u063A\u064A\u064A\u0631\u0647\u0627
changeMetadata.selectText.2 = \u062D\u0630\u0641 \u0643\u0644 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0623\u0648\u0644\u064A\u0629
changeMetadata.selectText.3 = \u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0623\u0648\u0644\u064A\u0629 \u0627\u0644\u0645\u062E\u0635\u0635\u0629:
changeMetadata.author = \u0627\u0644\u0645\u0624\u0644\u0641:
changeMetadata.creationDate = \u062A\u0627\u0631\u064A\u062E \u0627\u0644\u0625\u0646\u0634\u0627\u0621 (yyyy / MM / dd HH: mm: ss):
changeMetadata.creator = \u0627\u0644\u0645\u0646\u0634\u0626:
changeMetadata.keywords = \u0627\u0644\u0643\u0644\u0645\u0627\u062A \u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629:
changeMetadata.modDate = \u062A\u0627\u0631\u064A\u062E \u0627\u0644\u062A\u0639\u062F\u064A\u0644 (yyyy / MM / dd HH: mm: ss):
changeMetadata.producer = \u0627\u0644\u0645\u0646\u062A\u062C:
changeMetadata.subject = \u0627\u0644\u0645\u0648\u0636\u0648\u0639:
changeMetadata.title = \u0627\u0644\u0639\u0646\u0648\u0627\u0646:
changeMetadata.trapped = \u0645\u062D\u0627\u0635\u0631:
changeMetadata.selectText.4 = \u0628\u064A\u0627\u0646\u0627\u062A \u0648\u0635\u0641\u064A\u0629 \u0623\u062E\u0631\u0649:
changeMetadata.selectText.5 = \u0625\u0636\u0627\u0641\u0629 \u0625\u062F\u062E\u0627\u0644 \u0628\u064A\u0627\u0646\u0627\u062A \u0623\u0648\u0644\u064A\u0629 \u0645\u062E\u0635\u0635
changeMetadata.submit = \u062A\u063A\u064A\u064A\u0631

View file

@ -12,6 +12,10 @@ genericSubmit=Einreichen
processTimeWarning=Achtung: Abhängig von der Dateigröße kann dieser Prozess bis zu einer Minute dauern processTimeWarning=Achtung: Abhängig von der Dateigröße kann dieser Prozess bis zu einer Minute dauern
pageOrderPrompt=Seitenreihenfolge (Geben Sie eine durch Komma getrennte Liste von Seitenzahlen ein): pageOrderPrompt=Seitenreihenfolge (Geben Sie eine durch Komma getrennte Liste von Seitenzahlen ein):
goToPage=Los goToPage=Los
true=Wahr
false=Falsch
unknown=Unbekannt
############# #############
# HOME-PAGE # # HOME-PAGE #
############# #############
@ -61,7 +65,8 @@ home.removePassword.desc=Den Passwortschutz eines PDFs entfernen.
home.compressPdfs.title=PDF komprimieren home.compressPdfs.title=PDF komprimieren
home.compressPdfs.desc=PDF komprimieren um die Dateigröße zu reduzieren. home.compressPdfs.desc=PDF komprimieren um die Dateigröße zu reduzieren.
home.changeMetadata.title=Metadaten ändern
home.changeMetadata.desc=Ändern/Entfernen/Hinzufügen von Metadaten aus einem PDF-Dokument
#Add image #Add image
addImage.title=Bild hinzufügen addImage.title=Bild hinzufügen
@ -126,6 +131,13 @@ imageToPDF.submit=Umwandeln
pdfToImage.title=PDF zu Bild pdfToImage.title=PDF zu Bild
pdfToImage.header=PDF zu Bild pdfToImage.header=PDF zu Bild
pdfToImage.selectText=Bildformat pdfToImage.selectText=Bildformat
pdfToImage.singleOrMultiple=Bildergebnistyp
pdfToImage.single=Einzelnes großes Bild
pdfToImage.multi=Mehrere Bilder
pdfToImage.colorType=Farbtyp
pdfToImage.color=Farbe
pdfToImage.grey=Graustufen
pdfToImage.blackwhite=Schwarzweiß (Datenverlust möglich!)
pdfToImage.submit=Umwandeln pdfToImage.submit=Umwandeln
#addPassword #addPassword
@ -182,7 +194,23 @@ removePassword.selectText.2=Passwort
removePassword.submit=Entfernen removePassword.submit=Entfernen
changeMetadata.title=Metadaten ändern
changeMetadata.header=Metadaten ändern
changeMetadata.selectText.1=Bitte bearbeiten Sie die Variablen, die Sie ändern möchten
changeMetadata.selectText.2=Alle Metadaten löschen
changeMetadata.selectText.3=Benutzerdefinierte Metadaten anzeigen:
changeMetadata.author=Autor:
changeMetadata.creationDate=Erstellungsdatum (jjjj/MM/tt HH:mm:ss):
changeMetadata.creator=Ersteller:
changeMetadata.keywords=Schlüsselwörter:
changeMetadata.modDate=Änderungsdatum (JJJJ/MM/TT HH:mm:ss):
changeMetadata.producer=Produzent:
changeMetadata.subject=Betreff:
changeMetadata.title=Titel:
changeMetadata.trapped=Gefangen:
changeMetadata.selectText.4=Andere Metadaten:
changeMetadata.selectText.5=Benutzerdefinierten Metadateneintrag hinzufügen
changeMetadata.submit=Ändern

View file

@ -11,7 +11,10 @@ imgPrompt=Choose Image
genericSubmit=Submit genericSubmit=Submit
processTimeWarning=Warning: This process can take up to a minute depending on file-size processTimeWarning=Warning: This process can take up to a minute depending on file-size
pageOrderPrompt=Page Order (Enter a comma-separated list of page numbers) : pageOrderPrompt=Page Order (Enter a comma-separated list of page numbers) :
goToPage=go goToPage=Go
true=True
false=False
unknown=Unknown
############# #############
# HOME-PAGE # # HOME-PAGE #
############# #############
@ -61,6 +64,8 @@ home.removePassword.desc=Remove password protection from your PDF document.
home.compressPdfs.title=Compress PDFs home.compressPdfs.title=Compress PDFs
home.compressPdfs.desc=Compress PDFs to reduce their file size. home.compressPdfs.desc=Compress PDFs to reduce their file size.
home.changeMetadata.title=Change Metadata
home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document
#Add image #Add image
@ -125,6 +130,13 @@ imageToPDF.submit=Convert
pdfToImage.title=PDF to Image pdfToImage.title=PDF to Image
pdfToImage.header=PDF to Image pdfToImage.header=PDF to Image
pdfToImage.selectText=Image Format pdfToImage.selectText=Image Format
pdfToImage.singleOrMultiple=Image result type
pdfToImage.single=Single Big Image
pdfToImage.multi=Multiple Images
pdfToImage.colorType=Colour type
pdfToImage.color=Colour
pdfToImage.grey=Greyscale
pdfToImage.blackwhite=Black and White (May lose data!)
pdfToImage.submit=Convert pdfToImage.submit=Convert
#addPassword #addPassword
@ -178,4 +190,49 @@ removePassword.title=Remove password
removePassword.header=Remove password (Decrypt) removePassword.header=Remove password (Decrypt)
removePassword.selectText.1=Select PDF to Decrypt removePassword.selectText.1=Select PDF to Decrypt
removePassword.selectText.2=Password removePassword.selectText.2=Password
removePassword.submit=Remove removePassword.submit=Remove
changeMetadata.title=Change Metadata
changeMetadata.header=Change Metadata
changeMetadata.selectText.1=Please edit the variables you wish to change
changeMetadata.selectText.2=Delete all metadata
changeMetadata.selectText.3=Show Custom Metadata:
changeMetadata.author=Author:
changeMetadata.creationDate=Creation Date (yyyy/MM/dd HH:mm:ss):
changeMetadata.creator=Creator:
changeMetadata.keywords=Keywords:
changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss):
changeMetadata.producer=Producer:
changeMetadata.subject=Subject:
changeMetadata.title=Title:
changeMetadata.trapped=Trapped:
changeMetadata.selectText.4=Other Metadata:
changeMetadata.selectText.5=Add Custom Metadata Entry
changeMetadata.submit=Change

View file

@ -11,7 +11,10 @@ imgPrompt=Choose Image
genericSubmit=Submit genericSubmit=Submit
processTimeWarning=Warning: This process can take up to a minute depending on file-size processTimeWarning=Warning: This process can take up to a minute depending on file-size
pageOrderPrompt=Page Order (Enter a comma-separated list of page numbers) : pageOrderPrompt=Page Order (Enter a comma-separated list of page numbers) :
goToPage=go goToPage=Go
true=True
false=False
unknown=Unknown
############# #############
# HOME-PAGE # # HOME-PAGE #
############# #############
@ -61,6 +64,8 @@ home.removePassword.desc=Remove password protection from your PDF document.
home.compressPdfs.title=Compress PDFs home.compressPdfs.title=Compress PDFs
home.compressPdfs.desc=Compress PDFs to reduce their file size. home.compressPdfs.desc=Compress PDFs to reduce their file size.
home.changeMetadata.title=Change Metadata
home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document
#Add image #Add image
@ -125,8 +130,16 @@ imageToPDF.submit=Convert
pdfToImage.title=PDF to Image pdfToImage.title=PDF to Image
pdfToImage.header=PDF to Image pdfToImage.header=PDF to Image
pdfToImage.selectText=Image Format pdfToImage.selectText=Image Format
pdfToImage.singleOrMultiple=Image result type
pdfToImage.single=Single Big Image
pdfToImage.multi=Multiple Images
pdfToImage.colorType=Color type
pdfToImage.color=Color
pdfToImage.grey=Grayscale
pdfToImage.blackwhite=Black and White (May lose data!)
pdfToImage.submit=Convert pdfToImage.submit=Convert
#addPassword #addPassword
addPassword.title=Add Password addPassword.title=Add Password
addPassword.header=Add password (Encrypt) addPassword.header=Add password (Encrypt)
@ -180,8 +193,23 @@ removePassword.selectText.1=Select PDF to Decrypt
removePassword.selectText.2=Password removePassword.selectText.2=Password
removePassword.submit=Remove removePassword.submit=Remove
changeMetadata.title=Change Metadata
changeMetadata.header=Change Metadata
changeMetadata.selectText.1=Please edit the variables you wish to change
changeMetadata.selectText.2=Delete all metadata
changeMetadata.selectText.3=Show Custom Metadata:
changeMetadata.author=Author:
changeMetadata.creationDate=Creation Date (yyyy/MM/dd HH:mm:ss):
changeMetadata.creator=Creator:
changeMetadata.keywords=Keywords:
changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss):
changeMetadata.producer=Producer:
changeMetadata.subject=Subject:
changeMetadata.title=Title:
changeMetadata.trapped=Trapped:
changeMetadata.selectText.4=Other Metadata:
changeMetadata.selectText.5=Add Custom Metadata Entry
changeMetadata.submit=Change

View file

@ -15,7 +15,11 @@ imgPrompt=Choisir une image
genericSubmit=Soumettre genericSubmit=Soumettre
processTimeWarning=Attention : ce processus peut prendre jusqu'à une minute en fonction de la taille du fichier processTimeWarning=Attention : ce processus peut prendre jusqu'à une minute en fonction de la taille du fichier
pageOrderPrompt=Ordre des pages (Entrez une liste de numéros de page séparés par des virgules) : pageOrderPrompt=Ordre des pages (Entrez une liste de numéros de page séparés par des virgules) :
goToPage=aller goToPage=Aller
true=Vrai
false=Faux
unknown=Inconnu
############# #############
# HOME-PAGE # # HOME-PAGE #
############# #############
@ -65,6 +69,8 @@ home.removePassword.desc=Supprimez la protection par mot de passe de votre docum
home.compressPdfs.title=Compresser les PDF home.compressPdfs.title=Compresser les PDF
home.compressPdfs.desc=Compressez les PDF pour réduire leur taille de fichier. home.compressPdfs.desc=Compressez les PDF pour réduire leur taille de fichier.
home.changeMetadata.title=Modifier les métadonnées
home.changeMetadata.desc=Modifier/Supprimer/Ajouter des métadonnées d'un document PDF
#Add image #Add image
@ -129,6 +135,13 @@ imageToPDF.submit=Convertir
pdfToImage.title=PDF vers image pdfToImage.title=PDF vers image
pdfToImage.header=PDF vers image pdfToImage.header=PDF vers image
pdfToImage.selectText=Format d'image pdfToImage.selectText=Format d'image
pdfToImage.singleOrMultiple=Type de résultat d'image
pdfToImage.single=Une seule grande image
pdfToImage.multi=Plusieurs images
pdfToImage.colorType=Type de couleur
pdfToImage.color=Couleur
pdfToImage.grey=Niveaux de gris
pdfToImage.blackwhite=Noir et Blanc (Peut perdre des données !)
pdfToImage.submit=Convertir pdfToImage.submit=Convertir
#addPassword #addPassword
@ -182,4 +195,22 @@ removePassword.title=Supprimer le mot de passe
removePassword.header=Supprimer le mot de passe (Déchiffrer) removePassword.header=Supprimer le mot de passe (Déchiffrer)
removePassword.selectText.1=Sélectionnez le PDF à déchiffrer removePassword.selectText.1=Sélectionnez le PDF à déchiffrer
removePassword.selectText.2=Mot de passe removePassword.selectText.2=Mot de passe
removePassword.submit=Supprimer removePassword.submit=Supprimer
changeMetadata.title=Modifier les métadonnées
changeMetadata.header=Modifier les métadonnées
changeMetadata.selectText.1=Veuillez modifier les variables que vous souhaitez modifier
changeMetadata.selectText.2=Supprimer toutes les métadonnées
changeMetadata.selectText.3=Afficher les métadonnées personnalisées:
changeMetadata.author=Auteur:
changeMetadata.creationDate=Date de création (aaaa/MM/jj HH:mm:ss):
changeMetadata.creator=Créateur:
changeMetadata.keywords=Mots clés:
changeMetadata.modDate=Date de modification (aaaa/MM/jj HH:mm:ss):
changeMetadata.producer=Producteur:
changeMetadata.subject=Objet:
changeMetadata.title=Titre:
changeMetadata.trapped=Piégé:
changeMetadata.selectText.4=Autres métadonnées:
changeMetadata.selectText.5=Ajouter une entrée de métadonnées personnalisées
changeMetadata.submit=Modifier

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,23 +1,23 @@
#page-container { #page-container {
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
#content-wrap { #content-wrap {
flex: 1; flex: 1;
} }
#footer { #footer {
bottom: 0; bottom: 0;
width: 100%; width: 100%;
} }
html[lang-direction=ltr] * { html[lang-direction=ltr] * {
direction: ltr; direction: ltr;
} }
html[lang-direction=rtl] * { html[lang-direction=rtl] * {
direction: rtl; direction: rtl;
text-align: right; text-align: right;
} }

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="50px" height="50px" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
<path stroke="#007bff" stroke-width="38" d="M 297.507 242.806 L 339.507 242.806 M 247.507 242.806 L 289.507 242.806 M 198.507 242.806 L 240.507 242.806 M 149.507 242.806 L 190.507 242.806 M 99.507 242.806 L 141.507 242.806 M 149.507 196.806 L 190.507 196.806 M 198.507 196.806 L 240.507 196.806 M 247.507 196.806 L 289.507 196.806 M 247.507 150.806 L 289.507 150.806"/>
<path fill="#007bff" d="M 473.507 244.806 C 473.507 244.806 455.507 227.806 418.507 233.806 C 414.507 204.806 383.507 187.806 383.507 187.806 C 383.507 187.806 354.507 222.806 375.507 261.806 C 369.507 264.806 359.507 268.806 344.507 268.806 L 69.507 268.806 C 64.507 287.806 64.507 413.806 202.507 413.806 C 301.507 413.806 375.507 367.806 410.507 283.806 C 462.507 287.806 473.507 244.806 473.507 244.806"/>
</svg>

After

Width:  |  Height:  |  Size: 919 B

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="35px" height="35px" viewBox="0 -0.5 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Github-color</title>
<desc>Created with Sketch.</desc>
<defs>
</defs>
<g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Color-" transform="translate(-700.000000, -560.000000)" fill="#007bff">
<path d="M723.9985,560 C710.746,560 700,570.787092 700,584.096644 C700,594.740671 706.876,603.77183 716.4145,606.958412 C717.6145,607.179786 718.0525,606.435849 718.0525,605.797328 C718.0525,605.225068 718.0315,603.710086 718.0195,601.699648 C711.343,603.155898 709.9345,598.469394 709.9345,598.469394 C708.844,595.686405 707.2705,594.94548 707.2705,594.94548 C705.091,593.450075 707.4355,593.480194 707.4355,593.480194 C709.843,593.650366 711.1105,595.963499 711.1105,595.963499 C713.2525,599.645538 716.728,598.58234 718.096,597.964902 C718.3135,596.407754 718.9345,595.346062 719.62,594.743683 C714.2905,594.135281 708.688,592.069123 708.688,582.836167 C708.688,580.205279 709.6225,578.054788 711.1585,576.369634 C710.911,575.759726 710.0875,573.311058 711.3925,569.993458 C711.3925,569.993458 713.4085,569.345902 717.9925,572.46321 C719.908,571.928599 721.96,571.662047 724.0015,571.651505 C726.04,571.662047 728.0935,571.928599 730.0105,572.46321 C734.5915,569.345902 736.603,569.993458 736.603,569.993458 C737.9125,573.311058 737.089,575.759726 736.8415,576.369634 C738.3805,578.054788 739.309,580.205279 739.309,582.836167 C739.309,592.091712 733.6975,594.129257 728.3515,594.725612 C729.2125,595.469549 729.9805,596.939353 729.9805,599.18773 C729.9805,602.408949 729.9505,605.006706 729.9505,605.797328 C729.9505,606.441873 730.3825,607.191834 731.6005,606.9554 C741.13,603.762794 748,594.737659 748,584.096644 C748,570.787092 737.254,560 723.9985,560" id="Github">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -6,49 +6,35 @@
<body> <body>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<br> <div class="container">
<div class="container"> <div class="row justify-content-center">
<div class="row justify-content-center"> <div class="col-md-6">
<div class="col-md-6"> <h2 th:text="#{addImage.header}"></h2>
<h2 th:text="#{addImage.header}"></h2> <form method="post" th:action="@{add-image}" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput2" name="fileInput2" required>
<form method="post" th:action="@{add-image}" <label class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label>
enctype="multipart/form-data"> </div>
<div class="form-group">
<div class="custom-file"> <label for="x">X</label> <input type="number" class="form-control" id="x" name="x" step="0.01" required>
<input type="file" class="custom-file-input" id="fileInput" </div>
name="fileInput" required> <label <div class="form-group">
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label> <label for="y">Y</label> <input type="number" class="form-control" id="y" name="y" step="0.01" required>
</div> </div>
<div class="custom-file"> <button type="submit" class="btn btn-primary" th:text="#{addImage.submit}"></button>
<input type="file" class="custom-file-input" id="fileInput2" </form>
name="fileInput2" required> <label <th:block th:insert="~{fragments/common :: filelist}"></th:block>
class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label> </div>
</div> </div>
<div class="form-group"> </div>
<label for="x">X</label> <input type="number" class="form-control" </div>
id="x" name="x" step="0.01" required> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
<div class="form-group">
<label for="y">Y</label> <input type="number" class="form-control"
id="y" name="y" step="0.01" required>
</div>
<button type="submit" class="btn btn-primary" th:text="#{addImage.submit}"></button>
</form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body> </body>
</html> </html>

View file

@ -5,40 +5,32 @@
<th:block th:insert="~{fragments/common :: head(title=#{compress.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{compress.title})}"></th:block>
<body> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<br> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{compress.header}"></h2> <h2 th:text="#{compress.header}"></h2>
<form action="#" th:action="@{compress-pdf}" <form action="#" th:action="@{compress-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
th:object="${rotateForm}" method="post" <p th:text="#{processTimeWarning}"></p>
enctype="multipart/form-data"> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<p th:text="#{processTimeWarning}"></p> <div class="form-group">
<div class="custom-file"> <label for="imageCompressionLevel" th:text="#{compress.compressLevel}"></label>
<input type="file" class="custom-file-input" id="fileInput" <input type="number" class="form-control" id="imageCompressionLevel" name="imageCompressionLevel" step="1" value="1" min="1" max="100" required>
name="fileInput" required> <label </div>
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label> <button type="submit" class="btn btn-primary" th:text="#{compress.submit}"></button>
</div> </form>
<div class="form-group"> <th:block th:insert="~{fragments/common :: filelist}"></th:block>
<label for="imageCompressionLevel" th:text="#{compress.compressLevel}"></label> <input type="number" class="form-control"
id="imageCompressionLevel" name="imageCompressionLevel" step="1"
value="1" min="1" max="100" required>
</div>
<button type="submit" class="btn btn-primary" th:text="#{compress.submit}"></button> </div>
</form> </div>
<th:block th:insert="~{fragments/common :: filelist}"></th:block> </div>
</div>
</div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body> </body>
</html> </html>

View file

@ -3,37 +3,33 @@
<th:block th:insert="~{fragments/common :: head(title=#{imageToPDF.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{imageToPDF.title})}"></th:block>
<body> <body>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{imageToPDF.header}"></h2>
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <form method="post" enctype="multipart/form-data" th:action="@{img-to-pdf}">
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" required>
<label class="custom-file-label" for="fileInput" th:text="#{imgPrompt}"></label>
</div>
<br> <br>
<button type="submit" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
<br> </form>
<br> <th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div>
</div>
</div>
<div class="container"> </div>
<div class="row justify-content-center"> <div th:insert="~{fragments/footer.html :: footer}"></div>
<div class="col-md-6"> </div>
<h2 th:text="#{imageToPDF.header}"></h2>
<form method="post" enctype="multipart/form-data" th:action="@{img-to-pdf}">
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" required>
<label class="custom-file-label" for="fileInput" th:text="#{imgPrompt}"></label>
</div>
<br>
<br>
<button type="submit" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
</form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body> </body>
</html> </html>

View file

@ -1,45 +1,57 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org"> <html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{pdfToImage})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{pdfToImage.title})}"></th:block>
<body> <body>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{pdfToImage.header}"></h2>
<p th:text="#{processTimeWarning}"></p>
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-img}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div class="form-group">
<label th:text="#{pdfToImage.selectText}"></label>
<select class="form-control" name="imageFormat">
<option value="png">PNG</option>
</select>
</div>
<div class="form-group">
<label th:text="#{pdfToImage.singleOrMultiple}"></label>
<select class="form-control" name="singleOrMultiple">
<option value="single" th:text="#{pdfToImage.single}"></option>
<option value="multiple" th:text="#{pdfToImage.multi}"></option>
</select>
</div>
<div class="form-group">
<label th:text="#{pdfToImage.colorType}"></label>
<select class="form-control" name="colorType">
<option value="color" th:text="#{pdfToImage.color}"></option>
<option value="greyscale" th:text="#{pdfToImage.grey}"></option>
<option value="blackwhite" th:text="#{pdfToImage.blackwhite}"></option>
</select>
</div>
<div class="form-group">
<label for="dpi">DPI:</label>
<input type="number" name="dpi" class="form-control" id="dpi" min="1" max="100" step="1" value="30" required>
</div>
<button type="submit" class="btn btn-primary" th:text="#{pdfToImage.submit}"></button>
</form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
<br> </div>
<br> </div>
</div>
<div class="container"> </div>
<div class="row justify-content-center"> <div th:insert="~{fragments/footer.html :: footer}"></div>
<div class="col-md-6"> </div>
<h2 th:text="#{pdfToImage.header}"></h2>
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-img}">
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" required>
<label class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
</div>
<div class="form-group">
<label th:text="#{pdfToImage.selectText}"></label> <select class="form-control"
name="imageFormat">
<option value="jpg">JPEG</option>
<option value="png">PNG</option>
<option value="gif">GIF</option>
</select>
</div>
<button type="submit" class="btn btn-primary" th:text="#{pdfToImage.submit}"></button>
</form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body> </body>
</html> </html>

View file

@ -1,5 +1,5 @@
<div th:fragment="card" class="feature-card"> <div th:fragment="card" class="feature-card">
<h5 class="card-title" th:text="${cardTitle}"></h5> <h5 class="card-title" th:text="${cardTitle}"></h5>
<p class="card-text" th:text="${cardText}"></p> <p class="card-text" th:text="${cardText}"></p>
<a class="btn btn-primary" th:href="${cardLink}" th:text="#{goToPage}"></a> <a class="btn btn-primary" th:href="${cardLink}" th:text="#{goToPage}"></a>
</div> </div>

View file

@ -1,31 +1,29 @@
<head th:fragment="head"> <head th:fragment="head">
<!-- Metadata --> <!-- Metadata -->
<meta charset="UTF-8"> <meta charset="UTF-8">
<title th:text="'S-PDF ' + ${title}"></title> <title th:text="'S-PDF ' + ${title}"></title>
<link rel="shortcut icon" href="favicon.svg"> <link rel="shortcut icon" href="favicon.svg">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- jQuery --> <!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script> <script src="js/jquery.min.js"></script>
<!-- Bootstrap --> <!-- Bootstrap -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> <script src="js/popper.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> <script src="js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css"> <link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/bootstrap-icons.css">
<!-- FontAwesome --> <!-- PDF.js -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.1/css/all.css"> <script src="pdfjs/pdf.min.js"></script>
<link href="pdfjs/pdf_viewer.min.css" rel="stylesheet">
<!-- PDF.js --> <!-- Custom -->
<script src="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.3.122/build/pdf.min.js"></script> <link rel="stylesheet" href="css/general.css">
<link href="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.3.122/web/pdf_viewer.min.css" rel="stylesheet"> <link rel="stylesheet" th:href="@{css/dark-mode.css}" id="dark-mode-styles">
<!-- Custom --> <script>
<link rel="stylesheet" href="general.css">
<link rel="stylesheet" th:href="@{dark-mode.css}" id="dark-mode-styles">
<script>
function toggleDarkMode() { function toggleDarkMode() {
var checkbox = document.getElementById("toggle-dark-mode"); var checkbox = document.getElementById("toggle-dark-mode");
var darkModeStyles = document.getElementById("dark-mode-styles"); var darkModeStyles = document.getElementById("dark-mode-styles");
@ -54,9 +52,9 @@
</head> </head>
<th:block th:fragment="filelist"> <th:block th:fragment="filelist">
<div id="fileList"></div> <div id="fileList"></div>
<div id="fileList2"></div> <div id="fileList2"></div>
<script> <script>
var input = document.getElementById("fileInput"); var input = document.getElementById("fileInput");
var output = document.getElementById("fileList"); var output = document.getElementById("fileList");
@ -71,7 +69,7 @@
output.innerHTML = fileNames; output.innerHTML = fileNames;
}); });
</script> </script>
<script> <script>
var input2 = document.getElementById("fileInput2"); var input2 = document.getElementById("fileInput2");
var output2 = document.getElementById("fileList2"); var output2 = document.getElementById("fileList2");
if (input2 != null && !input2) { if (input2 != null && !input2) {
@ -89,7 +87,7 @@
</script> </script>
<script> <script>
if (dropContainer) { if (dropContainer) {
dropContainer.ondragover = dropContainer.ondragenter = function(evt) { dropContainer.ondragover = dropContainer.ondragenter = function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -112,14 +110,14 @@
</th:block> </th:block>
<th:block th:fragment="fileSelector(name, multiple)"> <th:block th:fragment="fileSelector(name, multiple)">
<div class="custom-file-chooser"> <div class="custom-file-chooser">
<div class="custom-file"> <div class="custom-file">
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:multiple="${multiple}"> <input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:multiple="${multiple}">
<label class="custom-file-label" th:for="${name}+'-input'" th:text="#{pdfPrompt}"></label> <label class="custom-file-label" th:for="${name}+'-input'" th:text="#{pdfPrompt}"></label>
</div> </div>
</div> </div>
<script th:inline="javascript"> <script th:inline="javascript">
$([[${"#"+name+"-input"}]]).on("change", function() { $([[${"#"+name+"-input"}]]).on("change", function() {
const files = $(this).get(0).files; const files = $(this).get(0).files;
const fileNames = Array.from(files).map(f => f.name).join(", "); const fileNames = Array.from(files).map(f => f.name).join(", ");
@ -131,9 +129,9 @@
}); });
</script> </script>
<style> <style>
.custom-file-label { .custom-file-label {
padding-right: 90px; padding-right: 90px;
} }
</style> </style>
</th:block> </th:block>

View file

@ -1,6 +1,6 @@
<div th:fragment="footer"> <div th:fragment="footer">
<footer id="footer" class="text-center py-3"> <footer id="footer" class="text-center py-3">
<a href="https://github.com/Frooodle/Stirling-PDF" target="_blank" class="mx-1"> <i class="fab fa-github fa-2x"></i></a> <a href="https://github.com/Frooodle/Stirling-PDF" target="_blank" class="mx-1"><img src="images/github.svg"></img></a>
<a href="https://hub.docker.com/r/frooodle/s-pdf" target="_blank" class="mx-1"> <i class="fab fa-docker fa-2x"></i></a> <a href="https://hub.docker.com/r/frooodle/s-pdf" target="_blank" class="mx-1"><img src="images/docker.svg"></img></a>
</footer> </footer>
</div> </div>

View file

@ -45,6 +45,7 @@
<a class="dropdown-item" href="#" th:href="@{remove-password}" th:classappend="${currentPage}=='remove-password' ? 'active' : ''" th:text="#{home.removePassword.title}"></a> <a class="dropdown-item" href="#" th:href="@{remove-password}" th:classappend="${currentPage}=='remove-password' ? 'active' : ''" th:text="#{home.removePassword.title}"></a>
<a class="dropdown-item" href="#" th:href="@{change-permissions}" th:classappend="${currentPage}=='change-permissions' ? 'active' : ''" th:text="#{home.permissions.title}"></a> <a class="dropdown-item" href="#" th:href="@{change-permissions}" th:classappend="${currentPage}=='change-permissions' ? 'active' : ''" th:text="#{home.permissions.title}"></a>
<a class="dropdown-item" href="#" th:href="@{add-watermark}" th:classappend="${currentPage}=='add-watermark' ? 'active' : ''" th:text="#{home.watermark.title}"></a> <a class="dropdown-item" href="#" th:href="@{add-watermark}" th:classappend="${currentPage}=='add-watermark' ? 'active' : ''" th:text="#{home.watermark.title}"></a>
<a class="dropdown-item" href="#" th:href="@{change-metadata}" th:classappend="${currentPage}=='change-metadata' ? 'active' : ''" th:text="#{home.changeMetadata.title}"></a>
</div> </div>
</li> </li>
@ -64,11 +65,11 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''"></a> <a class="nav-link" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''"></a>
</li> </li>
<input type="checkbox" id="toggle-dark-mode" checked="true" th:onclick="javascript:toggleDarkMode()"> <input type="checkbox" id="toggle-dark-mode" checked="true" th:onclick="javascript:toggleDarkMode()">
<a class="nav-link" href="#" for="toggle-dark-mode" th:text="#{navbar.darkmode}"></a> <a class="nav-link" href="#" for="toggle-dark-mode" th:text="#{navbar.darkmode}"></a>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="bi bi-globe2"></i> <i class="bi bi-globe2"></i>
@ -77,7 +78,7 @@
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="en_US">English (US)</a> <a class="dropdown-item lang_dropdown-item" href="" data-language-code="en_US">English (US)</a>
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="en_GB">English (UK)</a> <a class="dropdown-item lang_dropdown-item" href="" data-language-code="en_GB">English (UK)</a>
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="ar_AR">العربية</a> <a class="dropdown-item lang_dropdown-item" href="" data-language-code="ar_AR">العربية</a>
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="de_DE">German</a> <a class="dropdown-item lang_dropdown-item" href="" data-language-code="de_DE">Deutsch</a>
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="fr_FR">Français</a> <a class="dropdown-item lang_dropdown-item" href="" data-language-code="fr_FR">Français</a>
</div> </div>
</li> </li>

View file

@ -5,62 +5,63 @@
<th:block th:insert="~{fragments/common :: head(title='')}"></th:block> <th:block th:insert="~{fragments/common :: head(title='')}"></th:block>
<style> <style>
.features-container { .features-container {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr)); grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr));
gap: 25px 30px; gap: 25px 30px;
} }
.feature-card {
border: 1px solid rgba(0,0,0,.125);
border-radius: 0.25rem;
padding: 1.25rem;
display: flex; .feature-card {
flex-direction: column; border: 1px solid rgba(0, 0, 0, .125);
align-items: flex-start; border-radius: 0.25rem;
} padding: 1.25rem;
.feature-card .card-text { display: flex;
flex: 1; flex-direction: column;
} align-items: flex-start;
}
.feature-card .card-text {
flex: 1;
}
</style> </style>
<body> <body>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<!-- Jumbotron --> <!-- Jumbotron -->
<div class="jumbotron jumbotron-fluid" id="jumbotron"> <div class="jumbotron jumbotron-fluid" id="jumbotron">
<div class="container"> <div class="container">
<h1 class="display-4">Stirling PDF</h1> <h1 class="display-4">Stirling PDF</h1>
<p class="lead" th:text="#{home.desc}"></p> <p class="lead" th:text="#{home.desc}"></p>
</div> </div>
</div> </div>
<!-- Features --> <!-- Features -->
<div class="features-container container"> <div class="features-container container">
<div th:replace="~{fragments/card :: card(cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.rotate.title}, cardText=#{home.rotate.desc}, cardLink='rotate-pdf')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.rotate.title}, cardText=#{home.rotate.desc}, cardLink='rotate-pdf')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.imageToPdf.title}, cardText=#{home.imageToPdf.desc}, cardLink='img-to-pdf')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.imageToPdf.title}, cardText=#{home.imageToPdf.desc}, cardLink='img-to-pdf')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfToImage.title}, cardText=#{home.pdfToImage.desc}, cardLink='pdf-to-img')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfToImage.title}, cardText=#{home.pdfToImage.desc}, cardLink='pdf-to-img')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfOrganiser.title}, cardText=#{home.pdfOrganiser.desc}, cardLink='pdf-organizer')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfOrganiser.title}, cardText=#{home.pdfOrganiser.desc}, cardLink='pdf-organizer')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.addImage.title}, cardText=#{home.addImage.desc}, cardLink='add-image')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.addImage.title}, cardText=#{home.addImage.desc}, cardLink='add-image')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.watermark.title}, cardText=#{home.watermark.desc}, cardLink='add-watermark')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.watermark.title}, cardText=#{home.watermark.desc}, cardLink='add-watermark')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.removePages.title}, cardText=#{home.removePages.desc}, cardLink='remove-pages')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.removePages.title}, cardText=#{home.removePages.desc}, cardLink='remove-pages')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.addPassword.title}, cardText=#{home.addPassword.desc}, cardLink='add-password')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.addPassword.title}, cardText=#{home.addPassword.desc}, cardLink='add-password')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.removePassword.title}, cardText=#{home.removePassword.desc}, cardLink='remove-password')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.removePassword.title}, cardText=#{home.removePassword.desc}, cardLink='remove-password')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf')}"></div> <div th:replace="~{fragments/card :: card(cardTitle=#{home.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.changeMetadata.title}, cardText=#{home.changeMetadata.desc}, cardLink='change-metadata')}"></div>
</div> </div>
</div> </div>
<div th:insert="~{fragments/footer.html :: footer}"></div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -4,130 +4,124 @@
<th:block th:insert="~{fragments/common :: head(title=#{merge.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{merge.title})}"></th:block>
<body> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<br> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<div class="container" id="dropContainer"> <div class="container" id="dropContainer">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{merge.header}"></h2> <h2 th:text="#{merge.header}"></h2>
<form action="merge-pdfs" method="post" <form action="merge-pdfs" method="post" enctype="multipart/form-data">
enctype="multipart/form-data"> <div class="form-group">
<div class="form-group"> <label th:text="#{multiPdfDropPrompt}"></label>
<label th:text="#{multiPdfDropPrompt}"></label> <div class="custom-file">
<div class="custom-file"> <input type="file" class="custom-file-input" id="fileInput" name="fileInput" multiple required>
<input type="file" class="custom-file-input" id="fileInput" <label class="custom-file-label" th:text="#{pdfPrompt}"></label>
name="fileInput" multiple required> <label </div>
class="custom-file-label" th:text="#{pdfPrompt}">s</label> </div>
</div> <div class="form-group">
</div> <ul id="selectedFiles" class="list-group"></ul>
<div class="form-group"> </div>
<ul id="selectedFiles" class="list-group"></ul> <div class="form-group text-center">
</div> <button type="submit" class="btn btn-primary" th:text="#{merge.submit}"></button>
<div class="form-group text-center"> </div>
<button type="submit" class="btn btn-primary" th:text="#{merge.submit}"></button> </form>
</div>
</form>
<script>
document
.getElementById(
"fileInput")
.addEventListener(
"change",
function() {
var files = this.files;
var list = document
.getElementById("selectedFiles");
list.innerHTML = "";
for (var i = 0; i < files.length; i++) {
var item = document
.createElement("li");
item.className = "list-group-item d-flex justify-content-between align-items-center";
item.textContent = files[i].name;
item.innerHTML += '<div><button class="btn btn-secondary move-up"><i class="fas fa-arrow-up"></i></button> <button class="btn btn-secondary move-down"><i class="fas fa-arrow-down"></i></button></div>';
list
.appendChild(item);
}
var moveUpButtons = document
.querySelectorAll(".move-up");
for (var i = 0; i < moveUpButtons.length; i++) {
moveUpButtons[i]
.addEventListener(
"click",
function(
event) {
event
.preventDefault();
var parent = this.parentNode.parentNode;
var grandParent = parent.parentNode;
if (parent.previousElementSibling) {
grandParent
.insertBefore(
parent,
parent.previousElementSibling);
updateFiles();
}
});
}
var moveDownButtons = document
.querySelectorAll(".move-down");
for (var i = 0; i < moveDownButtons.length; i++) {
moveDownButtons[i]
.addEventListener(
"click",
function(
event) {
event
.preventDefault();
var parent = this.parentNode.parentNode;
var grandParent = parent.parentNode;
if (parent.nextElementSibling) {
grandParent
.insertBefore(
parent.nextElementSibling,
parent);
updateFiles();
}
});
}
function updateFiles() {
var dataTransfer = new DataTransfer();
var liElements = document
.querySelectorAll("#selectedFiles li");
for (var i = 0; i < liElements.length; i++) {
var fileNameFromList = liElements[i].innerText
<script> .replace(
document "\nMove Up Move Down",
.getElementById("fileInput") "");
.addEventListener( var fileFromFiles
"change", for (var j = 0; j < files.length; j++) {
function() { var file = files[j];
var files = this.files; if (file.name === fileNameFromList) {
var list = document dataTransfer.items
.getElementById("selectedFiles"); .add(file);
list.innerHTML = ""; break;
for (var i = 0; i < files.length; i++) { }
var item = document
.createElement("li");
item.className = "list-group-item d-flex justify-content-between align-items-center";
item.textContent = files[i].name;
item.innerHTML += '<div><button class="btn btn-secondary move-up">Move Up</button> <button class="btn btn-secondary move-down">Move Down</button></div>';
list.appendChild(item);
}
var moveUpButtons = document
.querySelectorAll(".move-up");
for (var i = 0; i < moveUpButtons.length; i++) {
moveUpButtons[i]
.addEventListener(
"click",
function(event) {
event
.preventDefault();
var parent = this.parentNode.parentNode;
var grandParent = parent.parentNode;
if (parent.previousElementSibling) {
grandParent
.insertBefore(
parent,
parent.previousElementSibling);
updateFiles();
}
});
}
var moveDownButtons = document
.querySelectorAll(".move-down");
for (var i = 0; i < moveDownButtons.length; i++) {
moveDownButtons[i]
.addEventListener(
"click",
function(event) {
event
.preventDefault();
var parent = this.parentNode.parentNode;
var grandParent = parent.parentNode;
if (parent.nextElementSibling) {
grandParent
.insertBefore(
parent.nextElementSibling,
parent);
updateFiles();
}
});
}
function updateFiles() {
var dataTransfer = new DataTransfer();
var liElements = document
.querySelectorAll("#selectedFiles li");
for (var i = 0; i < liElements.length; i++) {
var fileNameFromList = liElements[i].innerText
.replace(
"\nMove Up Move Down",
"");
var fileFromFiles
for (var j = 0; j < files.length; j++) {
var file = files[j];
if (file.name === fileNameFromList) {
dataTransfer.items
.add(file);
break;
} }
} }
document
.getElementById("fileInput").files = dataTransfer.files;
} }
document });
.getElementById("fileInput").files = dataTransfer.files; </script>
} </div>
}); </div>
</script> </div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body> </body>
</html> </html>

View file

@ -4,37 +4,31 @@
<th:block th:insert="~{fragments/common :: head(title=#{pdfOrganiser.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{pdfOrganiser.title})}"></th:block>
<body> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<br> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{pdfOrganiser.header}"></h2> <h2 th:text="#{pdfOrganiser.header}"></h2>
<form th:action="@{rearrange-pages}" method="post"
enctype="multipart/form-data">
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput"
name="fileInput" required> <label
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
</div>
<div class="form-group">
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label> <input type="text" class="form-control"
id="fileInput" name="pageOrder"
placeholder="(e.g. 1,3,2 or 4-8,2,10-12)" required>
</div>
<button type="submit" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
</form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div> <form th:action="@{rearrange-pages}" method="post" enctype="multipart/form-data">
</div> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
</div> <div class="form-group">
</div> <label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
<div th:insert="~{fragments/footer.html :: footer}"></div> <input type="text" class="form-control" id="fileInput" name="pageOrder" placeholder="(e.g. 1,3,2 or 4-8,2,10-12)" required>
</div> </div>
<button type="submit" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
</form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body> </body>
</html> </html>

View file

@ -4,37 +4,31 @@
<th:block th:insert="~{fragments/common :: head(title=#{pageRemover.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{pageRemover.title})}"></th:block>
<body> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<br> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{pageRemover.header}"></h2> <h2 th:text="#{pageRemover.header}"></h2>
<form th:action="@{remove-pages}" method="post" <form th:action="@{remove-pages}" method="post" enctype="multipart/form-data">
enctype="multipart/form-data"> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div class="custom-file"> <div class="form-group">
<input type="file" class="custom-file-input" id="fileInput" <label for="pagesToDelete" th:text="#{pageRemover.pagesToDelete}"></label>
name="fileInput" required> <label <input type="text" class="form-control" id="fileInput" name="pagesToDelete" placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label> </div>
</div> <button type="submit" class="btn btn-primary" th:text="#{pageRemover.submit}"></button>
<div class="form-group"> </form>
<label for="pagesToDelete" th:text="#{pageRemover.pagesToDelete}"></label> <input type="text" class="form-control" <th:block th:insert="~{fragments/common :: filelist}"></th:block>
id="fileInput" name="pagesToDelete"
placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
</div>
<button type="submit" class="btn btn-primary" th:text="#{pageRemover.submit}"></button>
</form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div th:insert="~{fragments/footer.html :: footer}"></div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -5,45 +5,51 @@
<th:block th:insert="~{fragments/common :: head(title=#{rotate.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{rotate.title})}"></th:block>
<body> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<br> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{rotate.header}"></h2> <h2 th:text="#{rotate.header}"></h2>
<form action="#" th:action="@{rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data"> <form action="#" th:action="@{rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
<div th:replace="fragments/common :: fileSelector(name='fileInput', multiple=false)"></div> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<input type="hidden" id="angleInput" name="angle" value="0"> <input type="hidden" id="angleInput" name="angle" value="0">
<div id="editSection" style="display: none"> <div id="editSection" style="display: none">
<div class="previewContainer"> <div class="previewContainer">
<img id="pdf-preview"/> <img id="pdf-preview" />
</div> </div>
<div class="buttonContainer"> <div class="buttonContainer">
<button type="button" class="btn btn-secondary" onclick="rotate(-90)"> <button type="button" class="btn btn-secondary" onclick="rotate(-90)">
<i class="bi bi-arrow-counterclockwise"></i> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
</button> <path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" />
<button type="submit" class="btn btn-primary" th:text="#{rotate.submit}"></button> <path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
<button type="button" class="btn btn-secondary" onclick="rotate(90)"> </svg>
<i class="bi bi-arrow-clockwise"></i> </button>
</button> <button type="submit" class="btn btn-primary" th:text="#{rotate.submit}"></button>
</div> <button type="button" class="btn btn-secondary" onclick="rotate(90)">
</div> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
</form> <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
</svg>
</button>
</div>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div th:insert="~{fragments/footer.html :: footer}"></div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
<script> <script>
const angleInput = document.getElementById("angleInput"); const angleInput = document.getElementById("angleInput");
const fileInput = document.getElementById("fileInput-input"); const fileInput = document.getElementById("fileInput-input");
const preview = document.getElementById("pdf-preview"); const preview = document.getElementById("pdf-preview");
@ -90,36 +96,37 @@
angleInput.value = newAngle; angleInput.value = newAngle;
} }
</script> </script>
<style> <style>
#pdf-preview { #pdf-preview {
margin: 0 auto; margin: 0 auto;
display: block; display: block;
max-width: calc(100% - 30px); max-width: calc(100% - 30px);
max-height: calc(100% - 30px); max-height: calc(100% - 30px);
box-shadow: 0 0 4px rgba(100,100,100,.25); box-shadow: 0 0 4px rgba(100, 100, 100, .25);
transition: rotate .3s; transition: rotate .3s;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
translate: -50% -50%; translate: -50% -50%;
} }
.previewContainer {
aspect-ratio: 1;
width: 100%;
border: 1px solid rgba(0,0,0,.125);
border-radius: 0.25rem;
margin: 1rem 0;
padding: 15px;
display: block;
overflow: hidden;
position: relative;
}
.buttonContainer { .previewContainer {
display: flex; aspect-ratio: 1;
justify-content: space-around; width: 100%;
} border: 1px solid rgba(0, 0, 0, .125);
</style> border-radius: 0.25rem;
margin: 1rem 0;
padding: 15px;
display: block;
overflow: hidden;
position: relative;
}
.buttonContainer {
display: flex;
justify-content: space-around;
}
</style>
</body> </body>
</html> </html>

View file

@ -3,94 +3,78 @@
<th:block th:insert="~{fragments/common :: head(title=#{addPassword.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{addPassword.title})}"></th:block>
<body> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<br> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{addPassword.header}"></h2> <h2 th:text="#{addPassword.header}"></h2>
<form action="add-password" method="post" <form action="add-password" method="post" enctype="multipart/form-data">
enctype="multipart/form-data"> <div class="form-group">
<div class="form-group"> <label th:text="#{addPassword.selectText.1}"></label>
<label th:text="#{addPassword.selectText.1}"></label> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div class="custom-file"> </div>
<input type="file" class="custom-file-input" id="fileInput" <div class="form-group">
name="fileInput" required> <label <label th:text="#{addPassword.selectText.2}"></label> <input type="password" class="form-control" id="password" name="password" required>
class="custom-file-label" th:text="#{pdfPrompt}"></label> </div>
</div> <div class="form-group">
</div> <label th:text="#{addPassword.selectText.3}"></label> <select class="form-control" id="keyLength" name="keyLength">
<div class="form-group"> <option value="40">40</option>
<label th:text="#{addPassword.selectText.2}"></label> <input type="password" <option value="128">128</option>
class="form-control" id="password" name="password" required> <option value="256">256</option>
</div> </select> <small class="form-text text-muted" th:text="#{addPassword.selectText.4}"></small>
<div class="form-group"> </div>
<label th:text="#{addPassword.selectText.3}"></label> <select class="form-control" <div class="form-group">
id="keyLength" name="keyLength"> <label th:text="#{addPassword.selectText.5}"></label>
<option value="40">40</option> <div class="form-check">
<option value="128">128</option> <input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
<option value="256">256</option> <label class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label>
</select> <small class="form-text text-muted" th:text="#{addPassword.selectText.4}"></small> </div>
</div> <div class="form-check">
<div class="form-group"> <input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
<label th:text="#{addPassword.selectText.5}"></label> <label class="form-check-label" for="canExtractContent" th:text="#{addPassword.selectText.7}"></label>
<div class="form-check"> </div>
<input class="form-check-input" type="checkbox" <div class="form-check">
id="canAssembleDocument" name="canAssembleDocument"> <label <input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label> <label class="form-check-label" for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox" id="canFillInForm" name="canFillInForm">
id="canExtractContent" name="canExtractContent"> <label <label class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></label>
class="form-check-label" for="canExtractContent" th:text="#{addPassword.selectText.7}"></label> </div>
</div> <div class="form-check">
<div class="form-check"> <input class="form-check-input" type="checkbox" id="canModify" name="canModify">
<input class="form-check-input" type="checkbox" <label class="form-check-label" for="canModify" th:text="#{addPassword.selectText.10}"></label>
id="canExtractForAccessibility" </div>
name="canExtractForAccessibility"> <label <div class="form-check">
class="form-check-label" for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label> <input class="form-check-input" type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations">
</div> <label class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
<div class="form-check"> </div>
<input class="form-check-input" type="checkbox" <div class="form-check">
id="canFillInForm" name="canFillInForm"> <label <input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></label> <label class="form-check-label" for="canPrint" th:text="#{addPassword.selectText.12}"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="canModify" <input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
name="canModify"> <label class="form-check-label" <label class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
for="canModify" th:text="#{addPassword.selectText.10}"></label> </div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="canModifyAnnotations" name="canModifyAnnotations"> <label
class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="canPrint"
name="canPrint"> <label class="form-check-label"
for="canPrint" th:text="#{addPassword.selectText.12}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="canPrintFaithful" name="canPrintFaithful"> <label
class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
</div>
</div> </div>
<br /> <br />
<div class="form-group text-center"> <div class="form-group text-center">
<button type="submit" class="btn btn-primary" th:text="#{addPassword.submit}"></button> <button type="submit" class="btn btn-primary" th:text="#{addPassword.submit}"></button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div th:insert="~{fragments/footer.html :: footer}"></div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -3,54 +3,50 @@
<th:block th:insert="~{fragments/common :: head(title=#{watermark.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{watermark.title})}"></th:block>
<body> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<br> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{watermark.header}"></h2> <h2 th:text="#{watermark.header}"></h2>
<form method="post" enctype="multipart/form-data" action="add-watermark"> <form method="post" enctype="multipart/form-data" action="add-watermark">
<div class="form-group"> <div class="form-group">
<label th:text="#{watermark.selectText.1}"></label> <label th:text="#{watermark.selectText.1}"></label>
<div class="custom-file"> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<input type="file" class="custom-file-input" id="fileInput" </div>
name="fileInput" required> <label <div class="form-group">
class="custom-file-label" th:text="#{pdfPrompt}"></label> <label for="watermarkText" th:text="#{watermark.selectText.2}"></label>
</div> <input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="watermarkText" th:text="#{watermark.selectText.2}"></label> <label for="fontSize" th:text="#{watermark.selectText.3}"></label>
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required/> <input type="text" id="fontSize" name="fontSize" class="form-control" value="30" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="fontSize" th:text="#{watermark.selectText.3}"></label> <label for="rotation" th:text="#{watermark.selectText.4}"></label>
<input type="text" id="fontSize" name="fontSize" class="form-control" value="30"/> <input type="text" id="rotation" name="rotation" class="form-control" value="45" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="rotation" th:text="#{watermark.selectText.4}"></label> <label for="widthSpacer" th:text="#{watermark.selectText.5}"></label>
<input type="text" id="rotation" name="rotation" class="form-control" value="45"/> <input type="text" id="widthSpacer" name="widthSpacer" class="form-control" value="50" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="widthSpacer" th:text="#{watermark.selectText.5}"></label> <label for="heightSpacer" th:text="#{watermark.selectText.6}"></label>
<input type="text" id="widthSpacer" name="widthSpacer" class="form-control" value="50"/> <input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50" />
</div> </div>
<div class="form-group"> <div class="form-group text-center">
<label for="heightSpacer" th:text="#{watermark.selectText.6}"></label> <input type="submit" th:value="#{watermark.submit}" class="btn btn-primary" />
<input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50"/> </div>
</div> </form>
<div class="form-group text-center"> </div>
<input type="submit" th:value="#{watermark.submit}" class="btn btn-primary"/> </div>
</div> </div>
</form> </div>
</div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body> </body>
</html> </html>

View file

@ -0,0 +1,254 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{changeMetadata.title})}"></th:block>
<body>
<div id="page-container">
<div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{changeMetadata.header}"></h2>
<form method="post" id="form1" enctype="multipart/form-data" th:action="@{/update-metadata}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
<div class="form-group-inline form-check">
<input type="checkbox" class="form-check-input" id="deleteAll" name="deleteAll">
<label class="ml-3" for="deleteAll" th:text="#{changeMetadata.selectText.2}" ></label>
</div>
<div class="form-group-inline form-check">
<input type="checkbox" class="form-check-input" id="customModeCheckbox">
<label class="ml-3" for="customModeCheckbox" th:text="#{changeMetadata.selectText.3}"></label>
</div>
<div class="form-group">
<label class="form-check-label" for="author" th:text="#{changeMetadata.author}"></label>
<input type="text" class="form-control" id="author" name="author">
</div>
<div class="form-group">
<label class="form-check-label" for="creationDate" th:text="#{changeMetadata.creationDate}"></label>
<input type="text" class="form-control" id="creationDate" name="creationDate" placeholder="2020/12/25 18:30:59">
</div>
<div class="form-group">
<label class="form-check-label" for="creator" th:text="#{changeMetadata.creator}"></label>
<input type="text" class="form-control" id="creator" name="creator">
</div>
<div class="form-group">
<label class="form-check-label" for="keywords" th:text="#{changeMetadata.keywords}"></label>
<input type="text" class="form-control" id="keywords" name="keywords">
</div>
<div class="form-group">
<label class="form-check-label" for="modificationDate" th:text="#{changeMetadata.modDate}"></label>
<input type="text" class="form-control" id="modificationDate" name="modificationDate" placeholder="2020/12/25 18:30:59">
</div>
<div class="form-group">
<label class="form-check-label" for="producer" th:text="#{changeMetadata.producer}"></label>
<input type="text" class="form-control" id="producer" name="producer">
</div>
<div class="form-group">
<label class="form-check-label" for="subject" th:text="#{changeMetadata.subject}"></label>
<input type="text" class="form-control" id="subject" name="subject">
</div>
<div class="form-group">
<label class="form-check-label" for="title" th:text="#{changeMetadata.title}"></label>
<input type="text" class="form-control" id="title" name="title">
</div>
<div class="form-group">
<label class="form-check-label" for="trapped" th:text="#{changeMetadata.trapped}"></label>
<select class="form-control" id="trapped" name="trapped">
<option value="True" th:text="#{true}"></option>
<option value="False" th:text="#{false}" selected></option>
<option value="Unknown" th:text="#{unknown}"></option>
</select>
</div>
<div id="customMetadata" style="display: none;">
<h3 th:text="#{changeMetadata.selectText.4}"></h3>
<div class="form-group" id="otherMetadataEntries"></div>
</div>
<div id="customMetadataEntries"></div>
<button type="button" class="btn btn-secondary" id="addMetadataBtn" th:text="#{changeMetadata.selectText.5}"></button>
<br>
<br>
<button class="btn btn-primary" type="submit" th:text="#{changeMetadata.submit}"></button>
<script>
const deleteAllCheckbox = document.querySelector("#deleteAll");
const inputs = document.querySelectorAll(".form-control");
const customMetadataDiv = document.getElementById('customMetadata');
const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries');
deleteAllCheckbox.addEventListener("change", function(event) {
if (event.target !== deleteAllCheckbox) {
return;
}
inputs.forEach(input => {
if (input === deleteAllCheckbox) {
return;
}
input.disabled = deleteAllCheckbox.checked;
});
});
const customModeCheckbox = document.getElementById('customModeCheckbox');
const addMetadataBtn = document.getElementById("addMetadataBtn");
const customMetadataFormContainer = document.getElementById("customMetadataEntries");
var count = 1;
const fileInput = document.querySelector("#fileInput-input");
const authorInput = document.querySelector("#author");
const creationDateInput = document.querySelector("#creationDate");
const creatorInput = document.querySelector("#creator");
const keywordsInput = document.querySelector("#keywords");
const modificationDateInput = document.querySelector("#modificationDate");
const producerInput = document.querySelector("#producer");
const subjectInput = document.querySelector("#subject");
const titleInput = document.querySelector("#title");
const trappedInput = document.querySelector("#trapped");
var lastPDFFileMeta = null;
fileInput.addEventListener("change", async function() {
while (otherMetadataEntriesDiv.firstChild) {
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
}
while (customMetadataFormContainer.firstChild) {
customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
}
const file = this.files[0];
var url = URL.createObjectURL(file)
const pdf = await pdfjsLib.getDocument(url).promise;
const pdfMetadata = await pdf.getMetadata();
lastPDFFile = pdfMetadata?.info
console.log(pdfMetadata);
authorInput.value = pdfMetadata?.info?.Author;
creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate);
creatorInput.value = pdfMetadata?.info?.Creator;
keywordsInput.value = pdfMetadata?.info?.Keywords;
modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate);
producerInput.value = pdfMetadata?.info?.Producer;
subjectInput.value = pdfMetadata?.info?.Subject;
titleInput.value = pdfMetadata?.info?.Title;
console.log(pdfMetadata?.info);
const trappedValue = pdfMetadata?.info?.Trapped;
// Get all options in the select element
const options = trappedInput.options;
// Loop through all options to find the one with a matching value
for (let i = 0; i < options.length; i++) {
if (options[i].value === trappedValue) {
options[i].selected = true;
break;
}
}
addExtra();
});
addMetadataBtn.addEventListener("click", () => {
const keyInput = document.createElement("input");
keyInput.type = "text";
keyInput.placeholder = "Key";
keyInput.className = "form-control";
keyInput.name = "customKey" + count;
const valueInput = document.createElement("input");
valueInput.type = "text";
valueInput.placeholder = "Value";
valueInput.className = "form-control";
valueInput.name = "customValue" + count;
count = count + 1;
const formGroup = document.createElement("div");
formGroup.className = "form-group";
formGroup.appendChild(keyInput);
formGroup.appendChild(valueInput);
customMetadataFormContainer.appendChild(formGroup);
});
function convertDateFormat(dateTimeString) {
if (!dateTimeString || dateTimeString.length < 17) {
return dateTimeString;
}
const year = dateTimeString.substring(2, 6);
const month = dateTimeString.substring(6, 8);
const day = dateTimeString.substring(8, 10);
const hour = dateTimeString.substring(10, 12);
const minute = dateTimeString.substring(12, 14);
const second = dateTimeString.substring(14, 16);
return year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second;
}
function addExtra() {
const event = document.getElementById("customModeCheckbox");
if (event.checked && lastPDFFile?.Custom) {
customMetadataDiv.style.display = 'block';
for (const [key, value] of Object.entries(lastPDFFile.Custom)) {
if (key === 'Author' || key === 'CreationDate' || key === 'Creator' || key === 'Keywords' || key === 'ModDate' || key === 'Producer' || key === 'Subject' || key === 'Title' || key === 'Trapped') {
continue;
}
const entryDiv = document.createElement('div');
entryDiv.className = 'form-group';
entryDiv.innerHTML = `<div class="form-group"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
otherMetadataEntriesDiv.appendChild(entryDiv);
}
} else {
customMetadataDiv.style.display = 'none';
while (otherMetadataEntriesDiv.firstChild) {
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
}
}
}
customModeCheckbox.addEventListener('change', (event) => {
addExtra();
});
</script>
</form>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>

View file

@ -4,81 +4,68 @@
<th:block th:insert="~{fragments/common :: head(title=#{permissions.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{permissions.title})}"></th:block>
<body> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<br> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{permissions.header}"></h2> <h2 th:text="#{permissions.header}"></h2>
<p th:text="#{permissions.warning}"></p> <p th:text="#{permissions.warning}"></p>
<form action="add-password" method="post" <form action="add-password" method="post" enctype="multipart/form-data">
enctype="multipart/form-data"> <div class="form-group">
<div class="form-group"> <label th:text="#{permissions.selectText.1}"></label>
<label th:text="#{permissions.selectText.1}"></label> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div class="custom-file"> </div>
<input type="file" class="custom-file-input" id="fileInput" <div class="form-group">
name="fileInput"> <label class="custom-file-label" th:text="#{pdfPrompt}"></label> <label th:text="#{permissions.selectText.2}"></label>
</div> <div class="form-check">
</div> <input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
<div class="form-group"> <label class="form-check-label" for="canAssembleDocument" th:text="#{permissions.selectText.3}"></label>
<label th:text="#{permissions.selectText.2}"></label> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
id="canAssembleDocument" name="canAssembleDocument"> <label <label class="form-check-label" for="canExtractContent" th:text="#{permissions.selectText.4}"></label>
class="form-check-label" for="canAssembleDocument" th:text="#{permissions.selectText.3}"></label> </div>
</div> <div class="form-check">
<div class="form-check"> <input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
<input class="form-check-input" type="checkbox" <label class="form-check-label" for="canExtractForAccessibility" th:text="#{permissions.selectText.5}"></label>
id="canExtractContent" name="canExtractContent"> <label </div>
class="form-check-label" for="canExtractContent" th:text="#{permissions.selectText.4}"></label> <div class="form-check">
</div> <input class="form-check-input" type="checkbox" id="canFillInForm" name="canFillInForm">
<div class="form-check"> <label class="form-check-label" for="canFillInForm" th:text="#{permissions.selectText.6}"></label>
<input class="form-check-input" type="checkbox" </div>
id="canExtractForAccessibility" <div class="form-check">
name="canExtractForAccessibility"> <label <input class="form-check-input" type="checkbox" id="canModify" name="canModify">
class="form-check-label" for="canExtractForAccessibility" th:text="#{permissions.selectText.5}"></label> <label class="form-check-label" for="canModify" th:text="#{permissions.selectText.7}"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations">
id="canFillInForm" name="canFillInForm"> <label <label class="form-check-label" for="canModifyAnnotations" th:text="#{permissions.selectText.8}"></label>
class="form-check-label" for="canFillInForm" th:text="#{permissions.selectText.6}"></label> </div>
</div> <div class="form-check">
<div class="form-check"> <input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
<input class="form-check-input" type="checkbox" id="canModify" <label class="form-check-label" for="canPrint" th:text="#{permissions.selectText.9}"></label>
name="canModify"> <label class="form-check-label" </div>
for="canModify" th:text="#{permissions.selectText.7}"></label> <div class="form-check">
</div> <input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
<div class="form-check"> <label class="form-check-label" for="canPrintFaithful" th:text="#{permissions.selectText.10}"></label>
<input class="form-check-input" type="checkbox" </div>
id="canModifyAnnotations" name="canModifyAnnotations"> <label
class="form-check-label" for="canModifyAnnotations" th:text="#{permissions.selectText.8}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="canPrint"
name="canPrint"> <label class="form-check-label"
for="canPrint" th:text="#{permissions.selectText.9}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="canPrintFaithful" name="canPrintFaithful"> <label
class="form-check-label" for="canPrintFaithful" th:text="#{permissions.selectText.10}"></label>
</div>
</div> </div>
<br /> <br />
<div class="form-group text-center"> <div class="form-group text-center">
<button type="submit" class="btn btn-primary" th:text="#{permissions.submit}"></button> <button type="submit" class="btn btn-primary" th:text="#{permissions.submit}"></button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div th:insert="~{fragments/footer.html :: footer}"></div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -3,40 +3,35 @@
<th:block th:insert="~{fragments/common :: head(title=#{removePassword.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{removePassword.title})}"></th:block>
<body> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<br> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{removePassword.header}"></h2> <h2 th:text="#{removePassword.header}"></h2>
<form action="add-password" method="post" <form action="add-password" method="post" enctype="multipart/form-data">
enctype="multipart/form-data"> <div class="form-group">
<div class="form-group"> <label th:text="#{removePassword.selectText.1}"></label>
<label th:text="#{removePassword.selectText.1}"></label> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div class="custom-file"> </div>
<input type="file" class="custom-file-input" id="fileInput" <div class="form-group">
name="fileInput" required> <label <label th:text="#{removePassword.selectText.2}"></label>
class="custom-file-label" th:text="#{pdfPrompt}"></label> <input type="password" class="form-control" id="password" name="password" required>
</div> </div>
</div> <br />
<div class="form-group"> <div class="form-group text-center">
<label th:text="#{removePassword.selectText.2}"></label> <input type="password" <button type="submit" class="btn btn-primary" th:text="#{removePassword.submit}"></button>
class="form-control" id="password" name="password" required> </div>
</div> </form>
<br /> </div>
<div class="form-group text-center"> </div>
<button type="submit" class="btn btn-primary" th:text="#{removePassword.submit}"></button> </div>
</div> </div>
</form> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body> </body>
</html> </html>

View file

@ -6,49 +6,39 @@
<body> <body>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h1 th:text="#{split.header}"></h1>
<p th:text="#{split.desc.1}"></p>
<p th:text="#{split.desc.2}"></p>
<p th:text="#{split.desc.3}"></p>
<p th:text="#{split.desc.4}"></p>
<p th:text="#{split.desc.5}"></p>
<p th:text="#{split.desc.6}"></p>
<p th:text="#{split.desc.7}"></p>
<p th:text="#{split.desc.8}"></p>
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <form th:action="@{split-pages}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<br> <div class="form-group">
<br> <label for="pages" th:text="#{split.splitPages}"></label>
<input type="text" class="form-control" id="pages" name="pages" placeholder="1,3,5-10" required>
<div class="container"> </div>
<div class="row justify-content-center"> <br>
<div class="col-md-6"> <button type="submit" class="btn btn-primary" th:text="#{split.submit}"></button>
<h1 th:text="#{split.header}"></h1> </form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
<p th:text="#{split.desc.1}"></p> </div>
<p th:text="#{split.desc.2}"></p> </div>
<p th:text="#{split.desc.3}"></p> </div>
<p th:text="#{split.desc.4}"></p> </div>
<p th:text="#{split.desc.5}"></p> <div th:insert="~{fragments/footer.html :: footer}"></div>
<p th:text="#{split.desc.6}"></p> </div>
<p th:text="#{split.desc.7}"></p>
<p th:text="#{split.desc.8}"></p>
<form th:action="@{split-pages}" method="post" enctype="multipart/form-data">
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" required>
<label class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
</div>
<div class="form-group">
<label for="pages" th:text="#{split.splitPages}"></label>
<input type="text" class="form-control" id="pages" name="pages" placeholder="1,3,5-10" required>
</div>
<br>
<button type="submit" class="btn btn-primary" th:text="#{split.submit}"></button>
</form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body> </body>
</html> </html>