Compare commits

...

13 Commits

Author SHA1 Message Date
Anthony Stirling
b40d324e04 nullcheck fixes 2023-02-11 15:17:17 +00:00
Anthony Stirling
54e1ced26a Update build.gradle 2023-02-11 14:28:06 +00:00
Anthony Stirling
c739c9dd2b 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
2023-02-11 14:27:15 +00:00
Anthony Stirling
aa9f8329d5 Merge pull request #43 from Sf298/localisationAndIcons
Manually added combined changes that wouldn't merge
2023-02-07 20:19:18 +00:00
Saud Fatayerji
9b3aac4a8a Combined changes that wouldn't merge 2023-02-07 23:14:03 +03:00
Saud Fatayerji
75d841083c Fixed formatting 2023-02-07 15:56:50 +00:00
Saud Fatayerji
a01ad71414 Added french and arabic to dropdown 2023-02-07 15:56:50 +00:00
Saud Fatayerji
9e6771d0f8 Fixed alignment for rtl languages. other alignment fixes 2023-02-07 15:56:50 +00:00
Saud Fatayerji
c83c57a37e Fixed file encoding for messages_xx.properties files 2023-02-07 15:56:50 +00:00
Saud Fatayerji
90ef6deca8 Dynamic set the html lang attribute 2023-02-07 15:56:50 +00:00
Saud Fatayerji
83bd2d5915 Added Arabic 2023-02-07 15:56:50 +00:00
Saud Fatayerji
879f558b44 Added French 2023-02-07 15:56:50 +00:00
Anthony Stirling
f7d320ac32 Update push-docker.yml 2023-02-06 12:39:26 +00:00
60 changed files with 12234 additions and 1566 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

View File

@@ -39,18 +39,18 @@ jobs:
username: ${{ secrets.DOCKER_HUB_USERNAME }} username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }} password: ${{ secrets.DOCKER_HUB_API }}
- name: Check if tag exists # - name: Check if tag exists
id: checkIdExists # id: checkIdExists
continue-on-error: true # continue-on-error: true
run: | # run: |
response=$(curl -s https://hub.docker.com/v2/repositories/frooodle/s-pdf/tags/?name=${{ steps.versionNumber.outputs.versionNumber }}) # response=$(curl -s https://hub.docker.com/v2/repositories/frooodle/s-pdf/tags/?name=${{ steps.versionNumber.outputs.versionNumber }})
result=$(echo $response | jq ".results") # result=$(echo $response | jq ".results")
if [ "$result" == "[]" ]; then # if [ "$result" == "[]" ]; then
echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} doesnt exist. Continuing with build and push." # echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} doesnt exist. Continuing with build and push."
else # else
echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} already exists. Skipping build and push." # echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} already exists. Skipping build and push."
exit 1; # exit 1;
fi # fi
@@ -58,9 +58,15 @@ jobs:
run: | run: |
docker buildx create --name mybuilder docker buildx create --name mybuilder
docker buildx use mybuilder docker buildx use mybuilder
- name: Build and push versioned amd64 and v8
if: github.ref == 'refs/heads/main'
run: |
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:${{ steps.versionNumber.outputs.versionNumber }}-alpha" .
- name: Build and push versioned amd64 and v8 - name: Build and push versioned amd64 and v8
if: github.ref == 'refs/heads/main' && steps.checkIdExists.outcome != 'failure' if: github.ref == 'refs/heads/master'
run: | run: |
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:${{ steps.versionNumber.outputs.versionNumber }}" . docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:${{ steps.versionNumber.outputs.versionNumber }}" .

2
.gitignore vendored
View File

@@ -108,3 +108,5 @@ local.properties
*.zip *.zip
*.tar.gz *.tar.gz
*.rar *.rar
/build

View File

