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

@ -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;
@ -39,8 +33,8 @@ public class CompressController {
} }
@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();

View file

@ -33,8 +33,7 @@ public class MergeController {
} }
@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<>();
@ -49,8 +48,7 @@ public class MergeController {
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);

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;
@ -29,8 +27,7 @@ public class OverlayImageController {
} }
@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();

View file

@ -1,25 +1,10 @@
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 {
@ -31,13 +16,10 @@ public class PdfController {
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;
@ -40,8 +36,7 @@ public class RearrangePagesPDFController {
} }
@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()); PDDocument document = PDDocument.load(pdfFile.getBytes());
@ -60,7 +55,7 @@ public class RearrangePagesPDFController {
} }
private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) { private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) {
List<Integer> newPageOrder = new ArrayList<Integer>(); List<Integer> newPageOrder = new ArrayList<>();
// loop through the page order array // loop through the page order array
for (String element : pageOrderArr) { for (String element : pageOrderArr) {
// check if the element contains a range of pages // check if the element contains a range of pages
@ -88,8 +83,7 @@ public class RearrangePagesPDFController {
} }
@PostMapping("/rearrange-pages") @PostMapping("/rearrange-pages")
public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) {
@RequestParam("pageOrder") String pageOrder) {
try { try {
// Load the input PDF // Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream()); PDDocument document = PDDocument.load(pdfFile.getInputStream());

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;
@ -36,8 +29,7 @@ public class RotationController {
} }
@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());
@ -45,11 +37,7 @@ public class RotationController {
// 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();
while (iterPage.hasNext()) {
PDPage page = iterPage.next();
page.setRotation(page.getRotation() + angle); page.setRotation(page.getRotation() + angle);
} }

View file

@ -46,8 +46,7 @@ public class SplitPDFController {
} }
@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
@ -80,8 +79,7 @@ public class SplitPDFController {
} }
} }
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<>();
@ -136,7 +134,7 @@ public class SplitPDFController {
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;
@ -44,16 +47,40 @@ public class ConvertImgPDFController {
} }
@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(); byte[] pdfBytes = file.getBytes();
ImageType colorTypeResult = ImageType.RGB;
if ("greyscale".equals(colorType)) {
colorTypeResult = ImageType.GRAY;
} else if ("blackwhite".equals(colorType)) {
colorTypeResult = ImageType.BINARY;
}
// returns bytes for image // returns bytes for image
byte[] result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toLowerCase()); 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(); HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat))); headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0"); headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
ResponseEntity<byte[]> response = new ResponseEntity<>(result, headers, HttpStatus.OK); ResponseEntity<Resource> response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
return response; 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) { private String getMediaType(String imageFormat) {

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;
@ -47,25 +41,20 @@ public class PasswordController {
} }
@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 = "canAssembleDocument") boolean canAssembleDocument,
@RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent, @RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent,
@RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility, @RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility,
@RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
@RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
@RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations, @RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations,
@RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, @RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, @RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
@RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
throws IOException { throws IOException {
PDDocument document = PDDocument.load(fileInput.getBytes()); PDDocument document = PDDocument.load(fileInput.getBytes());

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;
@ -34,12 +29,10 @@ public class WatermarkController {
} }
@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());
@ -47,8 +40,7 @@ public class WatermarkController {
// 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;
@ -67,8 +59,7 @@ public class WatermarkController {
// 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 });
} }
} }

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;
@ -76,24 +78,52 @@ public class PdfUtils {
} }
} }
public static byte[] convertFromPdf(byte[] inputStream, String imageType) throws IOException { public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI)
throws IOException, Exception {
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) { try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
// Create a PDFRenderer to convert the PDF to an image
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));
}
// Create a ByteArrayOutputStream to save the image to if (singleImage) {
// Combine all images into a single big image
BufferedImage combined = new BufferedImage(images.get(0).getWidth(), images.get(0).getHeight() * pageCount, BufferedImage.TYPE_INT_RGB);
Graphics g = combined.getGraphics();
for (int i = 0; i < images.size(); i++) {
g.drawImage(images.get(i), 0, i * images.get(0).getHeight(), null);
}
images = Arrays.asList(combined);
}
// Create a ByteArrayOutputStream to save the image(s) to
ByteArrayOutputStream baos = new ByteArrayOutputStream(); 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
writer.write(new IIOImage(bim, null, null)); ImageIO.write(images.get(0), "PNG", baos);
// Log that the image was successfully written to the byte array // Log that the image was successfully written to the byte array
logger.info("Image successfully written to 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);
// Add the image to the zip file
zos.putNextEntry(new ZipEntry(String.format("page_%d.%s", i + 1, "png")));
zos.write(baosImage.toByteArray());
}
}
// Log that the images were successfully written to the byte array
logger.info("Images successfully written to byte array as a zip");
}
} }
return baos.toByteArray(); return baos.toByteArray();
} catch (IOException e) { } catch (IOException e) {
@ -108,8 +138,7 @@ public class PdfUtils {
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) { try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) {
// Get the first page of the PDF // Get the first page of the PDF
PDPage page = document.getPage(0); PDPage page = document.getPage(0);
try (PDPageContentStream contentStream = new PDPageContentStream(document, page, try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true)) {
PDPageContentStream.AppendMode.APPEND, true)) {
// Create an image object from the image bytes // Create an image object from the image bytes
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, ""); PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "");
// Draw the image onto the page at the specified x and y coordinates // Draw the image onto the page at the specified x and y coordinates
@ -150,8 +179,7 @@ public class PdfUtils {
return PdfUtils.boasToWebResponse(baos, docName); return PdfUtils.boasToWebResponse(baos, docName);
} }
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException {
throws IOException {
return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName); return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName);
} }

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
@ -183,3 +195,21 @@ 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
@ -179,3 +191,48 @@ 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
@ -183,3 +196,21 @@ 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

