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 ********
# 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
# supported CodeQL languages.
#
name: "CodeQL"
name: "Build repo"
on:
push:
@ -42,15 +41,15 @@ jobs:
java-version: '17'
distribution: 'temurin'
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: java
# - name: Initialize CodeQL
# uses: github/codeql-action/init@v2
# with:
# languages: java
- uses: gradle/gradle-build-action@v2.3.3
with:
gradle-version: 7.6
arguments: assemble --no-build-cache
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v2
#- name: Perform CodeQL analysis
# 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;
import java.util.Locale;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.SessionLocaleResolver;
import java.util.Locale;
@Configuration
public class Beans implements WebMvcConfigurer {

View file

@ -1,15 +1,9 @@
package stirling.software.SPDF.controller;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.slf4j.Logger;
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.stereotype.Controller;
import org.springframework.ui.Model;
@ -39,8 +33,8 @@ public class CompressController {
}
@PostMapping("/compress-pdf")
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile pdfFile,
@RequestParam("imageCompressionLevel") String imageCompressionLevel) throws IOException {
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("imageCompressionLevel") String imageCompressionLevel)
throws IOException {
// Load a sample PDF document
PdfDocument document = new PdfDocument();

View file

@ -33,8 +33,7 @@ public class MergeController {
}
@PostMapping("/merge-pdfs")
public ResponseEntity<InputStreamResource> mergePdfs(@RequestParam("fileInput") MultipartFile[] files)
throws IOException {
public ResponseEntity<InputStreamResource> mergePdfs(@RequestParam("fileInput") MultipartFile[] files) throws IOException {
// Read the input PDF files into PDDocument objects
List<PDDocument> documents = new ArrayList<>();
@ -49,8 +48,7 @@ public class MergeController {
mergedDoc.close();
// Create an InputStreamResource from the merged PDF
InputStreamResource resource = new InputStreamResource(
new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
// Return the merged PDF as a response
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.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@ -29,8 +27,7 @@ public class OverlayImageController {
}
@PostMapping("/add-image")
public ResponseEntity<byte[]> overlayImage(@RequestParam("fileInput") MultipartFile pdfFile,
@RequestParam("fileInput2") MultipartFile imageFile, @RequestParam("x") float x,
public ResponseEntity<byte[]> overlayImage(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("fileInput2") MultipartFile imageFile, @RequestParam("x") float x,
@RequestParam("y") float y) {
try {
byte[] pdfBytes = pdfFile.getBytes();

View file

@ -1,25 +1,10 @@
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.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.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;
@Controller
public class PdfController {
@ -31,13 +16,10 @@ public class PdfController {
return "redirect:/";
}
@GetMapping("/")
public String home(Model model) {
model.addAttribute("currentPage", "home");
return "home";
}
}

View file

@ -1,6 +1,5 @@
package stirling.software.SPDF.controller;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -9,9 +8,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger;
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.stereotype.Controller;
import org.springframework.ui.Model;
@ -40,8 +36,7 @@ public class RearrangePagesPDFController {
}
@PostMapping("/remove-pages")
public ResponseEntity<byte[]> deletePages(@RequestParam("fileInput") MultipartFile pdfFile,
@RequestParam("pagesToDelete") String pagesToDelete) throws IOException {
public ResponseEntity<byte[]> deletePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pagesToDelete") String pagesToDelete) throws IOException {
PDDocument document = PDDocument.load(pdfFile.getBytes());
@ -60,7 +55,7 @@ public class RearrangePagesPDFController {
}
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
for (String element : pageOrderArr) {
// check if the element contains a range of pages
@ -88,8 +83,7 @@ public class RearrangePagesPDFController {
}
@PostMapping("/rearrange-pages")
public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile,
@RequestParam("pageOrder") String pageOrder) {
public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) {
try {
// Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream());

View file

@ -1,19 +1,12 @@
package stirling.software.SPDF.controller;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.ListIterator;
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.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@ -36,8 +29,7 @@ public class RotationController {
}
@PostMapping("/rotate-pdf")
public ResponseEntity<byte[]> rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile,
@RequestParam("angle") Integer angle) throws IOException {
public ResponseEntity<byte[]> rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("angle") Integer angle) throws IOException {
// Load the PDF document
PDDocument document = PDDocument.load(pdfFile.getBytes());
@ -45,11 +37,7 @@ public class RotationController {
// Get the list of pages in the document
PDPageTree pages = document.getPages();
// Rotate all pages by the specified angle
Iterator<PDPage> iterPage = pages.iterator();
while (iterPage.hasNext()) {
PDPage page = iterPage.next();
for (PDPage page : pages) {
page.setRotation(page.getRotation() + angle);
}

View file

@ -46,8 +46,7 @@ public class SplitPDFController {
}
@PostMapping("/split-pages")
public ResponseEntity<Resource> splitPdf(@RequestParam("fileInput") MultipartFile file,
@RequestParam("pages") String pages) throws IOException {
public ResponseEntity<Resource> splitPdf(@RequestParam("fileInput") MultipartFile file, @RequestParam("pages") String pages) throws IOException {
// parse user input
// open the pdf document
@ -80,8 +79,7 @@ public class SplitPDFController {
}
}
logger.info("Splitting PDF into pages: {}",
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
@ -136,7 +134,7 @@ public class SplitPDFController {
ByteArrayResource resource = new ByteArrayResource(data);
new File("split_documents.zip").delete();
// return the Resource in the response
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=split_documents.zip")
.contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource);
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=split_documents.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(resource.contentLength()).body(resource);
}
}

View file

@ -2,8 +2,11 @@ package stirling.software.SPDF.controller.converters;
import java.io.IOException;
import org.apache.pdfbox.rendering.ImageType;
import org.slf4j.Logger;
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.HttpStatus;
import org.springframework.http.MediaType;
@ -44,16 +47,40 @@ public class ConvertImgPDFController {
}
@PostMapping("/pdf-to-img")
public ResponseEntity<byte[]> convertToImage(@RequestParam("fileInput") MultipartFile file,
@RequestParam("imageFormat") String imageFormat) throws IOException {
public ResponseEntity<Resource> convertToImage(@RequestParam("fileInput") MultipartFile file, @RequestParam("imageFormat") String imageFormat,
@RequestParam("singleOrMultiple") String singleOrMultiple, @RequestParam("colorType") String colorType, @RequestParam("dpi") String dpi) throws IOException {
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
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();
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
ResponseEntity<byte[]> response = new ResponseEntity<>(result, headers, HttpStatus.OK);
ResponseEntity<Resource> response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
return response;
} else {
ByteArrayResource resource = new ByteArrayResource(result);
// return the Resource in the response
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=converted_documents.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(resource.contentLength()).body(resource);
}
}
private String getMediaType(String imageFormat) {

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;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.slf4j.Logger;
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.stereotype.Controller;
import org.springframework.ui.Model;
@ -47,25 +41,20 @@ public class PasswordController {
}
@PostMapping("/remove-password")
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput,
@RequestParam(name = "password") String password) throws IOException {
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(name = "password") String password) throws IOException {
PDDocument document = PDDocument.load(fileInput.getBytes(), password);
document.setAllSecurityToBeRemoved(true);
return PdfUtils.pdfDocToWebResponse(document, fileInput.getName() + "_password_removed.pdf");
}
@PostMapping("/add-password")
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput,
@RequestParam(defaultValue = "", name = "password") String password,
@RequestParam(defaultValue = "128", name = "keyLength") int keyLength,
@RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument,
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(defaultValue = "", name = "password") String password,
@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 = "canExtractForAccessibility") boolean canExtractForAccessibility,
@RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm,
@RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
@RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
@RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations,
@RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint,
@RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
@RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, @RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
throws IOException {
PDDocument document = PDDocument.load(fileInput.getBytes());

View file

@ -1,7 +1,6 @@
package stirling.software.SPDF.controller.security;
import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
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.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
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.stereotype.Controller;
import org.springframework.ui.Model;
@ -34,12 +29,10 @@ public class WatermarkController {
}
@PostMapping("/add-watermark")
public ResponseEntity<byte[]> addWatermark(@RequestParam("fileInput") MultipartFile pdfFile,
@RequestParam("watermarkText") String watermarkText,
@RequestParam(defaultValue = "30", name = "fontSize") float fontSize,
@RequestParam(defaultValue = "0", name = "rotation") float rotation,
@RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer,
@RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer) throws IOException {
public ResponseEntity<byte[]> addWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText,
@RequestParam(defaultValue = "30", name = "fontSize") float fontSize, @RequestParam(defaultValue = "0", name = "rotation") float rotation,
@RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer, @RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer)
throws IOException {
// Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream());
@ -47,8 +40,7 @@ public class WatermarkController {
// Create a page in the document
for (PDPage page : document.getPages()) {
// Get the page's content stream
PDPageContentStream contentStream = new PDPageContentStream(document, page,
PDPageContentStream.AppendMode.APPEND, true);
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
// Set font of watermark
PDFont font = PDType1Font.HELVETICA_BOLD;
@ -67,8 +59,7 @@ public class WatermarkController {
// Add the watermark text
for (int i = 0; i < watermarkRows; i++) {
for (int j = 0; j < watermarkCols; j++) {
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation),
j * watermarkWidth, i * watermarkHeight));
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), j * watermarkWidth, i * watermarkHeight));
contentStream.showTextWithPositioning(new Object[] { watermarkText });
}
}

View file

@ -1,5 +1,6 @@
package stirling.software.SPDF.utils;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@ -7,12 +8,13 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
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.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import org.apache.pdfbox.pdmodel.PDDocument;
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))) {
// Create a PDFRenderer to convert the PDF to an image
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
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(imageType);
ImageWriter writer = writers.next();
// Create images of all pages
for (int i = 0; i < pageCount; i++) {
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();
try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) {
writer.setOutput(ios);
if (singleImage) {
// 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
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();
} catch (IOException e) {
@ -108,8 +138,7 @@ public class PdfUtils {
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) {
// Get the first page of the PDF
PDPage page = document.getPage(0);
try (PDPageContentStream contentStream = new PDPageContentStream(document, page,
PDPageContentStream.AppendMode.APPEND, true)) {
try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true)) {
// Create an image object from the image bytes
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "");
// Draw the image onto the page at the specified x and y coordinates
@ -150,8 +179,7 @@ public class PdfUtils {
return PdfUtils.boasToWebResponse(baos, docName);
}
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName)
throws IOException {
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException {
return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName);
}

View file

@ -16,6 +16,10 @@ genericSubmit=إرسال
processTimeWarning=تحذير: يمكن أن تستغرق هذه العملية ما يصل إلى دقيقة حسب حجم الملف
pageOrderPrompt=ترتيب الصفحات (أدخل قائمة بأرقام الصفحات مفصولة بفواصل):
goToPage=اذهب
true=\u0635\u062D\u064A\u062D
false=\u062E\u0637\u0623
unknown=\u063A\u064A\u0631 \u0645\u0639\u0631\u0648\u0641
#############
# HOME-PAGE #
#############
@ -65,7 +69,8 @@ home.removePassword.desc=إزالة الحماية بكلمة مرور من مس
home.compressPdfs.title=ضغط ملفات 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
addImage.title=إضافة صورة
@ -129,6 +134,13 @@ imageToPDF.submit=تحول
pdfToImage.title=تحويل PDF إلى صورة
pdfToImage.header=تحويل PDF إلى صورة
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=تحول
#addPassword
@ -183,3 +195,21 @@ removePassword.header=إزالة كلمة المرور (فك التشفير)
removePassword.selectText.1=حدد PDF لفك التشفير
removePassword.selectText.2=كلمة المرور
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
pageOrderPrompt=Seitenreihenfolge (Geben Sie eine durch Komma getrennte Liste von Seitenzahlen ein):
goToPage=Los
true=Wahr
false=Falsch
unknown=Unbekannt
#############
# HOME-PAGE #
#############
@ -61,7 +65,8 @@ home.removePassword.desc=Den Passwortschutz eines PDFs entfernen.
home.compressPdfs.title=PDF komprimieren
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
addImage.title=Bild hinzufügen
@ -126,6 +131,13 @@ imageToPDF.submit=Umwandeln
pdfToImage.title=PDF zu Bild
pdfToImage.header=PDF zu Bild
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
#addPassword
@ -182,7 +194,23 @@ removePassword.selectText.2=Passwort
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
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) :
goToPage=go
goToPage=Go
true=True
false=False
unknown=Unknown
#############
# HOME-PAGE #
#############
@ -61,6 +64,8 @@ home.removePassword.desc=Remove password protection from your PDF document.
home.compressPdfs.title=Compress PDFs
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
@ -125,6 +130,13 @@ imageToPDF.submit=Convert
pdfToImage.title=PDF to Image
pdfToImage.header=PDF to Image
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
#addPassword
@ -179,3 +191,48 @@ removePassword.header=Remove password (Decrypt)
removePassword.selectText.1=Select PDF to Decrypt
removePassword.selectText.2=Password
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
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) :
goToPage=go
goToPage=Go
true=True
false=False
unknown=Unknown
#############
# HOME-PAGE #
#############
@ -61,6 +64,8 @@ home.removePassword.desc=Remove password protection from your PDF document.
home.compressPdfs.title=Compress PDFs
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
@ -125,8 +130,16 @@ imageToPDF.submit=Convert
pdfToImage.title=PDF to Image
pdfToImage.header=PDF to Image
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
#addPassword
addPassword.title=Add Password
addPassword.header=Add password (Encrypt)
@ -180,8 +193,23 @@ removePassword.selectText.1=Select PDF to Decrypt
removePassword.selectText.2=Password
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
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) :
goToPage=aller
goToPage=Aller
true=Vrai
false=Faux
unknown=Inconnu
#############
# 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.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
@ -129,6 +135,13 @@ imageToPDF.submit=Convertir
pdfToImage.title=PDF vers image
pdfToImage.header=PDF vers 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
#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.2=Mot de passe
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="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br>
<br>
<br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{addImage.header}"></h2>
<form method="post" th:action="@{add-image}"
enctype="multipart/form-data">
<form method="post" th:action="@{add-image}" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput"
name="fileInput" required> <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>
<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 class="form-group">
<label for="x">X</label> <input type="number" class="form-control"
id="x" name="x" step="0.01" required>
<label for="x">X</label> <input type="number" class="form-control" id="x" name="x" step="0.01" required>
</div>
<div class="form-group">
<label for="y">Y</label> <input type="number" class="form-control"
id="y" name="y" step="0.01" required>
<label for="y">Y</label> <input type="number" class="form-control" id="y" name="y" step="0.01" required>
</div>
<button type="submit" class="btn btn-primary" th:text="#{addImage.submit}"></button>
</form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div>
</div>
</div>

View file

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

View file

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

View file

@ -7,23 +7,21 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 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 -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css">
<!-- FontAwesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.1/css/all.css">
<script src="js/popper.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/bootstrap-icons.css">
<!-- PDF.js -->
<script src="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.3.122/build/pdf.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.3.122/web/pdf_viewer.min.css" rel="stylesheet">
<script src="pdfjs/pdf.min.js"></script>
<link href="pdfjs/pdf_viewer.min.css" rel="stylesheet">
<!-- Custom -->
<link rel="stylesheet" href="general.css">
<link rel="stylesheet" th:href="@{dark-mode.css}" id="dark-mode-styles">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" th:href="@{css/dark-mode.css}" id="dark-mode-styles">
<script>
function toggleDarkMode() {

View file

@ -1,6 +1,6 @@
<div th:fragment="footer">
<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://hub.docker.com/r/frooodle/s-pdf" target="_blank" class="mx-1"> <i class="fab fa-docker 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"><img src="images/docker.svg"></img></a>
</footer>
</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="@{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="@{change-metadata}" th:classappend="${currentPage}=='change-metadata' ? 'active' : ''" th:text="#{home.changeMetadata.title}"></a>
</div>
</li>
@ -66,9 +67,9 @@
</li>
<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>
<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">
<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_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="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>
</div>
</li>

View file

@ -10,15 +10,16 @@
grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr));
gap: 25px 30px;
}
.feature-card {
border: 1px solid rgba(0, 0, 0, .125);
border-radius: 0.25rem;
padding: 1.25rem;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.feature-card .card-text {
flex: 1;
}
@ -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.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 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>
<body> <div id="page-container">
<body>
<div id="page-container">
<div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br>
<br>
<br> <br>
<div class="container" id="dropContainer">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{merge.header}"></h2>
<form action="merge-pdfs" method="post"
enctype="multipart/form-data">
<form action="merge-pdfs" method="post" enctype="multipart/form-data">
<div class="form-group">
<label th:text="#{multiPdfDropPrompt}"></label>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput"
name="fileInput" multiple required> <label
class="custom-file-label" th:text="#{pdfPrompt}">s</label>
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" multiple required>
<label class="custom-file-label" th:text="#{pdfPrompt}"></label>
</div>
</div>
<div class="form-group">
@ -31,13 +29,10 @@
</div>
</form>
<script>
document
.getElementById("fileInput")
.getElementById(
"fileInput")
.addEventListener(
"change",
function() {
@ -50,8 +45,9 @@
.createElement("li");
item.className = "list-group-item d-flex justify-content-between align-items-center";
item.textContent = files[i].name;
item.innerHTML += '<div><button class="btn btn-secondary move-up">Move Up</button> <button class="btn btn-secondary move-down">Move Down</button></div>';
list.appendChild(item);
item.innerHTML += '<div><button class="btn btn-secondary move-up"><i class="fas fa-arrow-up"></i></button> <button class="btn btn-secondary move-down"><i class="fas fa-arrow-down"></i></button></div>';
list
.appendChild(item);
}
var moveUpButtons = document
.querySelectorAll(".move-up");
@ -59,7 +55,8 @@
moveUpButtons[i]
.addEventListener(
"click",
function(event) {
function(
event) {
event
.preventDefault();
var parent = this.parentNode.parentNode;
@ -79,7 +76,8 @@
moveDownButtons[i]
.addEventListener(
"click",
function(event) {
function(
event) {
event
.preventDefault();
var parent = this.parentNode.parentNode;
@ -119,10 +117,6 @@
}
});
</script>
</div>
</div>
</div>

View file

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

View file

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

View file

@ -5,18 +5,18 @@
<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 th:insert="~{fragments/navbar.html :: navbar}"></div>
<br>
<br>
<br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{rotate.header}"></h2>
<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">
<div id="editSection" style="display: none">
@ -26,11 +26,17 @@
<div class="buttonContainer">
<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 type="submit" class="btn btn-primary" th:text="#{rotate.submit}"></button>
<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>
</div>
</div>
@ -103,6 +109,7 @@
left: 50%;
translate: -50% -50%;
}
.previewContainer {
aspect-ratio: 1;
width: 100%;

View file

@ -3,33 +3,26 @@
<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 th:insert="~{fragments/navbar.html :: navbar}"></div>
<br>
<br>
<br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{addPassword.header}"></h2>
<form action="add-password" method="post"
enctype="multipart/form-data">
<form action="add-password" method="post" enctype="multipart/form-data">
<div class="form-group">
<label th:text="#{addPassword.selectText.1}"></label>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput"
name="fileInput" required> <label
class="custom-file-label" th:text="#{pdfPrompt}"></label>
</div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
</div>
<div class="form-group">
<label th:text="#{addPassword.selectText.2}"></label> <input type="password"
class="form-control" id="password" name="password" required>
<label th:text="#{addPassword.selectText.2}"></label> <input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="form-group">
<label th:text="#{addPassword.selectText.3}"></label> <select class="form-control"
id="keyLength" name="keyLength">
<label th:text="#{addPassword.selectText.3}"></label> <select class="form-control" id="keyLength" name="keyLength">
<option value="40">40</option>
<option value="128">128</option>
<option value="256">256</option>
@ -38,45 +31,36 @@
<div class="form-group">
<label th:text="#{addPassword.selectText.5}"></label>
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="canAssembleDocument" name="canAssembleDocument"> <label
class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label>
<input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
<label class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="canExtractContent" name="canExtractContent"> <label
class="form-check-label" for="canExtractContent" th:text="#{addPassword.selectText.7}"></label>
<input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
<label class="form-check-label" for="canExtractContent" th:text="#{addPassword.selectText.7}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="canExtractForAccessibility"
name="canExtractForAccessibility"> <label
class="form-check-label" for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label>
<input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
<label class="form-check-label" for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="canFillInForm" name="canFillInForm"> <label
class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></label>
<input class="form-check-input" type="checkbox" id="canFillInForm" name="canFillInForm">
<label class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="canModify"
name="canModify"> <label class="form-check-label"
for="canModify" th:text="#{addPassword.selectText.10}"></label>
<input class="form-check-input" type="checkbox" id="canModify" name="canModify">
<label class="form-check-label" for="canModify" th:text="#{addPassword.selectText.10}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="canModifyAnnotations" name="canModifyAnnotations"> <label
class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
<input class="form-check-input" type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations">
<label class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="canPrint"
name="canPrint"> <label class="form-check-label"
for="canPrint" th:text="#{addPassword.selectText.12}"></label>
<input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
<label class="form-check-label" for="canPrint" th:text="#{addPassword.selectText.12}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="canPrintFaithful" name="canPrintFaithful"> <label
class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
<input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
<label class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
</div>
</div>

View file

@ -3,11 +3,11 @@
<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 th:insert="~{fragments/navbar.html :: navbar}"></div>
<br>
<br>
<br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
@ -16,11 +16,7 @@
<form method="post" enctype="multipart/form-data" action="add-watermark">
<div class="form-group">
<label th:text="#{watermark.selectText.1}"></label>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput"
name="fileInput" required> <label
class="custom-file-label" th:text="#{pdfPrompt}"></label>
</div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
</div>
<div class="form-group">
<label for="watermarkText" th:text="#{watermark.selectText.2}"></label>

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

View file

@ -3,29 +3,24 @@
<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 th:insert="~{fragments/navbar.html :: navbar}"></div>
<br>
<br>
<br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{removePassword.header}"></h2>
<form action="add-password" method="post"
enctype="multipart/form-data">
<form action="add-password" method="post" enctype="multipart/form-data">
<div class="form-group">
<label th:text="#{removePassword.selectText.1}"></label>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput"
name="fileInput" required> <label
class="custom-file-label" th:text="#{pdfPrompt}"></label>
</div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
</div>
<div class="form-group">
<label th:text="#{removePassword.selectText.2}"></label> <input type="password"
class="form-control" id="password" name="password" required>
<label th:text="#{removePassword.selectText.2}"></label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<br />
<div class="form-group text-center">

View file

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