@@ -5,7 +5,7 @@ plugins {
} }
group = 'stirling.software' group = 'stirling.software'
version = '0.3.2' version = '0.3.3'
sourceCompatibility = '17' sourceCompatibility = '17'
repositories { repositories {

BIN
docs/stirling-pdf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,215 @@
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
# Translated by Google Translate #
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
###########
# Generic #
###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=rtl
pdfPrompt=اختر PDF
multiPdfPrompt=اختر ملفات PDF (2+)
multiPdfDropPrompt=حدد (أو اسحب وأفلت) جميع ملفات PDF التي تحتاجها
imgPrompt=اختر صورة
genericSubmit=إرسال
processTimeWarning=تحذير: يمكن أن تستغرق هذه العملية ما يصل إلى دقيقة حسب حجم الملف
pageOrderPrompt=ترتيب الصفحات (أدخل قائمة بأرقام الصفحات مفصولة بفواصل):
goToPage=اذهب
true=\u0635\u062D\u064A\u062D
false=\u062E\u0637\u0623
unknown=\u063A\u064A\u0631 \u0645\u0639\u0631\u0648\u0641
#############
# HOME-PAGE #
#############
home.desc=متجرك الشامل المستضاف محليًا لجميع احتياجات PDF الخاصة بك.
navbar.convert=تحويل
navbar.security=الأمان
navbar.other=أخرى
navbar.darkmode=الوضع الداكن
home.merge.title=دمج ملفات PDF
home.merge.desc=دمج ملفات PDF متعددة في ملف واحد بسهولة.
home.split.title=انقسام ملفات PDF
home.split.desc=تقسيم ملفات PDF إلى مستندات متعددة
home.rotate.title=تدوير ملفات PDF
home.rotate.desc=قم بتدوير ملفات PDF الخاصة بك بسهولة.
home.imageToPdf.title=صورة إلى PDF
home.imageToPdf.desc=تحويل الصور (PNG ، JPEG ، GIF) إلى PDF.
home.pdfToImage.title=تحويل PDF إلى صورة
home.pdfToImage.desc=تحويل ملف PDF إلى صورة. (PNG ، JPEG ، GIF)
home.pdfOrganiser.title=منظم PDF
home.pdfOrganiser.desc=إزالة / إعادة ترتيب الصفحات بأي ترتيب
home.addImage.title=إضافة صورة إلى ملف PDF
home.addImage.desc=إضافة صورة إلى موقع معين في PDF (العمل قيد التقدم)
home.watermark.title=إضافة علامة مائية
home.watermark.desc=أضف علامة مائية مخصصة إلى مستند PDF الخاص بك.
home.permissions.title=تغيير الأذونات
home.permissions.desc=قم بتغيير أذونات مستند PDF الخاص بك
home.removePages.title=إزالة الصفحات
home.removePages.desc=حذف الصفحات غير المرغوب فيها من مستند PDF الخاص بك.
home.addPassword.title=إضافة كلمة مرور
home.addPassword.desc=تشفير مستند PDF الخاص بك بكلمة مرور.
home.removePassword.title=إزالة كلمة المرور
home.removePassword.desc=إزالة الحماية بكلمة مرور من مستند PDF الخاص بك.
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=إضافة صورة
addImage.header=إضافة صورة إلى PDF (العمل قيد التقدم)
addImage.submit=إضافة صورة
#compress
compress.title=ضغط
compress.header=ضغط ملف PDF
compress.compressLevel=القيمة بين 1 و 100 (يتم تقليل 1 إلى أقصى حد)
compress.submit=ضغط
#merge
merge.title=دمج
merge.header=دمج ملفات PDF متعددة (2+)
merge.submit=دمج
#pdfOrganiser
pdfOrganiser.title=منظم الصفحة
pdfOrganiser.header=منظم صفحات PDF
pdfOrganiser.submit=إعادة ترتيب الصفحات
#pageRemover
pageRemover.title=مزيل الصفحة
pageRemover.header=مزيل صفحة PDF
pageRemover.pagesToDelete=الصفحات المراد حذفها (أدخل قائمة بأرقام الصفحات مفصولة بفواصل):
pageRemover.submit=حذف الصفحات
#rotate
rotate.title=تدوير PDF
rotate.header=تدوير PDF
rotate.selectAngle=حدد زاوية الدوران (بمضاعفات 90 درجة):
rotate.submit=استدارة
#merge
split.title=انقسام PDF
split.header=تقسيم PDF
split.desc.1=الأرقام التي تحددها هي رقم الصفحة التي تريد تقسيمها
split.desc.2=على هذا النحو ، سيؤدي تحديد 1،3،7-8 إلى تقسيم مستند من 10 صفحات إلى 6 PDFS منفصلة مع:
split.desc.3=المستند رقم 1: الصفحة 1
split.desc.4=المستند رقم 2: الصفحتان 2 و 3
split.desc.5=المستند رقم 3: الصفحة 4 و 5 و 6
split.desc.6=المستند رقم 4: الصفحة 7
split.desc.7=المستند رقم 5: الصفحة 8
split.desc.8=المستند رقم 6: الصفحتان 9 و 10
split.splitPages=أدخل الصفحات المراد تقسيمها:
split.submit=Split
#merge
imageToPDF.title=صورة إلى PDF
imageToPDF.header=صورة إلى PDF
imageToPDF.submit=تحول
#pdfToImage
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
addPassword.title=إضافة كلمة مرور
addPassword.header=إضافة كلمة مرور (تشفير)
addPassword.selectText.1=حدد ملف PDF للتشفير
addPassword.selectText.2=كلمة المرور
addPassword.selectText.3=طول مفتاح التشفير
addPassword.selectText.4=القيم الأعلى تكون أقوى ، لكن القيم الأقل لها توافق أفضل.
addPassword.selectText.5=أذونات للتعيين
addPassword.selectText.6=منع تجميع المستند
addPassword.selectText.7=منع استخراج المحتوى
addPassword.selectText.8=منع الاستخراج للوصول
addPassword.selectText.9=منع ملء النموذج
addPassword.selectText.10=منع التعديل
addPassword.selectText.11=منع تعديل التعليقات التوضيحية
addPassword.selectText.12=منع الطباعة
addPassword.selectText.13=منع طباعة تنسيقات مختلفة
addPassword.submit=تشفير
#watermark
watermark.title=إضافة علامة مائية
watermark.header=إضافة علامة مائية
watermark.selectText.1=حدد PDF لإضافة العلامة المائية إلى:
watermark.selectText.2=نص العلامة المائية:
watermark.selectText.3=حجم الخط:
watermark.selectText.4=دوران (0-360):
watermark.selectText.5=widthSpacer (مسافة بين كل علامة مائية أفقيًا):
watermark.selectText.6=heightSpacer (مسافة بين كل علامة مائية عموديًا):
watermark.submit=إضافة علامة مائية
#Change permissions
permissions.title=تغيير الأذونات
permissions.header=تغيير الأذونات
permissions.warning=تحذير من أن تكون هذه الأذونات غير قابلة للتغيير ، يوصى بتعيينها بكلمة مرور عبر صفحة إضافة كلمة المرور
permissions.selectText.1=حدد ملف PDF لتغيير الأذونات
permissions.selectText.2=أذونات لتعيينها
permissions.selectText.3=منع تجميع المستند
permissions.selectText.4=منع استخراج المحتوى
permissions.selectText.5=منع الاستخراج للوصول
permissions.selectText.6=منع ملء النموذج
permissions.selectText.7=منع التعديل
permissions.selectText.8=منع تعديل التعليق التوضيحي
permissions.selectText.9=منع الطباعة
permissions.selectText.10=منع طباعة التنسيقات المختلفة
permissions.submit=تغيير
#remove password
removePassword.title=إزالة كلمة المرور
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

@@ -1,6 +1,9 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr
pdfPrompt=PDF auswählen pdfPrompt=PDF auswählen
multiPdfPrompt=PDFs auswählen(2+) multiPdfPrompt=PDFs auswählen(2+)
multiPdfDropPrompt=Wählen Sie alle gewünschten PDFs aus (oder ziehen Sie sie per Drag & Drop hierhin) multiPdfDropPrompt=Wählen Sie alle gewünschten PDFs aus (oder ziehen Sie sie per Drag & Drop hierhin)
@@ -9,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 #
############# #############
@@ -58,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
@@ -123,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
@@ -179,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

@@ -1,6 +1,9 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr
pdfPrompt=Choose PDF pdfPrompt=Choose PDF
multiPdfPrompt=Choose PDFs (2+) multiPdfPrompt=Choose PDFs (2+)
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
@@ -8,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 #
############# #############
@@ -58,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
@@ -122,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
@@ -177,9 +192,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

@@ -1,6 +1,9 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr
pdfPrompt=Choose PDF pdfPrompt=Choose PDF
multiPdfPrompt=Choose PDFs (2+) multiPdfPrompt=Choose PDFs (2+)
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
@@ -8,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 #
############# #############
@@ -58,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
@@ -122,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)
@@ -177,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

@@ -0,0 +1,216 @@
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
# Translated by Google Translate #
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
###########
# Generic #
###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr
pdfPrompt=Choisir PDF
multiPdfPrompt=Choisir des PDF (2+)
multiPdfDropPrompt=Sélectionnez (ou glissez-déposez) tous les PDF dont vous avez besoin
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
true=Vrai
false=Faux
unknown=Inconnu
#############
# HOME-PAGE #
#############
home.desc=Votre guichet unique hébergé localement pour tous vos besoins PDF.
navbar.convert=Convertir
navbar.security=Sécurité
navbar.other=Autre
navbar.darkmode=Mode sombre
home.merge.title=Fusionner des PDF
home.merge.desc=Fusionnez facilement plusieurs PDF en un seul.
home.split.title=Fractionner les PDF
home.split.desc=Diviser les PDF en plusieurs documents
home.rotate.title=Faire pivoter les PDF
home.rotate.desc=Faites pivoter facilement vos PDF.
home.imageToPdf.title=Image au format PDF
home.imageToPdf.desc=Convertir une image (PNG, JPEG, GIF) en PDF.
home.pdfToImage.title=PDF vers image
home.pdfToImage.desc=Convertir un PDF en image. (PNG, JPEG, GIF)
home.pdfOrganiser.title=Organisateur PDF
home.pdfOrganiser.desc=Supprimer/Réorganiser les pages dans n'importe quel ordre
home.addImage.title=Ajouter une image au PDF
home.addImage.desc=Ajoute une image à un emplacement défini sur le PDF (Travail en cours)
home.watermark.title=Ajouter un filigrane
home.watermark.desc=Ajoutez un filigrane personnalisé à votre document PDF.
home.permissions.title=Modifier les autorisations
home.permissions.desc=Modifier les permissions de votre document PDF
home.removePages.title=Supprimer des pages
home.removePages.desc=Supprimez les pages inutiles de votre document PDF.
home.addPassword.title=Ajouter un mot de passe
home.addPassword.desc=Cryptez votre document PDF avec un mot de passe.
home.removePassword.title=Supprimer le mot de passe
home.removePassword.desc=Supprimez la protection par mot de passe de votre document PDF.
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
addImage.title=Ajouter une image
addImage.header=Ajouter une image au PDF (Travail en cours)
addImage.submit=Ajouter une image
#compress
compress.title=Compresser
compress.header=Compresser le PDF
compress.compressLevel=Valeur entre 1 et 100 (1 étant le plus réduit)
compress.submit=Compresser
#merge
merge.title=Fusionner
merge.header=Fusionner plusieurs PDF (2+)
merge.submit=Fusionner
#pdfOrganiser
pdfOrganiser.title=Organisateur de pages
pdfOrganiser.header=Organisateur de pages PDF
pdfOrganiser.submit=Réorganiser les pages
#pageRemover
pageRemover.title=Suppresseur de pages
pageRemover.header=Outil de suppression de pages PDF
pageRemover.pagesToDelete=Pages à supprimer (Entrez une liste de numéros de page séparés par des virgules) :
pageRemover.submit=Supprimer des pages
#rotate
rotate.title=Faire pivoter le PDF
rotate.header=Faire pivoter le PDF
rotate.selectAngle=S\u00e9lectionner l'angle de rotation (en multiples de 90 degr\u00e9s):
rotate.submit=Rotation
#merge
split.title=Fractionner le PDF
split.header=Diviser le PDF
split.desc.1=Les numéros que vous sélectionnez sont le numéro de page sur lequel vous souhaitez faire un fractionnement
split.desc.2=Ainsi, la sélection de 1,3,7-8 diviserait un document de 10 pages en 6 PDF distincts avec :
split.desc.3=Document #1 : Page 1
split.desc.4=Document #2 : Pages 2 et 3
split.desc.5=Document #3 : Pages 4, 5 et 6
split.desc.6=Document #4 : Page 7
split.desc.7=Document #5 : Page 8
split.desc.8=Document #6 : Pages 9 et 10
split.splitPages=Entrez les pages sur lesquelles fractionner :
split.submit=Diviser
#merge
imageToPDF.title=Image au format PDF
imageToPDF.header=Image au format PDF
imageToPDF.submit=Convertir
#pdfToImage
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
addPassword.title=Ajouter un mot de passe
addPassword.header=Ajouter un mot de passe (chiffrer)
addPassword.selectText.1=Sélectionnez le PDF à chiffrer
addPassword.selectText.2=Mot de passe
addPassword.selectText.3=Longueur de la clé de chiffrement
addPassword.selectText.4=Les valeurs supérieures sont plus fortes, mais les valeurs inférieures ont une meilleure compatibilité.
addPassword.selectText.5=Autorisations à définir
addPassword.selectText.6=Empêcher l'assemblage du document
addPassword.selectText.7=Empêcher l'extraction de contenu
addPassword.selectText.8=Empêcher l'extraction pour l'accessibilité
addPassword.selectText.9=Empêcher de remplir le formulaire
addPassword.selectText.10=Empêcher la modification
addPassword.selectText.11=Empêcher la modification des annotations
addPassword.selectText.12=Empêcher l'impression
addPassword.selectText.13=Empêcher l'impression de différents formats
addPassword.submit=Crypter
#watermark
watermark.title=Ajouter un filigrane
watermark.header=Ajouter un filigrane
watermark.selectText.1=Sélectionnez le PDF auquel ajouter un filigrane :
watermark.selectText.2=Texte du filigrane :
watermark.selectText.3=Taille de la police :
watermark.selectText.4=Rotation (0-360) :
watermark.selectText.5=widthSpacer (Espace entre chaque filigrane horizontalement) :
watermark.selectText.6=heightSpacer (Espace entre chaque filigrane verticalement) :
watermark.submit=Ajouter un filigrane
#Change permissions
permissions.title=Modifier les autorisations
permissions.header=Modifier les autorisations
permissions.warning=Attention pour que ces permissions soient immuables il est recommandé de les définir avec un mot de passe via la page add-password
permissions.selectText.1=Sélectionnez PDF pour modifier les autorisations
permissions.selectText.2=Autorisations à définir
permissions.selectText.3=Employer l'assemblage du document
permissions.selectText.4=Employer l'extraction de contenu
permissions.selectText.5=Employer l'extraction pour l'accessibilité
permissions.selectText.6=Employer de remplir le formulaire
permissions.selectText.7=Employer la modification
permissions.selectText.8=Employer la modification des annotations
permissions.selectText.9=Employer l'impression
permissions.selectText.10=Emp�cher l'impression de diff�rents formats
permissions.submit=Modificateur
#supprimer le mot de passe
removePassword.title=Supprimer le mot de passe
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,23 @@
#page-container {
min-height: 100vh;
display: flex;
flex-direction: column;
}
#content-wrap {
flex: 1;
}
#footer {
bottom: 0;
width: 100%;
}
html[lang-direction=ltr] * {
direction: ltr;
}
html[lang-direction=rtl] * {
direction: rtl;
text-align: right;
}