@ -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

@ -9,40 +9,26 @@
<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>
<form method="post" th:action="@{add-image}"
enctype="multipart/form-data">
<div class="custom-file"> <div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput" <input type="file" class="custom-file-input" id="fileInput2" name="fileInput2" required>
name="fileInput" required> <label <label class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label>
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
</div>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput2"
name="fileInput2" required> <label
class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="x">X</label> <input type="number" class="form-control" <label for="x">X</label> <input type="number" class="form-control" id="x" name="x" step="0.01" required>
id="x" name="x" step="0.01" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="y">Y</label> <input type="number" class="form-control" <label for="y">Y</label> <input type="number" class="form-control" id="y" name="y" step="0.01" required>
id="y" name="y" step="0.01" required>
</div> </div>
<button type="submit" class="btn btn-primary" th:text="#{addImage.submit}"></button> <button type="submit" class="btn btn-primary" th:text="#{addImage.submit}"></button>
</form> </form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block> <th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div> </div>
</div> </div>
</div> </div>

View file

@ -5,30 +5,22 @@
<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="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="#{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"
enctype="multipart/form-data">
<p th:text="#{processTimeWarning}"></p> <p th:text="#{processTimeWarning}"></p>
<div class="custom-file"> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<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"> <div class="form-group">
<label for="imageCompressionLevel" th:text="#{compress.compressLevel}"></label> <input type="number" class="form-control" <label for="imageCompressionLevel" th:text="#{compress.compressLevel}"></label>
id="imageCompressionLevel" name="imageCompressionLevel" step="1" <input type="number" class="form-control" id="imageCompressionLevel" name="imageCompressionLevel" step="1" value="1" min="1" max="100" required>
value="1" min="1" max="100" required>
</div> </div>
<button type="submit" class="btn btn-primary" th:text="#{compress.submit}"></button> <button type="submit" class="btn btn-primary" th:text="#{compress.submit}"></button>
</form> </form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block> <th:block th:insert="~{fragments/common :: filelist}"></th:block>

View file

@ -3,15 +3,12 @@
<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> <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">
@ -22,11 +19,10 @@
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" required> <input type="file" class="custom-file-input" id="fileInput" name="fileInput" required>
<label class="custom-file-label" for="fileInput" th:text="#{imgPrompt}"></label> <label class="custom-file-label" for="fileInput" th:text="#{imgPrompt}"></label>
</div> </div>
<br> <br> <br>
<br>
<button type="submit" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button> <button type="submit" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
</form>
</form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block> <th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div> </div>
</div> </div>
@ -34,6 +30,6 @@
</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