View File

@@ -1,14 +0,0 @@
#page-container {
min-height: 100vh;
display: flex;
flex-direction: column;
}
#content-wrap {
flex: 1;
}
#footer {
bottom: 0;
width: 100%;
}

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.0 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

@@ -1,54 +1,40 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" 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=#{addImage.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{addImage.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="#{addImage.header}"></h2>
<h2 th:text="#{addImage.header}"></h2> <form method="post" th:action="@{add-image}" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput2" name="fileInput2" required>
<form method="post" th:action="@{add-image}" <label class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label>
enctype="multipart/form-data"> </div>
<div class="form-group">
<div class="custom-file"> <label for="x">X</label> <input type="number" class="form-control" id="x" name="x" step="0.01" required>
<input type="file" class="custom-file-input" id="fileInput" </div>
name="fileInput" required> <label <div class="form-group">
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label> <label for="y">Y</label> <input type="number" class="form-control" id="y" name="y" step="0.01" required>
</div> </div>
<div class="custom-file"> <button type="submit" class="btn btn-primary" th:text="#{addImage.submit}"></button>
<input type="file" class="custom-file-input" id="fileInput2" </form>
name="fileInput2" required> <label <th:block th:insert="~{fragments/common :: filelist}"></th:block>
class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label> </div>
</div> </div>
<div class="form-group"> </div>
<label for="x">X</label> <input type="number" class="form-control" </div>
id="x" name="x" step="0.01" required> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
<div class="form-group">
<label for="y">Y</label> <input type="number" class="form-control"
id="y" name="y" step="0.01" required>
</div>
<button type="submit" class="btn btn-primary" th:text="#{addImage.submit}"></button>
</form>
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body> </body>
</html> </html>

View File

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

View File

@@ -1,38 +1,35 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" 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=#{imageToPDF.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{imageToPDF.title})}"></th:block>
<body> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<br> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{imageToPDF.header}"></h2> <h2 th:text="#{imageToPDF.header}"></h2>
<form method="post" enctype="multipart/form-data" <form method="post" enctype="multipart/form-data" th:action="@{img-to-pdf}">
th:action="@{img-to-pdf}"> <div class="custom-file">
<div class="custom-file"> <input type="file" class="custom-file-input" id="fileInput" name="fileInput" required>
<input type="file" class="custom-file-input" id="fileInput" <label class="custom-file-label" for="fileInput" th:text="#{imgPrompt}"></label>
name="fileInput" required> <label </div>
class="custom-file-label" for="fileInput" th:text="#{imgPrompt}"></label> <br> <br>
</div> <button type="submit" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
<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> <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,43 +1,57 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" 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> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<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" <form method="post" enctype="multipart/form-data" th:action="@{pdf-to-img}">
th:action="@{pdf-to-img}"> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div class="custom-file"> <div class="form-group">
<input type="file" class="custom-file-input" id="fileInput" <label th:text="#{pdfToImage.selectText}"></label>
name="fileInput" required> <label <select class="form-control" name="imageFormat">
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label> <option value="png">PNG</option>
</div> </select>
<div class="form-group"> </div>
<label th:text="#{pdfToImage.selectText}"></label> <select class="form-control" <div class="form-group">
name="imageFormat"> <label th:text="#{pdfToImage.singleOrMultiple}"></label>
<option value="jpg">JPEG</option> <select class="form-control" name="singleOrMultiple">
<option value="png">PNG</option> <option value="single" th:text="#{pdfToImage.single}"></option>
<option value="gif">GIF</option> <option value="multiple" th:text="#{pdfToImage.multi}"></option>
</select> </select>
</div> </div>
<button type="submit" class="btn btn-primary" th:text="#{pdfToImage.submit}"></button> <div class="form-group">
</form> <label th:text="#{pdfToImage.colorType}"></label>
<th:block th:insert="~{fragments/common :: filelist}"></th:block> <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> </div>
</div> </div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div> </div>
</div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body> </body>
</html> </html>