@ -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> <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="#{pdfToImage.header}"></h2> <h2 th:text="#{pdfToImage.header}"></h2>
<p th:text="#{processTimeWarning}"></p>
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-img}"> <form method="post" enctype="multipart/form-data" th:action="@{pdf-to-img}">
<div class="custom-file"> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" required> <div class="form-group">
<label class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label> <label th:text="#{pdfToImage.selectText}"></label>
<select class="form-control" name="imageFormat">
<option value="png">PNG</option>
</select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label th:text="#{pdfToImage.selectText}"></label> <select class="form-control" <label th:text="#{pdfToImage.singleOrMultiple}"></label>
name="imageFormat"> <select class="form-control" name="singleOrMultiple">
<option value="jpg">JPEG</option> <option value="single" th:text="#{pdfToImage.single}"></option>
<option value="png">PNG</option> <option value="multiple" th:text="#{pdfToImage.multi}"></option>
<option value="gif">GIF</option>
</select> </select>
</div> </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> <button type="submit" class="btn btn-primary" th:text="#{pdfToImage.submit}"></button>
</form> </form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block> <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

@ -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");

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>
@ -66,9 +67,9 @@
</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,23 +5,24 @@
<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); .feature-card {
border: 1px solid rgba(0, 0, 0, .125);
border-radius: 0.25rem; border-radius: 0.25rem;
padding: 1.25rem; padding: 1.25rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
} }
.feature-card .card-text {
.feature-card .card-text {
flex: 1; flex: 1;
} }
</style> </style>
<body> <body>
@ -56,7 +57,7 @@
<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>

View file