View File

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

View File

@@ -1,49 +1,60 @@
<head th:fragment="head"> <head th:fragment="head">
<link rel="shortcut icon" href="favicon.svg">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script> <!-- Metadata -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title th:text="'S-PDF ' + ${title}"></title>
<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">
<script src="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.3.122/build/pdf.min.js"></script> <!-- jQuery -->
<link href="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.3.122/web/pdf_viewer.min.css" rel="stylesheet"> <script src="js/jquery.min.js"></script>
<!-- Bootstrap -->
<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="pdfjs/pdf.min.js"></script>
<link href="pdfjs/pdf_viewer.min.css" rel="stylesheet">
<!-- Custom -->
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" th:href="@{css/dark-mode.css}" id="dark-mode-styles">
<link rel="stylesheet" th:href="@{dark-mode.css}" id="dark-mode-styles">
<script> <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");
if (checkbox.checked) { if (checkbox.checked) {
localStorage.setItem("dark-mode", "on"); localStorage.setItem("dark-mode", "on");
darkModeStyles.disabled = false; darkModeStyles.disabled = false;
} else { } else {
localStorage.setItem("dark-mode", "off"); localStorage.setItem("dark-mode", "off");
darkModeStyles.disabled = true; darkModeStyles.disabled = true;
} }
}
$(document).ready(function() {
var darkModeStyles = document.getElementById("dark-mode-styles");
var checkbox = document.getElementById("toggle-dark-mode");
if (localStorage.getItem("dark-mode") == "on") {
darkModeStyles.disabled = false;
checkbox.checked = true;
}
if (localStorage.getItem("dark-mode") == "off") {
darkModeStyles.disabled = true;
checkbox.checked = false;
} }
$(document).ready(function() {
var darkModeStyles = document.getElementById("dark-mode-styles");
var checkbox = document.getElementById("toggle-dark-mode");
if (localStorage.getItem("dark-mode") == "on") {
darkModeStyles.disabled = false;
checkbox.checked = true;
}
if (localStorage.getItem("dark-mode") == "off") {
darkModeStyles.disabled = true;
checkbox.checked = false;
}
}); });
</script> </script>
<title th:text="'S-PDF ' + ${title}"></title>
<link rel="stylesheet" href="general.css">
</head> </head>
<th:block th:fragment="filelist"> <th:block th:fragment="filelist">
<div id="fileList"></div> <div id="fileList"></div>
<div id="fileList2"></div> <div id="fileList2"></div>
<script> <script>
var input = document.getElementById("fileInput"); var input = document.getElementById("fileInput");
var output = document.getElementById("fileList"); var output = document.getElementById("fileList");
@@ -58,7 +69,7 @@
output.innerHTML = fileNames; output.innerHTML = fileNames;
}); });
</script> </script>
<script> <script>
var input2 = document.getElementById("fileInput2"); var input2 = document.getElementById("fileInput2");
var output2 = document.getElementById("fileList2"); var output2 = document.getElementById("fileList2");
if (input2 != null && !input2) { if (input2 != null && !input2) {
@@ -76,7 +87,7 @@
</script> </script>
<script> <script>
if (dropContainer) { if (dropContainer) {
dropContainer.ondragover = dropContainer.ondragenter = function(evt) { dropContainer.ondragover = dropContainer.ondragenter = function(evt) {
evt.preventDefault(); evt.preventDefault();
@@ -99,14 +110,14 @@
</th:block> </th:block>
<th:block th:fragment="fileSelector(name, multiple)"> <th:block th:fragment="fileSelector(name, multiple)">
<div class="custom-file-chooser"> <div class="custom-file-chooser">
<div class="custom-file"> <div class="custom-file">
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:multiple="${multiple}"> <input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:multiple="${multiple}">
<label class="custom-file-label" th:for="${name}+'-input'" th:text="#{pdfPrompt}"></label> <label class="custom-file-label" th:for="${name}+'-input'" th:text="#{pdfPrompt}"></label>
</div> </div>
</div> </div>
<script th:inline="javascript"> <script th:inline="javascript">
$([[${"#"+name+"-input"}]]).on("change", function() { $([[${"#"+name+"-input"}]]).on("change", function() {
const files = $(this).get(0).files; const files = $(this).get(0).files;
const fileNames = Array.from(files).map(f => f.name).join(", "); const fileNames = Array.from(files).map(f => f.name).join(", ");
@@ -117,4 +128,10 @@
} }
}); });
</script> </script>
<style>
.custom-file-label {
padding-right: 90px;
}
</style>
</th:block> </th:block>

View File

@@ -1,11 +1,6 @@
<div th:fragment="footer"> <div th:fragment="footer">
<link rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.6.1/css/all.css">
<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" <a href="https://github.com/Frooodle/Stirling-PDF" target="_blank" class="mx-1"><img src="images/github.svg"></img></a>
class="mx-1"> <i class="fab fa-github fa-2x"></i> <a href="https://hub.docker.com/r/frooodle/s-pdf" target="_blank" class="mx-1"><img src="images/docker.svg"></img></a>
</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>
</footer> </footer>
</div> </div>

View File

@@ -1,132 +1,131 @@
<div th:fragment="navbar"> <div th:fragment="navbar">
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container"> <div class="container">
<a class="navbar-brand" href="#" th:href="@{/}">Stirling PDF</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" <a class="navbar-brand" href="#" th:href="@{/}">Stirling PDF</a>
data-target="#navbarNav" aria-controls="navbarNav"
aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-toggle="collapse"
<span class="navbar-toggler-icon"></span> data-target="#navbarNav" aria-controls="navbarNav"
</button> aria-expanded="false" aria-label="Toggle navigation">
<div class="collapse navbar-collapse" id="navbarNav"> <span class="navbar-toggler-icon"></span>
<ul class="navbar-nav"> </button>
<li class="nav-item"><a class="nav-link" href="#" <div class="collapse navbar-collapse" id="navbarNav">
th:href="@{merge-pdfs}"
th:classappend="${currentPage}=='merge-pdfs' ? 'active' : ''" th:text="#{home.merge.title}"></a></li> <ul class="navbar-nav">
<li class="nav-item"><a class="nav-link" href="#"
th:href="@{split-pdfs}" <li class="nav-item">
th:classappend="${currentPage}=='split-pdfs' ? 'active' : ''" th:text="#{home.split.title}"></a></li> <a class="nav-link" href="#" th:href="@{merge-pdfs}" th:classappend="${currentPage}=='merge-pdfs' ? 'active' : ''" th:text="#{home.merge.title}"></a>
<li class="nav-item"><a class="nav-link" href="#" </li>
th:href="@{pdf-organizer}"
th:classappend="${currentPage}=='pdf-organizer' ? 'active' : ''" th:text="#{home.pdfOrganiser.title}"></a></li> <li class="nav-item">
<a class="nav-link" href="#" th:href="@{split-pdfs}" th:classappend="${currentPage}=='split-pdfs' ? 'active' : ''" th:text="#{home.split.title}"></a>
<li class="nav-item"><a class="nav-link" href="#" </li>
th:href="@{rotate-pdf}"
th:classappend="${currentPage}=='rotate-pdf' ? 'active' : ''" th:text="#{home.rotate.title}"></a></li> <li class="nav-item">
<a class="nav-link" href="#" th:href="@{pdf-organizer}" th:classappend="${currentPage}=='pdf-organizer' ? 'active' : ''" th:text="#{home.pdfOrganiser.title}"></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" th:href="@{rotate-pdf}" th:classappend="${currentPage}=='rotate-pdf' ? 'active' : ''" th:text="#{home.rotate.title}"></a>
<li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-to-pdf' ? 'active' : ''"> </li>
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.convert}"></a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown"> <li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-to-pdf' ? 'active' : ''">
<a class="dropdown-item" href="#" th:href="@{pdf-to-img}" th:classappend="${currentPage}=='pdf-to-img' ? 'active' : ''" th:text="#{home.pdfToImage.title}"></a> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.convert}"></a>
<a class="dropdown-item" href="#" th:href="@{img-to-pdf}" th:classappend="${currentPage}=='img-to-pdf' ? 'active' : ''" th:text="#{home.imageToPdf.title}"></a> <div class="dropdown-menu" aria-labelledby="navbarDropdown">
</div> <a class="dropdown-item" href="#" th:href="@{pdf-to-img}" th:classappend="${currentPage}=='pdf-to-img' ? 'active' : ''" th:text="#{home.pdfToImage.title}"></a>
</li> <a class="dropdown-item" href="#" th:href="@{img-to-pdf}" th:classappend="${currentPage}=='img-to-pdf' ? 'active' : ''" th:text="#{home.imageToPdf.title}"></a>
</div>
<li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='change-permissions' OR ${currentPage}=='add-watermark' ? 'active' : ''"> </li>
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.security}"></a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown"> <li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='change-permissions' OR ${currentPage}=='add-watermark' ? 'active' : ''">
<a class="dropdown-item" href="#" th:href="@{add-password}" th:classappend="${currentPage}=='add-password' ? 'active' : ''" th:text="#{home.addPassword.title}"></a> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.security}"></a>
<a class="dropdown-item" href="#" th:href="@{remove-password}" th:classappend="${currentPage}=='remove-password' ? 'active' : ''" th:text="#{home.removePassword.title}"></a> <div class="dropdown-menu" aria-labelledby="navbarDropdown">
<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-password}" th:classappend="${currentPage}=='add-password' ? 'active' : ''" th:text="#{home.addPassword.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="@{remove-password}" th:classappend="${currentPage}=='remove-password' ? 'active' : ''" th:text="#{home.removePassword.title}"></a>
</div> <a class="dropdown-item" href="#" th:href="@{change-permissions}" th:classappend="${currentPage}=='change-permissions' ? 'active' : ''" th:text="#{home.permissions.title}"></a>
</li> <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>
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='add-image' OR ${currentPage}=='compress-pdf' ? 'active' : ''"> </div>
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.other}"></a> </li>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#" th:href="@{add-image}" th:classappend="${currentPage}=='add-image' ? 'active' : ''" th:text="#{home.addImage.title}"></a> <li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='add-image' OR ${currentPage}=='compress-pdf' ? 'active' : ''">
<a class="dropdown-item" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''" th:text="#{home.compressPdfs.title}"></a> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.other}"></a>
<a class="dropdown-item" href="#" th:href="@{remove-pages}" th:classappend="${currentPage}=='remove-pages' ? 'active' : ''" th:text="#{home.removePages.title}"></a> <div class="dropdown-menu" aria-labelledby="navbarDropdown">
</div> <a class="dropdown-item" href="#" th:href="@{add-image}" th:classappend="${currentPage}=='add-image' ? 'active' : ''" th:text="#{home.addImage.title}"></a>
</li> <a class="dropdown-item" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''" th:text="#{home.compressPdfs.title}"></a>
<a class="dropdown-item" href="#" th:href="@{remove-pages}" th:classappend="${currentPage}=='remove-pages' ? 'active' : ''" th:text="#{home.removePages.title}"></a>
</div>
</li>
<li class="nav-item"><a class="nav-link" href="#"
th:href="@{add-image}" <li class="nav-item">
th:classappend="${currentPage}=='add-image' ? 'active' : ''"></a></li> <a class="nav-link" href="#" th:href="@{add-image}" th:classappend="${currentPage}=='add-image' ? 'active' : ''"></a>
</li>
<li class="nav-item"><a class="nav-link" href="#"
th:href="@{compress-pdf}" <li class="nav-item">
th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''"></a></li> <a class="nav-link" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''"></a>
</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">
Language <i class="bi bi-globe2"></i>
</a> </a>
<div class="dropdown-menu" aria-labelledby="languageDropdown"> <div class="dropdown-menu" aria-labelledby="languageDropdown">
<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="de_DE">German</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">Deutsch</a>
</div> <a class="dropdown-item lang_dropdown-item" href="" data-language-code="fr_FR">Français</a>
</li> </div>
</li>
</ul>
</ul>
<script>
<script> document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function() { // Get the dropdown items
// Get the dropdown items var dropdownItems = document.querySelectorAll('.lang_dropdown-item');
var dropdownItems = document.querySelectorAll('.lang_dropdown-item');
// Loop through the dropdown items
// Loop through the dropdown items for (var i = 0; i < dropdownItems.length; i++) {
for (var i = 0; i < dropdownItems.length; i++) { dropdownItems[i].classList.remove('active');
dropdownItems[i].classList.remove('active'); if (dropdownItems[i].dataset.languageCode === localStorage.getItem('languageCode')) {
if(dropdownItems[i].dataset.languageCode === localStorage.getItem('languageCode')){ dropdownItems[i].classList.add('active');
dropdownItems[i].classList.add('active'); }
}
// Add a click event listener to each dropdown item // Add a click event listener to each dropdown item
dropdownItems[i].addEventListener('click', function(event) { dropdownItems[i].addEventListener('click', function(event) {
// Prevent the default link behavior
// Prevent the default link behavior event.preventDefault();
event.preventDefault();
// Get the language code
// Get the language code var languageCode = this.dataset.languageCode;
var languageCode = this.dataset.languageCode;
// Save the language code to local storage
// Save the language code to local storage localStorage.setItem('languageCode', languageCode);
localStorage.setItem('languageCode', languageCode);
// Get the current URL
// Get the current URL var currentUrl = window.location.href;
var currentUrl = window.location.href;
// Check if the URL already contains a "?lang" query parameter
// Check if the URL already contains a "?lang" query parameter if (currentUrl.indexOf('?lang=') === -1) {
if (currentUrl.indexOf('?lang=') === -1) { // Update the URL with the "?lang" query parameter
// Update the URL with the "?lang" query parameter window.location.href = currentUrl + '?lang=' + languageCode;
window.location.href = currentUrl + '?lang=' + languageCode; } else {
} else { // Replace the "?lang" query parameter with the new value
// Replace the "?lang" query parameter with the new value window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode);
window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode); }
} });
}); }
} });
}); </script>
</script> </div>
</div> </div>
</div> </nav>
</nav> </div>
</div>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,55 +1,55 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" 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=#{rotate.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{rotate.title})}"></th:block>
<body> <div id="page-container"> <body>
<div id="content-wrap"> <div id="page-container">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div id="content-wrap">
<br> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{rotate.header}"></h2> <h2 th:text="#{rotate.header}"></h2>
<form action="#" th:action="@{rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data"> <form action="#" th:action="@{rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
<div th:replace="fragments/common :: fileSelector(name='fileInput', multiple=false)"></div> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<input type="hidden" id="angleInput" name="angle" value="0"> <input type="hidden" id="angleInput" name="angle" value="0">
<div id="editSection" style="display: none"> <div id="editSection" style="display: none">
<div class="previewContainer"> <div class="previewContainer">
<img id="pdf-preview"/> <img id="pdf-preview" />
</div> </div>
<div class="buttonContainer"> <div class="buttonContainer">
<button type="button" class="btn btn-secondary" onclick="rotate(-90)"> <button type="button" class="btn btn-secondary" onclick="rotate(-90)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16"> <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 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"/> <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> </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)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16"> <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 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"/> <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> </svg>
</button> </button>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div th:insert="~{fragments/footer.html :: footer}"></div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
<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");
@@ -96,36 +96,37 @@
angleInput.value = newAngle; angleInput.value = newAngle;
} }
</script> </script>
<style> <style>
#pdf-preview { #pdf-preview {
margin: 0 auto; margin: 0 auto;
display: block; display: block;
max-width: calc(100% - 30px); max-width: calc(100% - 30px);
max-height: calc(100% - 30px); max-height: calc(100% - 30px);
box-shadow: 0 0 4px rgba(100,100,100,.25); box-shadow: 0 0 4px rgba(100, 100, 100, .25);
transition: rotate .3s; transition: rotate .3s;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
translate: -50% -50%; translate: -50% -50%;
} }
.previewContainer {
aspect-ratio: 1;
width: 100%;
border: 1px solid rgba(0,0,0,.125);
border-radius: 0.25rem;
margin: 1rem 0;
padding: 15px;
display: block;
overflow: hidden;
position: relative;
}
.buttonContainer { .previewContainer {
display: flex; aspect-ratio: 1;
justify-content: space-around; width: 100%;
} border: 1px solid rgba(0, 0, 0, .125);
</style> border-radius: 0.25rem;
margin: 1rem 0;
padding: 15px;
display: block;
overflow: hidden;
position: relative;
}
.buttonContainer {
display: flex;
justify-content: space-around;
}
</style>
</body> </body>
</html> </html>

View File

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

View File

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

View File

@@ -0,0 +1,263 @@
<!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);
if(!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) {
customModeCheckbox.disabled = true;
customModeCheckbox.checked = false;
} else {
customModeCheckbox.disabled = false;
}
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 != null) {
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

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

View File

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

View File

@@ -1,51 +1,44 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" 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=#{split.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{split.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"> <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> <p th:text="#{split.desc.4}"></p>
<p th:text="#{split.desc.4}"></p> <p th:text="#{split.desc.5}"></p>
<p th:text="#{split.desc.5}"></p> <p th:text="#{split.desc.6}"></p>
<p th:text="#{split.desc.6}"></p> <p th:text="#{split.desc.7}"></p>
<p th:text="#{split.desc.7}"></p> <p th:text="#{split.desc.8}"></p>
<p th:text="#{split.desc.8}"></p>
<form th:action="@{split-pages}" method="post" <form th:action="@{split-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="pages" th:text="#{split.splitPages}"></label> <input <label for="pages" th:text="#{split.splitPages}"></label>
type="text" class="form-control" id="pages" name="pages" <input type="text" class="form-control" id="pages" name="pages" placeholder="1,3,5-10" required>
placeholder="1,3,5-10" required> </div>
</div> <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>