@ -4,23 +4,21 @@
<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="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" 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" <input type="file" class="custom-file-input" id="fileInput" name="fileInput" multiple required>
name="fileInput" multiple required> <label <label class="custom-file-label" th:text="#{pdfPrompt}"></label>
class="custom-file-label" th:text="#{pdfPrompt}">s</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -31,13 +29,10 @@
</div> </div>
</form> </form>
<script> <script>
document document
.getElementById("fileInput") .getElementById(
"fileInput")
.addEventListener( .addEventListener(
"change", "change",
function() { function() {
@ -50,8 +45,9 @@
.createElement("li"); .createElement("li");
item.className = "list-group-item d-flex justify-content-between align-items-center"; item.className = "list-group-item d-flex justify-content-between align-items-center";
item.textContent = files[i].name; 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>'; 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); list
.appendChild(item);
} }
var moveUpButtons = document var moveUpButtons = document
.querySelectorAll(".move-up"); .querySelectorAll(".move-up");
@ -59,7 +55,8 @@
moveUpButtons[i] moveUpButtons[i]
.addEventListener( .addEventListener(
"click", "click",
function(event) { function(
event) {
event event
.preventDefault(); .preventDefault();
var parent = this.parentNode.parentNode; var parent = this.parentNode.parentNode;
@ -79,7 +76,8 @@
moveDownButtons[i] moveDownButtons[i]
.addEventListener( .addEventListener(
"click", "click",
function(event) { function(
event) {
event event
.preventDefault(); .preventDefault();
var parent = this.parentNode.parentNode; var parent = this.parentNode.parentNode;
@ -119,10 +117,6 @@
} }
}); });
</script> </script>
</div> </div>
</div> </div>
</div> </div>

View file

@ -4,27 +4,21 @@
<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="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="#{pdfOrganiser.header}"></h2> <h2 th:text="#{pdfOrganiser.header}"></h2>
<form th:action="@{rearrange-pages}" method="post" <form th:action="@{rearrange-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">
<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"> <div class="form-group">
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label> <input type="text" class="form-control" <label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
id="fileInput" name="pageOrder" <input type="text" class="form-control" id="fileInput" name="pageOrder" placeholder="(e.g. 1,3,2 or 4-8,2,10-12)" required>
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> <button type="submit" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
</form> </form>

View file

@ -4,27 +4,21 @@
<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="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="#{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">
<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"> <div class="form-group">
<label for="pagesToDelete" th:text="#{pageRemover.pagesToDelete}"></label> <input type="text" class="form-control" <label for="pagesToDelete" th:text="#{pageRemover.pagesToDelete}"></label>
id="fileInput" name="pagesToDelete" <input type="text" class="form-control" id="fileInput" name="pagesToDelete" placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
</div> </div>
<button type="submit" class="btn btn-primary" th:text="#{pageRemover.submit}"></button> <button type="submit" class="btn btn-primary" th:text="#{pageRemover.submit}"></button>
</form> </form>

View file

@ -5,32 +5,38 @@
<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="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="#{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">
<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" />
<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" />
</svg>
</button> </button>
<button type="submit" class="btn btn-primary" th:text="#{rotate.submit}"></button> <button type="submit" class="btn btn-primary" th:text="#{rotate.submit}"></button>
<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-clockwise"></i> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
<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> </button>
</div> </div>
</div> </div>
@ -41,9 +47,9 @@
</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 {
.previewContainer {
aspect-ratio: 1; aspect-ratio: 1;
width: 100%; width: 100%;
border: 1px solid rgba(0,0,0,.125); border: 1px solid rgba(0, 0, 0, .125);
border-radius: 0.25rem; border-radius: 0.25rem;
margin: 1rem 0; margin: 1rem 0;
padding: 15px; padding: 15px;
display: block; display: block;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
} }
.buttonContainer { .buttonContainer {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
} }
</style> </style>
</body> </body>
</html> </html>

View file

@ -3,33 +3,26 @@
<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="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="#{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 class="custom-file"> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<input type="file" class="custom-file-input" id="fileInput"
name="fileInput" required> <label
class="custom-file-label" th:text="#{pdfPrompt}"></label>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label th:text="#{addPassword.selectText.2}"></label> <input type="password" <label th:text="#{addPassword.selectText.2}"></label> <input type="password" class="form-control" id="password" name="password" required>
class="form-control" id="password" name="password" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label th:text="#{addPassword.selectText.3}"></label> <select class="form-control" <label th:text="#{addPassword.selectText.3}"></label> <select class="form-control" id="keyLength" name="keyLength">
id="keyLength" name="keyLength">
<option value="40">40</option> <option value="40">40</option>
<option value="128">128</option> <option value="128">128</option>
<option value="256">256</option> <option value="256">256</option>
@ -38,45 +31,36 @@
<div class="form-group"> <div class="form-group">
<label th:text="#{addPassword.selectText.5}"></label> <label th:text="#{addPassword.selectText.5}"></label>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
id="canAssembleDocument" name="canAssembleDocument"> <label <label class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label>
class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></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="canExtractContent" name="canExtractContent">
id="canExtractContent" name="canExtractContent"> <label <label class="form-check-label" for="canExtractContent" th:text="#{addPassword.selectText.7}"></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" <input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
id="canExtractForAccessibility" <label class="form-check-label" for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label>
name="canExtractForAccessibility"> <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="canFillInForm" name="canFillInForm"> <label <label class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></label>
class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></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="canModify" name="canModify">
name="canModify"> <label class="form-check-label" <label class="form-check-label" for="canModify" th:text="#{addPassword.selectText.10}"></label>
for="canModify" th:text="#{addPassword.selectText.10}"></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="canModifyAnnotations" name="canModifyAnnotations"> <label <label class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="canPrint" <input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
name="canPrint"> <label class="form-check-label" <label class="form-check-label" for="canPrint" th:text="#{addPassword.selectText.12}"></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" <input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
id="canPrintFaithful" name="canPrintFaithful"> <label <label class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
</div> </div>
</div> </div>

View file

@ -3,11 +3,11 @@
<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="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">
@ -16,37 +16,33 @@
<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"
name="fileInput" required> <label
class="custom-file-label" th:text="#{pdfPrompt}"></label>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="watermarkText" th:text="#{watermark.selectText.2}"></label> <label for="watermarkText" th:text="#{watermark.selectText.2}"></label>
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required/> <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="fontSize" th:text="#{watermark.selectText.3}"></label> <label for="fontSize" th:text="#{watermark.selectText.3}"></label>
<input type="text" id="fontSize" name="fontSize" class="form-control" value="30"/> <input type="text" id="fontSize" name="fontSize" class="form-control" value="30" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="rotation" th:text="#{watermark.selectText.4}"></label> <label for="rotation" th:text="#{watermark.selectText.4}"></label>
<input type="text" id="rotation" name="rotation" class="form-control" value="45"/> <input type="text" id="rotation" name="rotation" class="form-control" value="45" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="widthSpacer" th:text="#{watermark.selectText.5}"></label> <label for="widthSpacer" th:text="#{watermark.selectText.5}"></label>
<input type="text" id="widthSpacer" name="widthSpacer" class="form-control" value="50"/> <input type="text" id="widthSpacer" name="widthSpacer" class="form-control" value="50" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="heightSpacer" th:text="#{watermark.selectText.6}"></label> <label for="heightSpacer" th:text="#{watermark.selectText.6}"></label>
<input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50"/> <input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50" />
</div> </div>
<div class="form-group text-center"> <div class="form-group text-center">
<input type="submit" th:value="#{watermark.submit}" class="btn btn-primary"/> <input type="submit" th:value="#{watermark.submit}" class="btn btn-primary" />
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

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,67 +4,54 @@
<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="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="#{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 class="custom-file"> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<input type="file" class="custom-file-input" id="fileInput"
name="fileInput"> <label class="custom-file-label" th:text="#{pdfPrompt}"></label>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label th:text="#{permissions.selectText.2}"></label> <label th:text="#{permissions.selectText.2}"></label>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
id="canAssembleDocument" name="canAssembleDocument"> <label <label class="form-check-label" for="canAssembleDocument" th:text="#{permissions.selectText.3}"></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" <input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
id="canExtractContent" name="canExtractContent"> <label <label class="form-check-label" for="canExtractContent" th:text="#{permissions.selectText.4}"></label>
class="form-check-label" for="canExtractContent" th:text="#{permissions.selectText.4}"></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="canExtractForAccessibility" name="canExtractForAccessibility">
id="canExtractForAccessibility" <label class="form-check-label" for="canExtractForAccessibility" th:text="#{permissions.selectText.5}"></label>
name="canExtractForAccessibility"> <label
class="form-check-label" for="canExtractForAccessibility" th:text="#{permissions.selectText.5}"></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="canFillInForm" name="canFillInForm"> <label <label class="form-check-label" for="canFillInForm" th:text="#{permissions.selectText.6}"></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="canModify" <input class="form-check-input" type="checkbox" id="canModify" name="canModify">
name="canModify"> <label class="form-check-label" <label class="form-check-label" for="canModify" th:text="#{permissions.selectText.7}"></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="canModifyAnnotations" name="canModifyAnnotations"> <label <label class="form-check-label" for="canModifyAnnotations" th:text="#{permissions.selectText.8}"></label>
class="form-check-label" for="canModifyAnnotations" th:text="#{permissions.selectText.8}"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="canPrint" <input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
name="canPrint"> <label class="form-check-label" <label class="form-check-label" for="canPrint" th:text="#{permissions.selectText.9}"></label>
for="canPrint" th:text="#{permissions.selectText.9}"></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="canPrintFaithful" name="canPrintFaithful">
id="canPrintFaithful" name="canPrintFaithful"> <label <label class="form-check-label" for="canPrintFaithful" th:text="#{permissions.selectText.10}"></label>
class="form-check-label" for="canPrintFaithful" th:text="#{permissions.selectText.10}"></label>
</div> </div>
</div> </div>

View file

@ -3,29 +3,24 @@
<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="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="#{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 class="custom-file"> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<input type="file" class="custom-file-input" id="fileInput"
name="fileInput" required> <label
class="custom-file-label" th:text="#{pdfPrompt}"></label>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label th:text="#{removePassword.selectText.2}"></label> <input type="password" <label th:text="#{removePassword.selectText.2}"></label>
class="form-control" id="password" name="password" required> <input type="password" class="form-control" id="password" name="password" required>
</div> </div>
<br /> <br />
<div class="form-group text-center"> <div class="form-group text-center">

View file

@ -6,19 +6,14 @@
<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">
<h1 th:text="#{split.header}"></h1> <h1 th:text="#{split.header}"></h1>
<p th:text="#{split.desc.1}"></p> <p th:text="#{split.desc.1}"></p>
<p th:text="#{split.desc.2}"></p> <p th:text="#{split.desc.2}"></p>
<p th:text="#{split.desc.3}"></p> <p th:text="#{split.desc.3}"></p>
@ -29,10 +24,8 @@
<p th:text="#{split.desc.8}"></p> <p th:text="#{split.desc.8}"></p>
<form th:action="@{split-pages}" method="post" enctype="multipart/form-data"> <form th:action="@{split-pages}" method="post" enctype="multipart/form-data">
<div class="custom-file"> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<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"> <div class="form-group">
<label for="pages" th:text="#{split.splitPages}"></label> <label for="pages" th:text="#{split.splitPages}"></label>
<input type="text" class="form-control" id="pages" name="pages" placeholder="1,3,5-10" required> <input type="text" class="form-control" id="pages" name="pages" placeholder="1,3,5-10" required>
@ -40,15 +33,12 @@
<br> <br>
<button type="submit" class="btn btn-primary" th:text="#{split.submit}"></button> <button type="submit" class="btn btn-primary" th:text="#{split.submit}"></button>
</form> </form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block> <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>