Compare commits

...

16 Commits

Author SHA1 Message Date
Anthony Stirling
56669f4247 Update build.gradle 2023-04-02 11:53:13 +01:00
Anthony Stirling
22be1a1104 envs 2023-04-02 11:51:07 +01:00
Anthony Stirling
a80c585086 Update build.gradle 2023-04-01 21:03:13 +01:00
Anthony Stirling
6d5dbd9729 Fixes and others (#83)
Features
-------------
Custom application name via APP_NAME docker env
(These next 3 are done with OCRMyPDF)
Extra features to OCR for scanned page cleanup (tilt/noise fixing)
Adding OCR ability to read and output to text file
Added Dedicated PDF/A conversion page 

Bug fixes
--------------
Fix concurrent calls on Libre and OCRMyPDF
jbig fix for compressions
Fix for compression metadata issues due to forced conversions to PDF/A


Other
--------
Removal of UK US language and just using "English" due to extra development time
Still issue with concurrent files for PDF to image... will fix later sorry
2023-04-01 21:02:54 +01:00
Anthony Stirling
0b4e3de455 readme (#82) 2023-03-28 14:59:40 +01:00
dependabot[bot]
8faef235a6 Bump org.springframework.boot from 3.0.4 to 3.0.5 (#81)
Bumps [org.springframework.boot](https://github.com/spring-projects/spring-boot) from 3.0.4 to 3.0.5.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.0.4...v3.0.5)

---
updated-dependencies:
- dependency-name: org.springframework.boot
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 00:16:27 +01:00
Anthony Stirling
559bc7c731 Update build.gradle 2023-03-27 23:25:30 +01:00
Anthony Stirling
3d7fdd0f35 Bug fixes for merge and split (#80) 2023-03-27 23:24:48 +01:00
Anthony Stirling
726fcf861c Wording fix (#79) 2023-03-25 23:05:30 +00:00
Anthony Stirling
839105f41d Update build.gradle 2023-03-25 22:33:27 +00:00
Anthony Stirling
a2a27e2216 Image stuff (#77)
Features
---------
Image to PDF supports multiple images, stretching and auto rotation
File inputs now only search for wanted file type
Settings now has a zip range so it can zip if you have more than x downloads (default 4)

extras
---------
DevTools support for easier development
Fix for temporary files for thread safety
2023-03-25 22:16:26 +00:00
Anthony Stirling
f866c8a61f Watermark opacity (#76) 2023-03-24 13:30:26 +00:00
dependabot[bot]
472082cb03 Bump org.springframework.boot from 3.0.3 to 3.0.4
Bumps [org.springframework.boot](https://github.com/spring-projects/spring-boot) from 3.0.3 to 3.0.4.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.0.3...v3.0.4)

---
updated-dependencies:
- dependency-name: org.springframework.boot
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-22 22:15:33 +00:00
Anthony Stirling
b5a59ddb6a compress fix 2023-03-22 19:24:43 +00:00
Anthony Stirling
82da3c0027 Update README.md 2023-03-20 22:33:24 +00:00
Anthony Stirling
908e75de39 Update README.md 2023-03-20 22:24:07 +00:00
47 changed files with 951 additions and 734 deletions

View File

@@ -36,6 +36,7 @@ RUN apt-get update && \
python3-pip \
unoconv \
pngquant \
unpaper \
ocrmypdf && \
pip install --user --upgrade ocrmypdf
@@ -49,10 +50,12 @@ COPY build/libs/*.jar app.jar
EXPOSE 8080
# Set environment variables
ENV LOG_LEVEL=INFO
ENV APP_HOME_NAME="Stirling PDF"
#ENV APP_HOME_DESCRIPTION="Personal PDF Website!"
#ENV APP_NAVBAR_NAME="Stirling PDF"
# Run the application
ENTRYPOINT ["java","-jar","/app.jar","-Dlogging.level=${LOG_LEVEL}"]
ENTRYPOINT java -jar /app.jar

View File

@@ -16,7 +16,6 @@ I will support and fix/add things to this if there is a demand [Discord](https:/
![stirling-home](images/stirling-home.png)
## Features
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files.
@@ -25,17 +24,23 @@ I will support and fix/add things to this if there is a demand [Discord](https:/
- Reorganize PDF pages into different orders.
- Add images to PDFs at specified locations. (WIP)
- Rotating PDFs in 90 degree increments.
- Compressing PDFs to decrease their filesize.
- Compressing PDFs to decrease their filesize. (Using OCRMyPDF)
- Add and remove passwords
- Set PDF Permissions
- Add watermark(s)
- Convert Any common file to PDF (using LibreOffice)
- Extract images from PDF
- OCR on PDF (Using OCRMyPDF)
- Edit metadata
- Dark mode support.
- Custom download options (see [here](https://github.com/Frooodle/Stirling-PDF/blob/main/images/settings.png) for example)
- Parallel file processing and downloads
## Technologies used
- Spring Boot + Thymeleaf
- PDFBox
- e-iceblue spire.pdf.free (for PDF compression untill i find a nicer way)
- [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions
- [OcrMyPdf](https://github.com/ocrmypdf/OCRmyPDF)
- HTML, CSS, JavaScript
- Docker
@@ -69,9 +74,24 @@ services:
image: frooodle/s-pdf
```
## Enable OCR/Compression feature
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
## Want to add your own language?
If you want to add your own language to Stirling-PDF please refer
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
And please create a PR to merge it back in so others can use it!
Also please note as i add new features i will google translate existing languages so that they dont lose support. This could mean that new features need grammer corrections as added.
## How to View
1. Open a web browser and navigate to `http://localhost:8080/`
2. Use the application by following the instructions on the website.
## Note
The application is currently not thread-safe
## Customize App Name
Stirling PDF allows easy customization of the visible application name.
Simply use environment variables APP_HOME_NAME, APP_HOME_DESCRIPTION and APP_NAVBAR_NAME with Docker or Java.
If running Java directly, you can also pass these as properties using -D arguments.

View File

@@ -1,11 +1,11 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.3'
id 'org.springframework.boot' version '3.0.5'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'stirling.software'
version = '0.4.0'
version = '0.4.6'
sourceCompatibility = '17'
repositories {
@@ -19,10 +19,15 @@ dependencies {
implementation 'org.apache.logging.log4j:log4j-core:2.20.0'
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
//general PDF
implementation 'org.apache.pdfbox:pdfbox:2.0.27'
implementation 'com.itextpdf:itextpdf:5.5.13.3'
developmentOnly("org.springframework.boot:spring-boot-devtools")
}

BIN
images/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

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

View File

@@ -11,4 +11,33 @@ public class AppConfig {
String version = getClass().getPackage().getImplementationVersion();
return (version != null) ? version : "0.3.3";
}
@Bean(name = "appName")
public String appName() {
String appName = System.getProperty("APP_HOME_NAME");
if(appName == null)
appName = System.getenv("APP_HOME_NAME");
return (appName != null) ? appName : "Stirling PDF";
}
@Bean(name = "navBarText")
public String navBarText() {
String navBarText = System.getProperty("APP_NAVBAR_NAME");
if(navBarText == null)
navBarText = System.getenv("APP_NAVBAR_NAME");
if(navBarText == null)
navBarText = System.getProperty("APP_HOME_NAME");
if(navBarText == null)
navBarText = System.getenv("APP_HOME_NAME");
return (navBarText != null) ? navBarText : "Stirling PDF";
}
@Bean(name = "homeText")
public String homeText() {
String homeText = System.getProperty("APP_HOME_DESCRIPTION");
if(homeText == null)
homeText = System.getenv("APP_HOME_DESCRIPTION");
return (homeText != null) ? homeText : "null";
}
}

View File

@@ -16,7 +16,7 @@ public class Beans implements WebMvcConfigurer {
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.US);
slr.setDefaultLocale(Locale.UK);
return slr;
}

View File

@@ -1,23 +1,15 @@
package stirling.software.SPDF.controller;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@@ -26,30 +18,8 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Controller
public class CompressController {
@@ -80,8 +50,13 @@ public class CompressController {
// Prepare the OCRmyPDF command
List<String> command = new ArrayList<>();
command.add("ocrmypdf");
command.add("--skip-text");
command.add("--tesseract-timeout=0");
command.add("--optimize");
command.add(String.valueOf(optimizeLevel));
command.add("--output-type");
command.add("pdf");
if (fastWebView != null && fastWebView) {
long fileSize = inputFile.getSize();
@@ -97,7 +72,7 @@ public class CompressController {
command.add(tempInputFile.toString());
command.add(tempOutputFile.toString());
int returnCode = ProcessExecutor.runCommandWithOutputHandling(command);
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
// Read the optimized PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);

View File

@@ -21,6 +21,8 @@ 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 MergeController {
@@ -33,7 +35,7 @@ public class MergeController {
}
@PostMapping("/merge-pdfs")
public ResponseEntity<InputStreamResource> mergePdfs(@RequestParam("fileInput") MultipartFile[] files) throws IOException {
public ResponseEntity<byte[]> mergePdfs(@RequestParam("fileInput") MultipartFile[] files) throws IOException {
// Read the input PDF files into PDDocument objects
List<PDDocument> documents = new ArrayList<>();
@@ -43,15 +45,9 @@ public class MergeController {
}
PDDocument mergedDoc = mergeDocuments(documents);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
mergedDoc.save(byteArrayOutputStream);
mergedDoc.close();
// Create an InputStreamResource from the merged PDF
InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
// Return the merged PDF as a response
return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).body(resource);
return PdfUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_merged.pdf");
}
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {

View File

@@ -1,10 +1,8 @@
package stirling.software.SPDF.controller;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -13,11 +11,12 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
@@ -28,14 +27,8 @@ import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import stirling.software.SPDF.utils.ProcessExecutor;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
//import com.spire.pdf.*;
import java.util.concurrent.Semaphore;
@Controller
public class OCRController {
@@ -49,11 +42,18 @@ public class OCRController {
return modelAndView;
}
private final Semaphore semaphore = new Semaphore(2);
@PostMapping("/ocr-pdf")
public ResponseEntity<byte[]> processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile,
@RequestParam("languages") List<String> selectedLanguages,
@RequestParam(name = "sidecar", required = false) Boolean sidecar) throws IOException, InterruptedException {
@RequestParam(name = "sidecar", required = false) Boolean sidecar,
@RequestParam(name = "deskew", required = false) Boolean deskew,
@RequestParam(name = "clean", required = false) Boolean clean,
@RequestParam(name = "clean-final", required = false) Boolean cleanFinal,
@RequestParam(name = "ocrType", required = false) String ocrType) throws IOException, InterruptedException {
//--output-type pdfa
if (selectedLanguages == null || selectedLanguages.size() < 1) {
throw new IOException("Please select at least one language.");
@@ -66,20 +66,50 @@ public class OCRController {
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Prepare the output file path
Path sidecarTextPath = null;
// Run OCR Command
String languageOption = String.join("+", selectedLanguages);
List<String> command = new ArrayList<>(Arrays.asList("ocrmypdf","--verbose", "2", "--language", languageOption,
tempInputFile.toString(), tempOutputFile.toString()));
String sidecarFile = tempOutputFile.toString().replace(".pdf", ".txt");
List<String> command = new ArrayList<>(Arrays.asList("ocrmypdf","--verbose", "2", "--output-type", "pdf"));
if (sidecar != null && sidecar) {
sidecarTextPath = Files.createTempFile("sidecar", ".txt");
command.add("--sidecar");
command.add(sidecarFile);
command.add(sidecarTextPath.toString());
}
int returnCode = ProcessExecutor.runCommandWithOutputHandling(command);
if (deskew != null && deskew) {
command.add("--deskew");
}
if (clean != null && clean) {
command.add("--clean");
}
if (cleanFinal != null && cleanFinal) {
command.add("--clean-final");
}
if (ocrType != null && !ocrType.equals("")) {
if("skip-text".equals(ocrType)) {
command.add("--skip-text");
} else if("force-ocr".equals(ocrType)) {
command.add("--force-ocr");
} else if("Normal".equals(ocrType)) {
}
}
command.addAll(Arrays.asList("--language", languageOption,
tempInputFile.toString(), tempOutputFile.toString()));
//Run CLI command
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
// Read the OCR processed PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Clean up the temporary files
Files.delete(tempInputFile);
// Return the OCR processed PDF as a response
@@ -100,9 +130,9 @@ public class OCRController {
zipOut.closeEntry();
// Add text file to the zip
ZipEntry txtEntry = new ZipEntry(sidecarFile);
ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt"));
zipOut.putNextEntry(txtEntry);
Files.copy(Paths.get(sidecarFile), zipOut);
Files.copy(sidecarTextPath, zipOut);
zipOut.closeEntry();
}
@@ -111,7 +141,7 @@ public class OCRController {
// Clean up the temporary zip file
Files.delete(tempZipFile);
Files.delete(tempOutputFile);
Files.delete(Paths.get(sidecarFile));
Files.delete(sidecarTextPath);
// Return the zip file containing both the PDF and the text file
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

View File

@@ -1,7 +1,6 @@
package stirling.software.SPDF.controller;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -10,7 +9,6 @@ import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -33,7 +31,8 @@ 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 java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Controller
public class SplitPDFController {
@@ -107,32 +106,33 @@ public class SplitPDFController {
// closing the original document
document.close();
// create the zip file
Path zipFile = Paths.get("split_documents.zip");
URI uri = URI.create("jar:file:" + zipFile.toUri().getPath());
Map<String, String> env = new HashMap<>();
env.put("create", "true");
FileSystem zipfs = FileSystems.newFileSystem(uri, env);
Path zipFile = Files.createTempFile("split_documents", ".zip");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
// loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = "split_document_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry();
// loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = "split_document_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
Path pathInZipfile = zipfs.getPath(fileName);
try (OutputStream os = Files.newOutputStream(pathInZipfile)) {
os.write(pdf);
logger.info("Wrote split document {} to zip file", fileName);
} catch (Exception e) {
logger.error("Failed writing to zip", e);
throw e;
}
} catch (Exception e) {
logger.error("Failed writing to zip", e);
throw e;
}
zipfs.close();
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
byte[] data = Files.readAllBytes(zipFile);
ByteArrayResource resource = new ByteArrayResource(data);
new File("split_documents.zip").delete();
Files.delete(zipFile);
// return the Resource in the response
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_split.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(resource.contentLength()).body(resource);

View File

@@ -38,12 +38,13 @@ public class ConvertImgPDFController {
}
@PostMapping("/img-to-pdf")
public ResponseEntity<byte[]> convertToPdf(@RequestParam("fileInput") MultipartFile file) throws IOException {
public ResponseEntity<byte[]> convertToPdf(@RequestParam("fileInput") MultipartFile[] file,
@RequestParam(defaultValue = "false", name = "stretchToFit") boolean stretchToFit,
@RequestParam(defaultValue = "true", name = "autoRotate") boolean autoRotate) throws IOException {
// Convert the file to PDF and get the resulting bytes
byte[] bytes = PdfUtils.convertToPdf(file.getInputStream());
logger.info("File {} successfully converted to pdf", file.getOriginalFilename());
return PdfUtils.bytesToWebResponse(bytes, file.getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_coverted.pdf");
System.out.println(stretchToFit);
byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate);
return PdfUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_coverted.pdf");
}
@PostMapping("/pdf-to-img")
@@ -72,7 +73,6 @@ public class ConvertImgPDFController {
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 {

View File

@@ -1,9 +1,6 @@
package stirling.software.SPDF.controller.converters;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -17,9 +14,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import stirling.software.SPDF.LibreOfficeListener;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@Controller
@@ -58,7 +53,7 @@ public byte[] convertToPdf(MultipartFile inputFile) throws IOException, Interrup
"-o",
tempOutputFile.toString(),
tempInputFile.toString()));
int returnCode = ProcessExecutor.runCommandWithOutputHandling(command);
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command);
// Read the converted PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);

View File

@@ -0,0 +1,75 @@
package stirling.software.SPDF.controller.converters;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import com.itextpdf.xmp.XMPException;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@Controller
public class ConvertPDFToPDFA {
@GetMapping("/pdf-to-pdfa")
public String pdfToPdfAForm(Model model) {
model.addAttribute("currentPage", "pdf-to-pdfa");
return "convert/pdf-to-pdfa";
}
@PostMapping("/pdf-to-pdfa")
public ResponseEntity<byte[]> pdfToPdfA(
@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException {
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf");
inputFile.transferTo(tempInputFile.toFile());
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Prepare the OCRmyPDF command
List<String> command = new ArrayList<>();
command.add("ocrmypdf");
command.add("--skip-text");
command.add("--tesseract-timeout=0");
command.add("--output-type");
command.add("pdfa");
command.add(tempInputFile.toString());
command.add(tempOutputFile.toString());
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
// Read the optimized PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Clean up the temporary files
Files.delete(tempInputFile);
Files.delete(tempOutputFile);
// Return the optimized PDF as a response
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDispositionFormData("attachment", outputFilename);
return ResponseEntity.ok().headers(headers).body(pdfBytes);
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WatermarkRemover;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
@Controller
public class WatermarkController {
@@ -45,7 +46,8 @@ public class WatermarkController {
@PostMapping("/add-watermark")
public ResponseEntity<byte[]> addWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText,
@RequestParam(defaultValue = "30", name = "fontSize") float fontSize, @RequestParam(defaultValue = "0", name = "rotation") float rotation,
@RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer, @RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer)
@RequestParam(defaultValue = "0.5", name = "opacity") float opacity,
@RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer, @RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer)
throws IOException {
// Load the input PDF
@@ -53,9 +55,18 @@ public class WatermarkController {
// Create a page in the document
for (PDPage page : document.getPages()) {
// Get the page's content stream
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
// Set transparency
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(opacity);
contentStream.setGraphicsStateParameters(graphicsState);
// Set font of watermark
PDFont font = PDType1Font.HELVETICA_BOLD;
contentStream.beginText();

View File

@@ -8,6 +8,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -28,6 +29,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
@@ -37,49 +39,100 @@ public class PdfUtils {
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
public static byte[] convertToPdf(InputStream imageStream) throws IOException {
// Create a File object for the image
File imageFile = new File("image.jpg");
try (FileOutputStream fos = new FileOutputStream(imageFile); InputStream input = imageStream) {
byte[] buffer = new byte[1024];
int len;
// Read from the input stream and write to the file
while ((len = input.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
logger.info("Image successfully written to file: {}", imageFile.getAbsolutePath());
} catch (IOException e) {
logger.error("Error writing image to file: {}", imageFile.getAbsolutePath(), e);
throw e;
}
public static byte[] imageToPdf(MultipartFile[] files, boolean stretchToFit, boolean autoRotate) throws IOException {
try (PDDocument doc = new PDDocument()) {
// Create a new PDF page
PDPage page = new PDPage();
doc.addPage(page);
for (MultipartFile file : files) {
// Create a temporary file for the image
File imageFile = Files.createTempFile("image", ".jpg").toFile();
// Create an image object from the image file
PDImageXObject image = PDImageXObject.createFromFileByContent(imageFile, doc);
try (FileOutputStream fos = new FileOutputStream(imageFile); InputStream input = file.getInputStream()) {
byte[] buffer = new byte[1024];
int len;
// Read from the input stream and write to the file
while ((len = input.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
logger.info("Image successfully written to file: {}", imageFile.getAbsolutePath());
} catch (IOException e) {
logger.error("Error writing image to file: {}", imageFile.getAbsolutePath(), e);
throw e;
}
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
// Draw the image onto the page
contentStream.drawImage(image, 0, 0);
logger.info("Image successfully added to PDF");
} catch (IOException e) {
logger.error("Error adding image to PDF", e);
throw e;
// Create a new PDF page
PDPage page = new PDPage();
doc.addPage(page);
// Create an image object from the image file
PDImageXObject image = PDImageXObject.createFromFileByContent(imageFile, doc);
float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight();
if (autoRotate && ((image.getWidth() > image.getHeight() && pageHeight > pageWidth) || (image.getWidth() < image.getHeight() && pageWidth > pageHeight))) {
// Rotate the page 90 degrees if the image better fits the page in landscape orientation
page.setRotation(90);
pageWidth = page.getMediaBox().getHeight();
pageHeight = page.getMediaBox().getWidth();
}
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
if (stretchToFit) {
if (page.getRotation() == 0 || page.getRotation() == 180) {
// Stretch the image to fit the whole page
contentStream.drawImage(image, 0, 0, pageWidth, pageHeight);
} else {
// Adjust the width and height of the page when rotated
contentStream.drawImage(image, 0, 0, pageHeight, pageWidth);
}
logger.info("Image successfully added to PDF, stretched to fit page");
} else {
// Ensure the image fits the page but maintain the image's aspect ratio
float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight();
float pageAspectRatio = pageWidth / pageHeight;
// Determine the scale factor to fit the image onto the page
float scaleFactor = 1.0f;
if (imageAspectRatio > pageAspectRatio) {
// Image is wider than the page, scale to fit the width
scaleFactor = pageWidth / image.getWidth();
} else {
// Image is taller than the page, scale to fit the height
scaleFactor = pageHeight / image.getHeight();
}
// Calculate the position of the image on the page
float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2;
float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2;
// Draw the image onto the page
if (page.getRotation() == 0 || page.getRotation() == 180) {
contentStream.drawImage(image, xPos, yPos, image.getWidth() * scaleFactor, image.getHeight() * scaleFactor);
} else {
// Adjust the width and height of the page when rotated
contentStream.drawImage(image, yPos, xPos, image.getHeight() * scaleFactor, image.getWidth() * scaleFactor);
}
logger.info("Image successfully added to PDF, maintaining aspect ratio");
}
} catch (IOException e) {
logger.error("Error adding image to PDF", e);
throw e;
}
// Delete the temporary file
imageFile.delete();
}
// Create a ByteArrayOutputStream to save the PDF to
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
doc.save(byteArrayOutputStream);
logger.info("PDF successfully saved to byte array");
return byteArrayOutputStream.toByteArray();
}
}
public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI)
throws IOException, Exception {
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {

View File

@@ -1,67 +1,100 @@
package stirling.software.SPDF.utils;
import java.io.BufferedReader;
import java.util.concurrent.ConcurrentHashMap;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.io.BufferedReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
public class ProcessExecutor {
public static int runCommandWithOutputHandling(List<String> command) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process process = processBuilder.start();
public enum Processes {
LIBRE_OFFICE,
OCR_MY_PDF
}
// Read the error stream and standard output stream concurrently
List<String> errorLines = new ArrayList<>();
List<String> outputLines = new ArrayList<>();
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
Thread errorReaderThread = new Thread(() -> {
try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = errorReader.readLine()) != null) {
errorLines.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
Thread outputReaderThread = new Thread(() -> {
try (BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = outputReader.readLine()) != null) {
outputLines.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
private final Semaphore semaphore;
errorReaderThread.start();
outputReaderThread.start();
// Wait for the conversion process to complete
int exitCode = process.waitFor();
// Wait for the reader threads to finish
errorReaderThread.join();
outputReaderThread.join();
if (outputLines.size() > 0) {
String outputMessage = String.join("\n", outputLines);
System.out.println("Command output:\n" + outputMessage);
}
if (errorLines.size() > 0) {
String errorMessage = String.join("\n", errorLines);
System.out.println("Command error output:\n" + errorMessage);
if (exitCode != 0) {
throw new IOException("Command process failed with exit code " + exitCode + ". Error message: " + errorMessage);
}
}
private ProcessExecutor(int semaphoreLimit) {
this.semaphore = new Semaphore(semaphoreLimit);
}
public static ProcessExecutor getInstance(Processes processType) {
return instances.computeIfAbsent(processType, key -> {
int semaphoreLimit = switch (key) {
case LIBRE_OFFICE -> 1;
case OCR_MY_PDF -> 2;
};
return new ProcessExecutor(semaphoreLimit);
});
}
public int runCommandWithOutputHandling(List<String> command) throws IOException, InterruptedException {
int exitCode = 1;
semaphore.acquire();
try {
System.out.print("Running command: " + String.join(" ", command));
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process process = processBuilder.start();
// Read the error stream and standard output stream concurrently
List<String> errorLines = new ArrayList<>();
List<String> outputLines = new ArrayList<>();
Thread errorReaderThread = new Thread(() -> {
try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = errorReader.readLine()) != null) {
errorLines.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
Thread outputReaderThread = new Thread(() -> {
try (BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = outputReader.readLine()) != null) {
outputLines.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
errorReaderThread.start();
outputReaderThread.start();
// Wait for the conversion process to complete
exitCode = process.waitFor();
// Wait for the reader threads to finish
errorReaderThread.join();
outputReaderThread.join();
if (outputLines.size() > 0) {
String outputMessage = String.join("\n", outputLines);
System.out.println("Command output:\n" + outputMessage);
}
if (errorLines.size() > 0) {
String errorMessage = String.join("\n", errorLines);
System.out.println("Command error output:\n" + errorMessage);
if (exitCode != 0) {
throw new IOException("Command process failed with exit code " + exitCode + ". Error message: " + errorMessage);
}
}
} finally {
semaphore.release();
}
return exitCode;
}

View File

@@ -1,12 +1,12 @@
spring.http.multipart.max-file-size=1GB
spring.http.multipart.max-request-size=1GB
spring.http.multipart.max-file-size=2GB
spring.http.multipart.max-request-size=2GB
multipart.enabled=true
multipart.max-file-size=1000MB
multipart.max-request-size=1000MB
multipart.max-file-size=2000MB
multipart.max-request-size=2000MB
spring.servlet.multipart.max-file-size=1000MB
spring.servlet.multipart.max-request-size=1000MB
spring.servlet.multipart.max-file-size=2000MB
spring.servlet.multipart.max-request-size=2000MB
server.forward-headers-strategy=NATIVE
@@ -16,4 +16,9 @@ server.error.include-stacktrace=always
server.error.include-exception=true
server.error.include-message=always
server.servlet.session.tracking-modes=cookie
server.servlet.session.tracking-modes=cookie
spring.devtools.restart.enabled=true
spring.devtools.livereload.enabled=true
spring.thymeleaf.encoding=UTF-8

View File

@@ -5,7 +5,7 @@
###########
# Generic #
###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
# the direction that the language is written (ltr=left to right, rtl=right to left)
language.direction=rtl
pdfPrompt=اختر PDF
@@ -56,8 +56,8 @@ home.addImage.desc=إضافة صورة إلى موقع معين في PDF (الع
home.watermark.title=إضافة علامة مائية
home.watermark.desc=أضف علامة مائية مخصصة إلى مستند PDF الخاص بك.
home.remove-watermark.title = \u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629
home.remove-watermark.desc = \u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0627\u062A \u0627\u0644\u0645\u0627\u0626\u064A\u0629 \u0645\u0646 \u0645\u0633\u062A\u0646\u062F PDF \u0627\u0644\u062E\u0627\u0635 \u0628\u0643.
home.remove-watermark.title=\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629
home.remove-watermark.desc=\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0627\u062A \u0627\u0644\u0645\u0627\u0626\u064A\u0629 \u0645\u0646 \u0645\u0633\u062A\u0646\u062F PDF \u0627\u0644\u062E\u0627\u0635 \u0628\u0643.
home.permissions.title=تغيير الأذونات
home.permissions.desc=قم بتغيير أذونات مستند PDF الخاص بك
@@ -74,37 +74,45 @@ home.removePassword.desc=إزالة الحماية بكلمة مرور من مس
home.compressPdfs.title=ضغط ملفات PDF
home.compressPdfs.desc=ضغط ملفات PDF لتقليل حجم الملف.
home.changeMetadata.title = \u062A\u063A\u064A\u064A\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0648\u0635\u0641\u064A\u0629
home.changeMetadata.desc = \u062A\u063A\u064A\u064A\u0631 / \u0625\u0632\u0627\u0644\u0629 / \u0625\u0636\u0627\u0641\u0629 \u0628\u064A\u0627\u0646\u0627\u062A \u0623\u0648\u0644\u064A\u0629 \u0645\u0646 \u0645\u0633\u062A\u0646\u062F PDF
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
home.fileToPDF.title=\u062A\u062D\u0648\u064A\u0644 \u0627\u0644\u0645\u0644\u0641 \u0625\u0644\u0649 PDF
home.fileToPDF.desc=\u062A\u062D\u0648\u064A\u0644 \u0623\u064A \u0645\u0644\u0641 \u062A\u0642\u0631\u064A\u0628\u0627 \u0625\u0644\u0649 PDF (DOCX \u0648PNG \u0648XLS \u0648PPT \u0648TXT \u0648\u0627\u0644\u0645\u0632\u064A\u062F)
home.ocr.title=\u062A\u0634\u063A\u064A\u0644 OCR \u0639\u0644\u0649 PDF
home.ocr.desc=\u0645\u0633\u062D \u0648\u0627\u0643\u062A\u0634\u0627\u0641 \u0627\u0644\u0646\u0635 \u0645\u0646 \u0627\u0644\u0635\u0648\u0631 \u062F\u0627\u062E\u0644 PDF \u0648\u0625\u0639\u0627\u062F\u0629 \u0625\u0636\u0627\u0641\u062A\u0647 \u0643\u0646\u0635.
home.ocr.title=\u062A\u0634\u063A\u064A\u0644 OCR \u0639\u0644\u0649 PDF \u0648 / \u0623\u0648 \u0645\u0633\u062D \u0636\u0648\u0626\u064A
home.ocr.desc=\u064A\u0642\u0648\u0645 \u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644\u062A\u0646\u0638\u064A\u0641 \u0628\u0645\u0633\u062D \u0648\u0627\u0643\u062A\u0634\u0627\u0641 \u0627\u0644\u0646\u0635 \u0645\u0646 \u0627\u0644\u0635\u0648\u0631 \u062F\u0627\u062E\u0644 \u0645\u0644\u0641 PDF \u0648\u064A\u0639\u064A\u062F \u0625\u0636\u0627\u0641\u062A\u0647 \u0643\u0646\u0635
home.extractImages.title=\u0627\u0633\u062A\u062E\u0631\u0627\u062C \u0627\u0644\u0635\u0648\u0631
home.extractImages.desc=\u064A\u0633\u062A\u062E\u0631\u062C \u062C\u0645\u064A\u0639 \u0627\u0644\u0635\u0648\u0631 \u0645\u0646 \u0645\u0644\u0641 PDF \u0648\u064A\u062D\u0641\u0638\u0647\u0627 \u0641\u064A \u0627\u0644\u0631\u0645\u0632 \u0627\u0644\u0628\u0631\u064A\u062F\u064A
navbar.settings = \u0625\u0639\u062F\u0627\u062F\u0627\u062A
navbar.settings=\u0625\u0639\u062F\u0627\u062F\u0627\u062A
settings.title=\u0627\u0644\u0625\u0639\u062F\u0627\u062F\u0627\u062A
settings.update = \u0627\u0644\u062A\u062D\u062F\u064A\u062B \u0645\u062A\u0627\u062D
settings.appVersion = \u0625\u0635\u062F\u0627\u0631 \u0627\u0644\u062A\u0637\u0628\u064A\u0642:
settings.update=\u0627\u0644\u062A\u062D\u062F\u064A\u062B \u0645\u062A\u0627\u062D
settings.appVersion=\u0625\u0635\u062F\u0627\u0631 \u0627\u0644\u062A\u0637\u0628\u064A\u0642:
settings.downloadOption.title=\u062A\u062D\u062F\u064A\u062F \u062E\u064A\u0627\u0631 \u0627\u0644\u062A\u0646\u0632\u064A\u0644 (\u0644\u0644\u062A\u0646\u0632\u064A\u0644\u0627\u062A \u0630\u0627\u062A \u0627\u0644\u0645\u0644\u0641 \u0627\u0644\u0648\u0627\u062D\u062F \u063A\u064A\u0631 \u0627\u0644\u0645\u0636\u063A\u0648\u0637):
settings.downloadOption.1=\u0641\u062A\u062D \u0641\u064A \u0646\u0641\u0633 \u0627\u0644\u0646\u0627\u0641\u0630\u0629
settings.downloadOption.2=\u0641\u062A\u062D \u0641\u064A \u0646\u0627\u0641\u0630\u0629 \u062C\u062F\u064A\u062F\u0629
settings.downloadOption.3=\u062A\u0646\u0632\u064A\u0644 \u0627\u0644\u0645\u0644\u0641
settings.zip=\u0645\u0644\u0641\u0627\u062A \u0627\u0644\u062A\u0646\u0632\u064A\u0644 \u0627\u0644\u0645\u062A\u0639\u062F\u062F \u0627\u0644\u0645\u0636\u063A\u0648\u0637\u0629
settings.zipThreshold=\u0645\u0644\u0641\u0627\u062A \u0645\u0636\u063A\u0648\u0637\u0629 \u0639\u0646\u062F \u062A\u062C\u0627\u0648\u0632 \u0639\u062F\u062F \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0627\u0644\u062A\u064A \u062A\u0645 \u062A\u0646\u0632\u064A\u0644\u0647\u0627
#OCR
OCR.title = OCR
ocr.header=OCR (\u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u0623\u062D\u0631\u0641)
ocr.selectText.1=\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0644\u063A\u0627\u062A \u0627\u0644\u062A\u064A \u0633\u064A\u062A\u0645 \u0627\u0643\u062A\u0634\u0627\u0641\u0647\u0627 \u062F\u0627\u062E\u0644 PDF (\u0627\u0644\u0644\u063A\u0627\u062A \u0627\u0644\u0645\u062F\u0631\u062C\u0629 \u0647\u064A \u062A\u0644\u0643 \u0627\u0644\u0645\u0643\u062A\u0634\u0641\u0629 \u062D\u0627\u0644\u064A\u0627):
ocr.selectText.2=\u0625\u0646\u062A\u0627\u062C \u0645\u0644\u0641 \u0646\u0635\u064A \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0646\u0635 OCR \u062C\u0646\u0628\u0627 \u0625\u0644\u0649 \u062C\u0646\u0628 \u0645\u0639 \u0645\u0644\u0641 PDF OCR'ed
ocr.title=\u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641 / \u062A\u0646\u0638\u064A\u0641 \u0627\u0644\u0645\u0633\u062D \u0627\u0644\u0636\u0648\u0626\u064A
ocr.header=\u0645\u0633\u062D \u0627\u0644\u0645\u0633\u062D \u0627\u0644\u0636\u0648\u0626\u064A / \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641 (\u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641)
ocr.selectText.1=\u062D\u062F\u062F \u0627\u0644\u0644\u063A\u0627\u062A \u0627\u0644\u062A\u064A \u0633\u064A\u062A\u0645 \u0627\u0643\u062A\u0634\u0627\u0641\u0647\u0627 \u062F\u0627\u062E\u0644 \u0645\u0644\u0641 PDF (\u0627\u0644\u0644\u063A\u0627\u062A \u0627\u0644\u0645\u062F\u0631\u062C\u0629 \u0647\u064A \u062A\u0644\u0643 \u0627\u0644\u062A\u064A \u062A\u0645 \u0627\u0643\u062A\u0634\u0627\u0641\u0647\u0627 \u062D\u0627\u0644\u064A\u064B\u0627):
ocr.selectText.2=\u0625\u0646\u062A\u0627\u062C \u0645\u0644\u0641 \u0646\u0635\u064A \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0646\u0635 OCR \u0628\u062C\u0627\u0646\u0628 \u0645\u0644\u0641 PDF \u0627\u0644\u0630\u064A \u062A\u0645 \u0625\u0639\u062F\u0627\u062F\u0647 \u0628\u0648\u0627\u0633\u0637\u0629 OCR
ocr.selectText.3=\u062A\u0645 \u0645\u0633\u062D \u0627\u0644\u0635\u0641\u062D\u0627\u062A \u0627\u0644\u0635\u062D\u064A\u062D\u0629 \u0636\u0648\u0626\u064A\u064B\u0627 \u0628\u0632\u0627\u0648\u064A\u0629 \u0645\u0646\u062D\u0631\u0641\u0629 \u0639\u0646 \u0637\u0631\u064A\u0642 \u062A\u062F\u0648\u064A\u0631\u0647\u0627 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649 \u0641\u064A \u0645\u0643\u0627\u0646\u0647\u0627
ocr.selectText.4=\u0635\u0641\u062D\u0629 \u0646\u0638\u064A\u0641\u0629 \u0644\u0630\u0644\u0643 \u0645\u0646 \u063A\u064A\u0631 \u0627\u0644\u0645\u062D\u062A\u0645\u0644 \u0623\u0646 \u064A\u062C\u062F OCR \u0646\u0635\u064B\u0627 \u0641\u064A \u0636\u0648\u0636\u0627\u0621 \u0627\u0644\u062E\u0644\u0641\u064A\u0629. (\u0644\u0627 \u064A\u0648\u062C\u062F \u062A\u063A\u064A\u064A\u0631 \u0641\u064A \u0627\u0644\u0625\u062E\u0631\u0627\u062C)
ocr.selectText.5=\u0635\u0641\u062D\u0629 \u0646\u0638\u064A\u0641\u0629 \u060C \u0644\u0630\u0644\u0643 \u0645\u0646 \u063A\u064A\u0631 \u0627\u0644\u0645\u062D\u062A\u0645\u0644 \u0623\u0646 \u064A\u062C\u062F OCR \u0646\u0635\u064B\u0627 \u0641\u064A \u0636\u0648\u0636\u0627\u0621 \u0627\u0644\u062E\u0644\u0641\u064A\u0629 \u060C \u0648\u064A\u062D\u0627\u0641\u0638 \u0639\u0644\u0649 \u0627\u0644\u062A\u0646\u0638\u064A\u0641 \u0641\u064A \u0627\u0644\u0625\u062E\u0631\u0627\u062C.
ocr.selectText.6=\u064A\u062A\u062C\u0627\u0647\u0644 \u0627\u0644\u0635\u0641\u062D\u0627\u062A \u0627\u0644\u062A\u064A \u062A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0646\u0635 \u062A\u0641\u0627\u0639\u0644\u064A \u060C \u0641\u0642\u0637 \u0635\u0641\u062D\u0627\u062A OCRs \u0627\u0644\u062A\u064A \u0647\u064A \u0635\u0648\u0631
ocr.selectText.7=\u0641\u0631\u0636 \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641 \u060C \u0633\u064A\u0624\u062F\u064A \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641 \u0639\u0644\u0649 \u0643\u0644 \u0635\u0641\u062D\u0629 \u0625\u0644\u0649 \u0625\u0632\u0627\u0644\u0629 \u062C\u0645\u064A\u0639 \u0639\u0646\u0627\u0635\u0631 \u0627\u0644\u0646\u0635 \u0627\u0644\u0623\u0635\u0644\u064A
ocr.selectText.8=\u0639\u0627\u062F\u064A (\u062E\u0637\u0623 \u0625\u0630\u0627 \u0643\u0627\u0646 PDF \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0646\u0635)
ocr.selectText.9=\u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0625\u0636\u0627\u0641\u064A\u0629
ocr.selectText.10=\u0648\u0636\u0639 \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641
ocr.help=\u064A\u0631\u062C\u0649 \u0642\u0631\u0627\u0621\u0629 \u0647\u0630\u0647 \u0627\u0644\u0648\u062B\u0627\u0626\u0642 \u062D\u0648\u0644 \u0643\u064A\u0641\u064A\u0629 \u0627\u0633\u062A\u062E\u062F\u0627\u0645 \u0647\u0630\u0627 \u0644\u0644\u063A\u0627\u062A \u0623\u062E\u0631\u0649 \u0648 / \u0623\u0648 \u0627\u0644\u0627\u0633\u062A\u062E\u062F\u0627\u0645 \u0644\u064A\u0633 \u0641\u064A \u0639\u0627\u0645\u0644 \u0627\u0644\u0625\u0631\u0633\u0627\u0621
ocr.credit=\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\u0644\u062E\u062F\u0645\u0629 OCRmyPDF \u0648 Tesseract \u0644 OCR.
ocr.submit = \u0645\u0639\u0627\u0644\u062C\u0629 PDF \u0628\u0627\u0633\u062A\u062E\u062F\u0627\u0645 OCR
ocr.submit=\u0645\u0639\u0627\u0644\u062C\u0629 PDF \u0628\u0627\u0633\u062A\u062E\u062F\u0627\u0645 OCR
extractImages.title=\u0627\u0633\u062A\u062E\u0631\u0627\u062C \u0627\u0644\u0635\u0648\u0631
@@ -127,8 +135,15 @@ addImage.submit=إضافة صورة
#compress
compress.title=ضغط
compress.header=ضغط ملف PDF
compress.compressLevel=القيمة بين 1 و 100 (يتم تقليل 1 إلى أقصى حد)
compress.header=\u0636\u063A\u0637 PDF
compress.credit=\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\u0644\u062E\u062F\u0645\u0629 OCRmyPDF \u0644\u0636\u063A\u0637 / \u062A\u062D\u0633\u064A\u0646 PDF.
compress.selectText.1=\u0645\u0633\u062A\u0648\u0649 \u0627\u0644\u062A\u062D\u0633\u064A\u0646:
compress.selectText.2=0 (\u0628\u062F\u0648\u0646 \u062A\u062D\u0633\u064A\u0646)
compress.selectText.3=1 (\u0627\u0641\u062A\u0631\u0627\u0636\u064A\u060C \u062A\u062D\u0633\u064A\u0646 \u0628\u062F\u0648\u0646 \u0641\u0642\u062F\u0627\u0646)
compress.selectText.4=2 (\u062A\u062D\u0633\u064A\u0646 \u0636\u064A\u0627\u0639)
compress.selectText.5=3 (\u062A\u062D\u0633\u064A\u0646 \u0636\u064A\u0627\u0639 \u060C \u0623\u0643\u062B\u0631 \u0639\u062F\u0648\u0627\u0646\u064A\u0629)
compress.selectText.6=\u062A\u0645\u0643\u064A\u0646 \u0639\u0631\u0636 \u0627\u0644\u0648\u064A\u0628 \u0627\u0644\u0633\u0631\u064A\u0639 (\u062E\u0637\u064A PDF)
compress.selectText.7=\u062A\u0645\u0643\u064A\u0646 \u062A\u0631\u0645\u064A\u0632 JBIG2 \u0627\u0644\u0645\u0641\u0642\u0648\u062F
compress.submit=ضغط
@@ -177,18 +192,23 @@ split.submit=Split
imageToPDF.title=صورة إلى PDF
imageToPDF.header=صورة إلى PDF
imageToPDF.submit=تحول
imageToPDF.selectText.1=\u062A\u0645\u062F\u062F \u0644\u0644\u0645\u0644\u0627\u0621\u0645\u0629
imageToPDF.selectText.2=\u062F\u0648\u0631\u0627\u0646 PDF \u062A\u0644\u0642\u0627\u0626\u064A\u064B\u0627
imageToPDF.selectText.3=\u0627\u0644\u0645\u0646\u0637\u0642 \u0627\u0644\u0645\u062A\u0639\u062F\u062F \u0644\u0644\u0645\u0644\u0641\u0627\u062A (\u0645\u0641\u0639\u0651\u0644 \u0641\u0642\u0637 \u0625\u0630\u0627 \u0643\u0646\u062A \u062A\u0639\u0645\u0644 \u0645\u0639 \u0635\u0648\u0631 \u0645\u062A\u0639\u062F\u062F\u0629)
imageToPDF.selectText.4=\u062F\u0645\u062C \u0641\u064A \u0645\u0644\u0641 PDF \u0648\u0627\u062D\u062F
imageToPDF.selectText.5=\u062A\u062D\u0648\u064A\u0644 \u0625\u0644\u0649 \u0645\u0644\u0641\u0627\u062A PDF \u0645\u0646\u0641\u0635\u0644\u0629
#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.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
@@ -218,14 +238,15 @@ watermark.selectText.3=حجم الخط:
watermark.selectText.4=دوران (0-360):
watermark.selectText.5=widthSpacer (مسافة بين كل علامة مائية أفقيًا):
watermark.selectText.6=heightSpacer (مسافة بين كل علامة مائية عموديًا):
watermark.selectText.7=\u0627\u0644\u062A\u0639\u062A\u064A\u0645 (0\u066A - 100\u066A):
watermark.submit=إضافة علامة مائية
#remove-watermark
remove-watermark.title = \u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629
remove-watermark.header = \u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629
remove-watermark.selectText.1 = \u062D\u062F\u062F PDF \u0644\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629 \u0645\u0646:
remove-watermark.selectText.2 = \u0646\u0635 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629:
remove-watermark.submit = \u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629
remove-watermark.title=\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629
remove-watermark.header=\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629
remove-watermark.selectText.1=\u062D\u062F\u062F PDF \u0644\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629 \u0645\u0646:
remove-watermark.selectText.2=\u0646\u0635 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629:
remove-watermark.submit=\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629
#Change permissions
permissions.title=تغيير الأذونات
@@ -250,27 +271,32 @@ 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
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
xlsToPdf.title = \u062A\u062D\u0648\u064A\u0644 Excel \u0625\u0644\u0649 PDF
xlsToPdf.header = \u062A\u062D\u0648\u064A\u0644 Excel \u0625\u0644\u0649 PDF
xlsToPdf.selectText.1 = \u062D\u062F\u062F \u0648\u0631\u0642\u0629 \u0625\u0643\u0633\u0644 XLS \u0623\u0648 XLSX \u0644\u0644\u062A\u062D\u0648\u064A\u0644
xlsToPdf.convert = \u062A\u062D\u0648\u064A\u0644
xlsToPdf.title=\u062A\u062D\u0648\u064A\u0644 Excel \u0625\u0644\u0649 PDF
xlsToPdf.header=\u062A\u062D\u0648\u064A\u0644 Excel \u0625\u0644\u0649 PDF
xlsToPdf.selectText.1=\u062D\u062F\u062F \u0648\u0631\u0642\u0629 \u0625\u0643\u0633\u0644 XLS \u0623\u0648 XLSX \u0644\u0644\u062A\u062D\u0648\u064A\u0644
xlsToPdf.convert=\u062A\u062D\u0648\u064A\u0644
pdfToPDFA.title=PDF \u0625\u0644\u0649 PDF / A
pdfToPDFA.header=PDF \u0625\u0644\u0649 PDF / A
pdfToPDFA.credit=\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\u0644\u062E\u062F\u0645\u0629 OCRmyPDF \u0644\u062A\u062D\u0648\u064A\u0644 PDF / A.
pdfToPDFA.submit=\u062A\u062D\u0648\u064A\u0644

View File

@@ -1,7 +1,7 @@
###########
# Generic #
###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
# the direction that the language is written (ltr=left to right, rtl=right to left)
language.direction=ltr
pdfPrompt=PDF auswählen
@@ -76,8 +76,8 @@ home.changeMetadata.desc=
home.fileToPDF.title=Datei in PDF konvertieren
home.fileToPDF.desc=Konvertieren Sie nahezu jede Datei in PDF (DOCX, PNG, XLS, PPT, TXT und mehr)
home.ocr.title=OCR auf PDF ausführen
home.ocr.desc=Scannt und erkennt Text aus Bildern in einer PDF-Datei und fügt ihn erneut als Text hinzu.
home.ocr.title=Führe OCR auf PDF- und/oder Cleanup-Scans aus
home.ocr.desc=Cleanup scannt und erkennt Text aus Bildern in einer PDF-Datei und fügt ihn erneut als Text hinzu.
home.extractImages.title=Bilder extrahieren
home.extractImages.desc=Extrahiert alle Bilder aus einer PDF-Datei und speichert sie als Zip-Datei
@@ -90,13 +90,21 @@ settings.downloadOption.title=Download-Option w
settings.downloadOption.1=Im selben Fenster öffnen
settings.downloadOption.2=In neuem Fenster öffnen
settings.downloadOption.3=Datei herunterladen
settings.zip=Dateien mit mehrfachem Download zippen
settings.zipThreshold=Dateien komprimieren, wenn die Anzahl der heruntergeladenen Dateien überschritten wird
#OCR
ocr.title=OCR
ocr.header=OCR (Optische Zeichenerkennung)
ocr.selectText.1=Wählen Sie die Sprachen aus, die in der PDF-Datei erkannt werden sollen (die aufgelisteten sind die aktuell erkannten):
ocr.selectText.2=Textdatei mit OCR-Text neben der OCR-PDF-Datei erstellen
ocr.title=OCR / Scan-Bereinigung
ocr.header=Scans bereinigen / OCR (Optical Character Recognition)
ocr.selectText.1=Sprachen auswählen, die im PDF erkannt werden sollen (die aufgelisteten sind die aktuell erkannten):
ocr.selectText.2=Textdatei erzeugen, die OCR-Text neben dem OCR-bearbeiteten PDF enthält
ocr.selectText.3=Korrekte Seiten wurden in einem schiefen Winkel gescannt, indem sie wieder an ihren Platz gedreht wurden
ocr.selectText.4=Seite säubern, daher ist es weniger wahrscheinlich, dass OCR Text im Hintergrundrauschen findet. (Keine Ausgangsänderung)
ocr.selectText.5=Seite säubern, sodass es weniger wahrscheinlich ist, dass OCR Text im Hintergrundrauschen findet, Bereinigung der Ausgabe wird beibehalten.
ocr.selectText.6=Ignoriert Seiten mit interaktivem Text, nur OCR-Seiten, die Bilder sind
ocr.selectText.7=OCR erzwingen, OCR wird jede Seite entfernen und alle ursprünglichen Textelemente entfernen
ocr.selectText.8=Normal (Fehler, wenn PDF Text enthält)
ocr.selectText.9=Zusätzliche Einstellungen
ocr.selectText.10=OCR-Modus
ocr.help=Bitte lesen Sie diese Dokumentation, um zu erfahren, wie Sie dies für andere Sprachen verwenden und/oder nicht in Docker verwenden können
ocr.credit=Dieser Dienst verwendet OCRmyPDF und Tesseract für OCR.
ocr.submit=PDF mit OCR verarbeiten
@@ -127,7 +135,14 @@ addImage.submit=Bild hinzufügen
#compress
compress.title=Komprimieren
compress.header=PDF komprimieren
compress.compressLevel=Wert zwischen 1 und 100 (1 ist am meisten komprimiert)
compress.credit=Dieser Dienst verwendet OCRmyPDF für die PDF-Komprimierung/-Optimierung.
compress.selectText.1=Optimierungsstufe:
compress.selectText.2=0 (Keine Optimierung)
compress.selectText.3=1 (Standard, verlustfreie Optimierung)
compress.selectText.4=2 (Verlustbehaftete Optimierung)
compress.selectText.5=3 (Verlustbehaftete Optimierung, aggressiver)
compress.selectText.6=Schnelle Webansicht aktivieren (PDF linearisieren)
compress.selectText.7=Verlustbehaftete JBIG2-Kodierung aktivieren
compress.submit=Komprimieren
@@ -176,6 +191,11 @@ split.submit=Aufteilen
imageToPDF.title=Bild zu PDF
imageToPDF.header=Bild zu PDF
imageToPDF.submit=Umwandeln
imageToPDF.selectText.1=Auf Seite strecken
imageToPDF.selectText.2=PDF automatisch drehen
imageToPDF.selectText.3=Mehrere Dateien verarbeiten (nur aktiv, wenn Sie mit mehreren Bildern arbeiten)
imageToPDF.selectText.4=In ein einziges PDF zusammenführen
imageToPDF.selectText.5=In separate PDFs konvertieren
#pdfToImage
pdfToImage.title=PDF zu Bild
@@ -217,6 +237,7 @@ watermark.selectText.3=Schriftgröße:
watermark.selectText.4=Drehung (0-360):
watermark.selectText.5=breiteSpacer (horizontaler Abstand zwischen den einzelnen Wasserzeichen):
watermark.selectText.6=höheSpacer (vertikaler Abstand zwischen den einzelnen Wasserzeichen):
watermark.selectText.7=Deckkraft (0% - 100 %):
watermark.submit=Wasserzeichen hinzufügen
#remove-watermark
@@ -276,7 +297,10 @@ xlsToPdf.selectText.1=XLS- oder XLSX-Excel-Tabelle zum Konvertieren ausw
xlsToPdf.convert=konvertieren
pdfToPDFA.title=PDF zu PDF/A
pdfToPDFA.header=PDF zu PDF/A
pdfToPDFA.credit=Dieser Dienst verwendet OCRmyPDF für die PDF/A-Konvertierung
pdfToPDFA.submit=Konvertieren

View File

@@ -4,10 +4,10 @@
# the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr
pdfPrompt=Choose PDF
multiPdfPrompt=Choose PDFs (2+)
pdfPrompt=Select PDF(s)
multiPdfPrompt=Select PDFs (2+)
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
imgPrompt=Choose Image
imgPrompt=Select Image(s)
genericSubmit=Submit
processTimeWarning=Warning: This process can take up to a minute depending on file-size
pageOrderPrompt=Page Order (Enter a comma-separated list of page numbers) :
@@ -76,12 +76,15 @@ home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document
home.fileToPDF.title=Convert file to PDF
home.fileToPDF.desc=Convert nearly any file to PDF (DOCX, PNG, XLS, PPT, TXT and more)
home.ocr.title=Run OCR on PDF
home.ocr.desc=Scans and detects text from images within a PDF and re-adds it as text.
home.ocr.title=Run OCR on PDF and/or Cleanup scans
home.ocr.desc=Cleanup scans and detects text from images within a PDF and re-adds it as text.
home.extractImages.title=Extract Images
home.extractImages.desc=Extracts all images from a PDF and saves them to zip
home.pdfToPDFA.title=Convert PDF to PDF/A
home.pdfToPDFA.desc=Convert PDF to PDF/A for long-term storage
navbar.settings=Settings
settings.title=Settings
@@ -91,17 +94,30 @@ settings.downloadOption.title=Choose download option (For single file non zip do
settings.downloadOption.1=Open in same window
settings.downloadOption.2=Open in new window
settings.downloadOption.3=Download file
settings.zip=Zip multi-download files
settings.zipThreshold=Zip files when the number of downloaded files exceeds
#OCR
ocr.title=OCR
ocr.header=OCR (Optical Character Recognition)
ocr.title=OCR / Scan Cleanup
ocr.header=Cleanup Scans / OCR (Optical Character Recognition)
ocr.selectText.1=Select languages that are to be detected within the PDF (Ones listed are the ones currently detected):
ocr.selectText.2=Produce text file containing OCR text alongside the OCR'ed PDF
ocr.selectText.3=Correct pages were scanned at a skewed angle by rotating them back into place
ocr.selectText.4=Clean page so its less likely that OCR will find text in background noise. (No output change)
ocr.selectText.5=Clean page so its less likely that OCR will find text in background noise, maintains cleanup in output.
ocr.selectText.6=Ignores pages that have interacive text on them, only OCRs pages that are images
ocr.selectText.7=Force OCR, will OCR Every page removing all original text elements
ocr.selectText.8=Normal (Will error if PDF contains text)
ocr.selectText.9=Additional Settings
ocr.selectText.10=OCR Mode
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
ocr.credit=This service uses OCRmyPDF and Tesseract for OCR.
ocr.submit=Process PDF with OCR
extractImages.title=Extract Images
extractImages.header=Extract Images
extractImages.selectText=Select image format to convert extracted images to
@@ -181,7 +197,12 @@ split.submit=Split
imageToPDF.title=Image to PDF
imageToPDF.header=Image to PDF
imageToPDF.submit=Convert
imageToPDF.selectText.1=Stretch to fit
imageToPDF.selectText.2=Auto rotate PDF
imageToPDF.selectText.3=Multi file logic (Only enabled if working with multiple images)
imageToPDF.selectText.4=Merge into single PDF
imageToPDF.selectText.5=Convert to separate PDFs
#pdfToImage
pdfToImage.title=PDF to Image
pdfToImage.header=PDF to Image
@@ -222,6 +243,7 @@ watermark.selectText.3=Font Size:
watermark.selectText.4=Rotation (0-360):
watermark.selectText.5=widthSpacer (Space between each watermark horizontally):
watermark.selectText.6=heightSpacer (Space between each watermark vertically):
watermark.selectText.7=Opacity (0% - 100%):
watermark.submit=Add Watermark
#remove-watermark
@@ -280,6 +302,10 @@ xlsToPdf.convert=convert
pdfToPDFA.title=PDF To PDF/A
pdfToPDFA.header=PDF To PDF/A
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
pdfToPDFA.submit=Convert

View File

@@ -1,285 +0,0 @@
###########
# Generic #
###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr
pdfPrompt=Choose PDF
multiPdfPrompt=Choose PDFs (2+)
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
imgPrompt=Choose Image
genericSubmit=Submit
processTimeWarning=Warning: This process can take up to a minute depending on file-size
pageOrderPrompt=Page Order (Enter a comma-separated list of page numbers) :
goToPage=Go
true=True
false=False
unknown=Unknown
save=Save
close=Close
#############
# HOME-PAGE #
#############
home.desc=Your locally hosted one-stop-shop for all your PDF needs.
navbar.convert=Convert
navbar.security=Security
navbar.other=Other
navbar.darkmode=Dark Mode
home.merge.title=Merge PDFs
home.merge.desc=Easily merge multiple PDFs into one.
home.split.title=Split PDFs
home.split.desc=Split PDFs into multiple documents
home.rotate.title=Rotate PDFs
home.rotate.desc=Easily rotate your PDFs.
home.imageToPdf.title=Image to PDF
home.imageToPdf.desc=Convert a images (PNG, JPEG, GIF) to PDF.
home.pdfToImage.title=PDF to Image
home.pdfToImage.desc=Convert a PDF to a image. (PNG, JPEG, GIF)
home.pdfOrganiser.title=PDF Organizer
home.pdfOrganiser.desc=Remove/Rearrange pages in any order
home.addImage.title=Add image onto PDF
home.addImage.desc=Adds a image onto a set location on the PDF (Work in progress)
home.watermark.title=Add Watermark
home.watermark.desc=Add a custom watermark to your PDF document.
home.remove-watermark.title=Remove Watermark
home.remove-watermark.desc=Remove watermarks from your PDF document.
home.permissions.title=Change Permissions
home.permissions.desc=Change the permissions of your PDF document
home.removePages.title=Remove Pages
home.removePages.desc=Delete unwanted pages from your PDF document.
home.addPassword.title=Add Password
home.addPassword.desc=Encrypt your PDF document with a password.
home.removePassword.title=Remove Password
home.removePassword.desc=Remove password protection from your PDF document.
home.compressPdfs.title=Compress PDFs
home.compressPdfs.desc=Compress PDFs to reduce their file size.
home.changeMetadata.title=Change Metadata
home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document
home.fileToPDF.title=Convert file to PDF
home.fileToPDF.desc=Convert nearly any file to PDF (DOCX, PNG, XLS, PPT, TXT and more)
home.ocr.title=Run OCR on PDF
home.ocr.desc=Scans and detects text from images within a PDF and re-adds it as text.
home.extractImages.title=Extract Images
home.extractImages.desc=Extracts all images from a PDF and saves them to zip
navbar.settings=Settings
settings.title=Settings
settings.update=Update available
settings.appVersion=App Version:
settings.downloadOption.title=Choose download option (For single file non zip downloads):
settings.downloadOption.1=Open in same window
settings.downloadOption.2=Open in new window
settings.downloadOption.3=Download file
settings.zip=Zip multi-download files
#OCR
ocr.title=OCR
ocr.header=OCR (Optical Character Recognition)
ocr.selectText.1=Select languages that are to be detected within the PDF (Ones listed are the ones currently detected):
ocr.selectText.2=Produce text file containing OCR text alongside the OCR'ed PDF
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
ocr.credit=This service uses OCRmyPDF and Tesseract for OCR.
ocr.submit=Process PDF with OCR
extractImages.title=Extract Images
extractImages.header=Extract Images
extractImages.selectText=Select image format to convert extracted images to
extractImages.submit=Extract
#File to PDF
fileToPDF.title=File to PDF
fileToPDF.header=Convert any file to PDF
fileToPDF.credit=This service uses LibreOffice and Unoconv for file conversion.
fileToPDF.supportedFileTypes=Supported file types should include the below however for a full updated list of supported formats, please refer to the LibreOffice documentation
fileToPDF.submit=Convert to PDF
#Add image
addImage.title=Add Image
addImage.header=Add image to PDF (Work in progress)
addImage.submit=Add image
#compress
compress.title=Compress
compress.header=Compress PDF
compress.compressLevel=Value between 1 and 100 (1 being most reduced)
compress.submit=Compress
#merge
merge.title=Merge
merge.header=Merge multiple PDFs (2+)
merge.submit=Merge
#pdfOrganiser
pdfOrganiser.title=Page Organizer
pdfOrganiser.header=PDF Page Organizer
pdfOrganiser.submit=Rearrange Pages
#pageRemover
pageRemover.title=Page Remover
pageRemover.header=PDF Page remover
pageRemover.pagesToDelete=Pages to delete (Enter a comma-separated list of page numbers) :
pageRemover.submit=Delete Pages
#rotate
rotate.title=Rotate PDF
rotate.header=Rotate PDF
rotate.selectAngle=Select rotation angle (in multiples of 90 degrees):
rotate.submit=Rotate
#merge
split.title=Split PDF
split.header=Split PDF
split.desc.1=The numbers you select are the page number you wish to do a split on
split.desc.2=As such selecting 1,3,7-8 would split a 10 page document into 6 separate PDFS with:
split.desc.3=Document #1: Page 1
split.desc.4=Document #2: Page 2 and 3
split.desc.5=Document #3: Page 4, 5 and 6
split.desc.6=Document #4: Page 7
split.desc.7=Document #5: Page 8
split.desc.8=Document #6: Page 9 and 10
split.splitPages=Enter pages to split on:
split.submit=Split
#merge
imageToPDF.title=Image to PDF
imageToPDF.header=Image to PDF
imageToPDF.submit=Convert
#pdfToImage
pdfToImage.title=PDF to Image
pdfToImage.header=PDF to Image
pdfToImage.selectText=Image Format
pdfToImage.singleOrMultiple=Image result type
pdfToImage.single=Single Big Image
pdfToImage.multi=Multiple Images
pdfToImage.colorType=Color type
pdfToImage.color=Color
pdfToImage.grey=Grayscale
pdfToImage.blackwhite=Black and White (May lose data!)
pdfToImage.submit=Convert
#addPassword
addPassword.title=Add Password
addPassword.header=Add password (Encrypt)
addPassword.selectText.1=Select PDF to encrypt
addPassword.selectText.2=Password
addPassword.selectText.3=Encryption Key Length
addPassword.selectText.4=Higher values are stronger, but lower values have better compatibility.
addPassword.selectText.5=Permissions to set
addPassword.selectText.6=Prevent assembly of document
addPassword.selectText.7=Prevent content extraction
addPassword.selectText.8=Prevent extraction for accessibility
addPassword.selectText.9=Prevent filling in form
addPassword.selectText.10=Prevent modification
addPassword.selectText.11=Prevent annotation modification
addPassword.selectText.12=Prevent printing
addPassword.selectText.13=Prevent printing different formats
addPassword.submit=Encrypt
#watermark
watermark.title=Add Watermark
watermark.header=Add Watermark
watermark.selectText.1=Select PDF to add watermark to:
watermark.selectText.2=Watermark Text:
watermark.selectText.3=Font Size:
watermark.selectText.4=Rotation (0-360):
watermark.selectText.5=widthSpacer (Space between each watermark horizontally):
watermark.selectText.6=heightSpacer (Space between each watermark vertically):
watermark.submit=Add Watermark
#remove-watermark
remove-watermark.title=Remove Watermark
remove-watermark.header=Remove Watermark
remove-watermark.selectText.1=Select PDF to remove watermark from:
remove-watermark.selectText.2=Watermark Text:
remove-watermark.submit=Remove Watermark
#Change permissions
permissions.title=Change Permissions
permissions.header=Change Permissions
permissions.warning=Warning to have these permissions be unchangeable it is recommended to set them with a password via the add-password page
permissions.selectText.1=Select PDF to change permissions
permissions.selectText.2=Permissions to set
permissions.selectText.3=Prevent assembly of document
permissions.selectText.4=Prevent content extraction
permissions.selectText.5=Prevent extraction for accessibility
permissions.selectText.6=Prevent filling in form
permissions.selectText.7=Prevent modification
permissions.selectText.8=Prevent annotation modification
permissions.selectText.9=Prevent printing
permissions.selectText.10=Prevent printing different formats
permissions.submit=Change
#remove password
removePassword.title=Remove password
removePassword.header=Remove password (Decrypt)
removePassword.selectText.1=Select PDF to Decrypt
removePassword.selectText.2=Password
removePassword.submit=Remove
changeMetadata.title=Change Metadata
changeMetadata.header=Change Metadata
changeMetadata.selectText.1=Please edit the variables you wish to change
changeMetadata.selectText.2=Delete all metadata
changeMetadata.selectText.3=Show Custom Metadata:
changeMetadata.author=Author:
changeMetadata.creationDate=Creation Date (yyyy/MM/dd HH:mm:ss):
changeMetadata.creator=Creator:
changeMetadata.keywords=Keywords:
changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss):
changeMetadata.producer=Producer:
changeMetadata.subject=Subject:
changeMetadata.title=Title:
changeMetadata.trapped=Trapped:
changeMetadata.selectText.4=Other Metadata:
changeMetadata.selectText.5=Add Custom Metadata Entry
changeMetadata.submit=Change
fileToPDF.credit=This service uses LibreOffice and Unoconv for file conversion.
fileToPDF.supportedFileTypes=Supported file types should include the below however for a full updated list of supported formats, please refer to the LibreOffice documentation

View File

@@ -5,7 +5,7 @@
###########
# Generic #
###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
# the direction that the language is written (ltr=left to right, rtl=right to left)
language.direction=ltr
pdfPrompt=Choisir PDF
@@ -81,8 +81,8 @@ home.changeMetadata.desc=Modifier/Supprimer/Ajouter des m
home.fileToPDF.title=Convertir un fichier en PDF
home.fileToPDF.desc=Convertissez presque n\u2019importe quel fichier en PDF (DOCX, PNG, XLS, PPT, TXT et plus)
home.ocr.title=Exécuter OCR sur PDF
home.ocr.desc=Analyse et détecte le texte des images d\u2019un fichier PDF et le rajoute en tant que texte.
home.ocr.title=Exécuter l'OCR sur les scans PDF et/ou de nettoyage
home.ocr.desc=Le nettoyage analyse et détecte le texte des images dans un PDF et le rajoute en tant que texte.
home.extractImages.title=Extraire les images
home.extractImages.desc=Extrait toutes les images d\u2019un PDF et les enregistre au format zip
@@ -96,14 +96,22 @@ settings.downloadOption.title=Choisissez l\u2019option de t
settings.downloadOption.1=Ouvrir dans la même fenêtre
settings.downloadOption.2=Ouvrir dans une nouvelle fenêtre
settings.downloadOption.3=Fichier téléchargé
settings.zip=Fichiers multi-téléchargements Zip
settings.zipThreshold=Zip les fichiers lorsque le nombre de fichiers téléchargés dépasse
#OCR
ocr.title=OCR
ocr.header=OCR (reconnaissance optique de caractères)
ocr.selectText.1=Sélectionnez les langues à détecter dans le fichier PDF (celles répertoriées sont celles actuellement détectées) :
ocr.selectText.2=Produire un fichier texte contenant du texte OCR à côté du PDF OCR
ocr.title=OCR / Nettoyage de numérisation
ocr.header=Nettoyage des scans / OCR (reconnaissance optique des caractères)
ocr.selectText.1=Sélectionnez les langues à détecter dans le PDF (celles répertoriées sont celles actuellement détectées) :
ocr.selectText.2=Produire un fichier texte contenant du texte OCR avec le PDF OCR
ocr.selectText.3=Les pages correctes ont été numérisées à un angle oblique en les remettant en place
ocr.selectText.4=Nettoyer la page pour qu'il soit moins probable que l'OCR trouve du texte dans le bruit de fond. (Pas de changement de sortie)
ocr.selectText.5=Nettoyer la page afin qu'il soit moins probable que l'OCR trouve du texte dans le bruit de fond, maintient le nettoyage dans la sortie.
ocr.selectText.6=Ignore les pages contenant du texte interactif, seulement les pages OCR qui sont des images
ocr.selectText.7=Forcer l'OCR, OCR chaque page supprimera tous les éléments de texte d'origine
ocr.selectText.8=Normal (Erreur si le PDF contient du texte)
ocr.selectText.9=Paramètres supplémentaires
ocr.selectText.10=Mode ROC
ocr.help=Veuillez lire cette documentation pour savoir comment l\u2019utiliser pour d\u2019autres langues et/ou une utilisation non dans docker
ocr.credit=Ce service utilise OCRmyPDF et Tesseract pour l\u2019OCR.
ocr.submit=Traiter PDF avec OCR
@@ -133,7 +141,14 @@ 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.credit=Ce service utilise OCRmyPDF pour la compression/optimisation PDF.
compress.selectText.1=Niveau d\u2019optimisation :
compress.selectText.2=0 (pas d\u2019optimisation)
compress.selectText.3=1 (par défaut, optimisation sans perte)
compress.selectText.4=2 (optimisation avec perte)
compress.selectText.5=3 (optimisation avec perte, plus agressive)
compress.selectText.6=Activer l\u2019affichage Web rapide (linéariser PDF)
compress.selectText.7=Activer l\u2019encodage JBIG2 avec perte
compress.submit=Compresser
@@ -182,6 +197,11 @@ split.submit=Diviser
imageToPDF.title=Image au format PDF
imageToPDF.header=Image au format PDF
imageToPDF.submit=Convertir
imageToPDF.selectText.1=Étirer pour s'adapter
imageToPDF.selectText.2=Rotation automatique du PDF
imageToPDF.selectText.3=Logique de fichiers multiples (activé uniquement si vous travaillez avec plusieurs images)
imageToPDF.selectText.4= Fusionner en un seul PDF
imageToPDF.selectText.5= Convertir en PDFs distincts
#pdfToImage
pdfToImage.title=PDF vers image
@@ -223,6 +243,7 @@ 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.selectText.7=Opacité (0 % - 100 %):
watermark.submit=Ajouter un filigrane
#remove-watermark
@@ -279,3 +300,7 @@ xlsToPdf.header=Excel en PDF
xlsToPdf.selectText.1=Sélectionnez une feuille Excel XLS ou XLSX à convertir
xlsToPdf.convert=convertir
pdfToPDFA.title=PDF vers PDF/A
pdfToPDFA.header=PDF vers PDF/A
pdfToPDFA.credit=Ce service utilise OCRmyPDF pour la conversion PDF/A
pdfToPDFA.submit=Convertir

View File

@@ -15,9 +15,9 @@
<div class="col-md-6">
<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 th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput2" name="fileInput2" required>
<input type="file" class="custom-file-input" id="fileInput2" name="fileInput2" accept="image/*" required>
<label class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label>
</div>
<div class="form-group">
@@ -26,7 +26,7 @@
<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>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{addImage.submit}"></button>
</form>
</div>
</div>

View File

@@ -15,7 +15,7 @@
<div class="col-md-6">
<h2 th:text="#{compress.header}"></h2>
<form action="#" th:action="@{/compress-pdf}" 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, accept='application/pdf')}"></div>
<div>
<label for="optimizeLevel" th:text="#{compress.selectText.1}"></label>
<select name="optimizeLevel" id="optimizeLevel">
@@ -33,7 +33,7 @@
<input type="checkbox" name="jbig2Lossy" id="jbig2Lossy">
<label for="jbig2Lossy" th:text="#{compress.selectText.7}"></label>
</div>
<button type="submit" th:text="#{compress.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{compress.submit}"></button>
</form>
<p class="mt-3" th:text="#{compress.credit}"></p>

View File

@@ -17,7 +17,7 @@
<form method="post" enctype="multipart/form-data" th:action="@{file-to-pdf}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<br>
<button type="submit" class="btn btn-primary" th:text="#{fileToPDF.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{fileToPDF.submit}"></button>
</form>
<p class="mt-3" th:text="#{fileToPDF.credit}"></p>

View File

@@ -15,13 +15,56 @@
<h2 th:text="#{imageToPDF.header}"></h2>
<form method="post" enctype="multipart/form-data" th:action="@{img-to-pdf}">
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" required>
<label class="custom-file-label" for="fileInput" th:text="#{imgPrompt}"></label>
</div>
<br> <br>
<button type="submit" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='image/*', inputText=#{imgPrompt})}"></div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="stretchToFit" id="stretchToFit">
<label class="ml-3" for="stretchToFit" th:text=#{imageToPDF.selectText.1}></label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="autoRotate" id="autoRotate">
<label class="ml-3" for="autoRotate" th:text=#{imageToPDF.selectText.2}></label>
</div>
<br>
<input type="hidden" id="override" name="override" value="multi">
<div class="form-group">
<label th:text=#{imageToPDF.selectText.3}></label>
<select class="form-control" id="conversionType" name="conversionType" disabled>
<option value="merge" th:text=#{imageToPDF.selectText.4}></option>
<option value="convert" th:text=#{imageToPDF.selectText.5} selected></option>
</select>
</div>
<br> <br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
<script>
$('#fileInput-input').on('change', function() {
var files = document.getElementById("fileInput-input").files;
var conversionType = document.getElementById("conversionType");
console.log("files.length=" + files.length)
if (files.length > 1) {
conversionType.disabled = false;
} else {
conversionType.disabled = true;
}
});
$('#conversionType').change(function() {
var selectedValue = $(this).val();
var override = document.getElementById("override");
console.log("selectedValue=" + selectedValue)
if (selectedValue === 'merge') {
override.value = "single";
} else if (selectedValue === 'convert') {
override.value = "multi";
}
});
</script>
</form>
</div>
</div>

View File

@@ -16,7 +16,7 @@
<h2 th:text="#{pdfToImage.header}"></h2>
<p th:text="#{processTimeWarning}"></p>
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-img}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="form-group">
<label th:text="#{pdfToImage.selectText}"></label>
<select class="form-control" name="imageFormat">
@@ -44,7 +44,7 @@
<label for="dpi">DPI:</label>
<input type="number" name="dpi" class="form-control" id="dpi" min="1" max="100" step="1" value="30" required>
</div>
<button type="submit" class="btn btn-primary" th:text="#{pdfToImage.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToImage.submit}"></button>
</form>
</div>

View File

@@ -0,0 +1,31 @@
<!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=#{pdfToPDFA.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="#{pdfToPDFA.header}"></h2>
<p>Currently doesn't work for multiple inputs at once</p>
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-pdfa}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToPDFA.submit}"></button>
</form>
<p class="mt-3" th:text="#{pdfToPDFA.credit}"></p>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>

View File

@@ -15,7 +15,7 @@
<h2 th:text="#{extractImages.header}"></h2>
<form id="multiPdfForm" th:action="@{extract-images}" 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, accept='application/pdf')}"></div>
<div class="form-group">
<label th:text="#{extractImages.selectText}"></label>
<select class="form-control" name="format">
@@ -24,7 +24,7 @@
<option value="gif">GIF</option>
</select>
</div>
<button type="submit" class="btn btn-primary" th:text="#{extractImages.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{extractImages.submit}"></button>
</form>
</div>
</div>

View File

@@ -2,7 +2,8 @@
<!-- Metadata -->
<meta charset="UTF-8">
<title th:text="'S-PDF ' + ${title}"></title>
<title th:text="${@appName} + (${title} != null and ${title} != '' ? ' - ' + ${title} : '')"></title>
<link rel="shortcut icon" href="favicon.svg">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -63,11 +64,11 @@ function toggleDarkMode() {
</script>
</head>
<th:block th:fragment="fileSelector(name, multiple)">
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}">
<div class="custom-file-chooser">
<div class="custom-file">
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" multiple>
<label class="custom-file-label" th:for="${name}+'-input'" th:text="#{pdfPrompt}"></label>
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple>
<label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label>
</div>
<div class="selected-files"></div>
</div>
@@ -83,6 +84,10 @@ function toggleDarkMode() {
<script>
$('form').submit(function(event) {
var processing = "Processing..."
var submitButtonText = $('#submitBtn').text()
$('#submitBtn').text('Processing...');
console.log("start download code")
var files = $('#fileInput-input')[0].files;
var url = this.action;
@@ -90,7 +95,9 @@ function toggleDarkMode() {
event.preventDefault(); // Prevent the default form handling behavior
/* Check if ${multiple} is false */
var multiple = [[${multiple}]] || false;
if (!multiple && files.length > 1) {
var override = $('#override').val() || '';
console.log("override=" + override)
if (override === 'multi' || (!multiple && files.length > 1) && override !== 'single' ) {
console.log("multi parallel download")
submitMultiPdfForm(event,url);
} else {
@@ -184,104 +191,118 @@ function toggleDarkMode() {
// Create an error message to display to the user
const message = `${errorMessage}\n\n${stackTrace}`;
$('#submitBtn').text(submitButtonText);
// Display the error message to the user
alert(message);
});
}
$('#submitBtn').text(submitButtonText);
});
async function submitMultiPdfForm(event,url) {
async function submitMultiPdfForm(event, url) {
// Get the selected PDF files
var files = $('#fileInput-input')[0].files;
let files = $('#fileInput-input')[0].files;
// Get the existing form data
var formData = new FormData($('form')[0]);
let formData = new FormData($('form')[0]);
formData.delete('fileInput');
// Show the progress bar
$('#progressBarContainer').show();
// Initialize the progress bar
var progressBar = $('#progressBar');
let progressBar = $('#progressBar');
progressBar.css('width', '0%');
progressBar.attr('aria-valuenow', 0);
progressBar.attr('aria-valuemax', files.length);
// Check the flag in localStorage
const zipFiles = localStorage.getItem('zipParallelFiles') === 'true';
// Check the flag in localStorage, default to 4
const zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4;
const zipFiles = files.length > zipThreshold;
// Initialize JSZip instance if needed
let jszip = null;
if (zipFiles) {
jszip = new JSZip();
jszip = new JSZip();
}
// Submit each PDF file in parallel
var promises = [];
for (var i = 0; i < files.length; i++) {
var promise = new Promise(function(resolve, reject) {
var fileFormData = new FormData();
let promises = [];
for (let i = 0; i < files.length; i++) {
let promise = new Promise(async function(resolve, reject) {
let fileFormData = new FormData();
fileFormData.append('fileInput', files[i]);
for (var pair of formData.entries()) {
for (let pair of formData.entries()) {
fileFormData.append(pair[0], pair[1]);
}
console.log(fileFormData);
fetch(url, {
method: 'POST',
body: fileFormData
}).then(function(response) {
try {
let response = await fetch(url, {
method: 'POST',
body: fileFormData
});
if (!response) {
throw new Error('Received null response for file ' + i);
}
if (!response.ok) {
throw new Error(`Error submitting request for file ${i}: ${response.status} ${response.statusText}`);
}
let contentDisposition = response.headers.get('content-disposition');
let fileName = "file.pdf"
if (!contentDisposition) {
//throw new Error('Content-Disposition header not found for file ' + i);
} else {
fileName = contentDisposition.split('filename=')[1].replace(/"/g, '');
}
console.log('Received response for file ' + i + ': ' + response);
var contentDisposition = response.headers.get('content-disposition');
var fileName = contentDisposition.split('filename=')[1].replace(/"/g, '');
response.blob().then(function (blob) {
if (zipFiles) {
// Add the file to the ZIP archive
jszip.file(fileName, blob);
resolve();
} else {
// Download the file directly
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
a.remove();
resolve();
}
});
}).catch(function(error) {
let blob = await response.blob();
if (zipFiles) {
// Add the file to the ZIP archive
jszip.file(fileName, blob);
resolve();
} else {
// Download the file directly
let url = window.URL.createObjectURL(blob);
let a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
a.remove();
resolve();
}
} catch (error) {
console.error('Error submitting request for file ' + i + ': ' + error);
// Set default values or fallbacks for error properties
var status = error && error.status || 500;
var statusText = error && error.statusText || 'Internal Server Error';
var message = error && error.message || 'An error occurred while processing your request.';
// Reject the Promise to signal that the request has failed
let status = error && error.status || 500;
let statusText = error && error.statusText || 'Internal Server Error';
let message = error && error.message || 'An error occurred while processing your request.';
// Reject the Promise to signal that the request has failed
reject();
// Redirect to error page with Spring Boot error parameters
var url = '/error?status=' + status + '&error=' + encodeURIComponent(statusText) + '&message=' + encodeURIComponent(message);
// Redirect to error page with Spring Boot error parameters
let url = '/error?status=' + status + '&error=' + encodeURIComponent(statusText) + '&message=' + encodeURIComponent(message);
window.location.href = url;
});
}
});
// Update the progress bar as each request finishes
promise.then(function() {
var progress = ((progressBar.attr('aria-valuenow') / files.length) * 100) + (100 / files.length);
progressBar.css('width', progress + '%');
progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1);
updateProgressBar(progressBar, files);
});
promises.push(promise);
}
@@ -291,24 +312,33 @@ function toggleDarkMode() {
} catch (error) {
console.error('Error while uploading files: ' + error);
}
// Update the progress bar
progressBar.css('width', '100%');
progressBar.attr('aria-valuenow', files.length);
// After all requests are finished, download the ZIP file if needed
// After all requests are finished, download the ZIP file if needed
if (zipFiles) {
jszip.generateAsync({ type: "blob" }).then(function (content) {
var url = window.URL.createObjectURL(content);
var a = document.createElement('a');
a.href = url;
a.download = "files.zip";
document.body.appendChild(a);
a.click();
a.remove();
});
try {
let content = await jszip.generateAsync({ type: "blob" });
let url = window.URL.createObjectURL(content);
let a = document.createElement('a');
a.href = url;
a.download = "files.zip";
document.body.appendChild(a);
a.click();
a.remove();
} catch (error) {
console.error('Error generating ZIP file: ' + error);
}
}
}
function updateProgressBar(progressBar, files) {
let progress = ((progressBar.attr('aria-valuenow') / files.length) * 100) + (100 / files.length);
progressBar.css('width', progress + '%');
progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1);
}
@@ -355,6 +385,7 @@ function toggleDarkMode() {
fileNames.forEach(fileName => {
selectedFilesContainer.append("<div>" + fileName + "</div>");
});
console.log("fileNames.length=" + fileNames.length)
if (fileNames.length === 1) {
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]);
} else if (fileNames.length > 1) {

View File

@@ -1,7 +1,8 @@
<div th:fragment="footer">
<footer id="footer" class="text-center py-3">
<a href="https://github.com/Frooodle/Stirling-PDF" target="_blank" class="mx-1"><img src="images/github.svg"></img></a>
<a href="https://hub.docker.com/r/frooodle/s-pdf" target="_blank" class="mx-1"><img src="images/docker.svg"></img></a>
<a href="https://discord.gg/Cn8pWhQRxZ" target="_blank" class="mx-1"><img src="images/discord.svg"></img></a>
</footer>
<footer id="footer" class="text-center py-3">
<a href="https://github.com/Frooodle/Stirling-PDF" target="_blank" class="mx-1"><img src="images/github.svg"></img></a>
<a href="https://hub.docker.com/r/frooodle/s-pdf" target="_blank" class="mx-1"><img src="images/docker.svg"></img></a>
<a href="https://discord.gg/Cn8pWhQRxZ" target="_blank" class="mx-1"><img src="images/discord.svg"></img></a>
<div th:if="${@appName} != 'Stirling PDF'" class="mt-2" style="color: grey;">Powered by Stirling PDF</div>
</footer>
</div>

View File

@@ -109,7 +109,7 @@ function compareVersions(version1, version2) {
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container ">
<a class="navbar-brand" href="#" th:href="@{/}">Stirling PDF</a>
<a class="navbar-brand" href="#" th:href="@{/}" th:text="${@navBarText}"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
@@ -127,12 +127,13 @@ function compareVersions(version1, version2) {
<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>
<li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-to-pdf' OR ${currentPage}=='file-to-pdf' OR ${currentPage}=='xlsx-to-pdf' ? 'active' : ''"><a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
<li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-to-pdf' OR ${currentPage}=='pdf-to-pdfa' OR ${currentPage}=='file-to-pdf' OR ${currentPage}=='xlsx-to-pdf' ? 'active' : ''"><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">
<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="dropdown-item" href="#" th:href="@{img-to-pdf}" th:classappend="${currentPage}=='img-to-pdf' ? 'active' : ''" th:text="#{home.imageToPdf.title}"></a>
<a class="dropdown-item" href="#" th:href="@{file-to-pdf}" th:classappend="${currentPage}=='file-to-pdf' ? 'active' : ''" th:text="#{home.fileToPDF.title}"></a>
<a class="dropdown-item" href="#" th:href="@{pdf-to-pdfa}" th:classappend="${currentPage}=='pdf-to-pdfa' ? 'active' : ''" th:text="#{home.pdfToPDFA.title}"></a>
</div>
</li>
@@ -172,8 +173,7 @@ function compareVersions(version1, version2) {
</svg>
</a>
<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_GB">English (UK)</a>
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="en_GB">English</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>
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="fr_FR">Français</a>
@@ -206,25 +206,24 @@ function compareVersions(version1, version2) {
</div>
<div class="modal-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<p class="mb-0" th:text="#{settings.appVersion} + ' ' + ${@appVersion}"></p>
<p class="mb-0" th:utext="#{settings.appVersion} + ' ' + ${@appVersion}"></p>
<a href="https://github.com/Frooodle/Stirling-PDF/releases" target="_blank">
<button type="button" class="btn btn-sm btn-outline-primary" id="update-btn" th:text="#{settings.update}"></button>
<button type="button" class="btn btn-sm btn-outline-primary" id="update-btn" th:utext="#{settings.update}"></button>
</a>
</div>
<div class="form-group">
<label for="downloadOption" th:text="#{settings.downloadOption.title}"></label>
<label for="downloadOption" th:utext="#{settings.downloadOption.title}"></label>
<select class="form-control" id="downloadOption">
<option value="sameWindow" th:text="#{settings.downloadOption.1}"></option>
<option value="newWindow" th:text="#{settings.downloadOption.2}"></option>
<option value="downloadFile" th:text="#{settings.downloadOption.3}"></option>
<option value="sameWindow" th:utext="#{settings.downloadOption.1}"></option>
<option value="newWindow" th:utext="#{settings.downloadOption.2}"></option>
<option value="downloadFile" th:utext="#{settings.downloadOption.3}"></option>
</select>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="zipParallelFiles">
<label class="custom-control-label" for="zipParallelFiles" th:text="#{settings.zip}"></label>
</div>
<label for="zipThreshold" th:utext="#{settings.zipThreshold}"></label>
<input type="range" class="custom-range" min="0" max="9" step="1" id="zipThreshold" value="4">
<span id="zipThresholdValue" class="ml-2"></span>
</div>
</div>
<div class="modal-footer">
@@ -253,17 +252,20 @@ function compareVersions(version1, version2) {
});
// Get the zipParallelFiles flag from local storage, or set it to false if it doesn't exist
var zipParallelFiles = localStorage.getItem('zipParallelFiles') === 'true';
// Get the zipThreshold value from local storage, or set it to 0 if it doesn't exist
var zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4;
// Set the checked state of the checkbox
document.getElementById('zipParallelFiles').checked = zipParallelFiles;
// Set the value of the slider and the display span
document.getElementById('zipThreshold').value = zipThreshold;
document.getElementById('zipThresholdValue').textContent = zipThreshold;
// Save the checked state of the checkbox to local storage when it changes
document.getElementById('zipParallelFiles').addEventListener('change', function () {
zipParallelFiles = this.checked;
localStorage.setItem('zipParallelFiles', zipParallelFiles);
// Save the selected value to local storage when the slider value changes
document.getElementById('zipThreshold').addEventListener('input', function () {
zipThreshold = this.value;
document.getElementById('zipThresholdValue').textContent = zipThreshold;
localStorage.setItem('zipThreshold', zipThreshold);
});
</script>

View File

@@ -33,14 +33,16 @@
<!-- Jumbotron -->
<div class="jumbotron jumbotron-fluid" id="jumbotron">
<div class="container">
<h1 class="display-4">Stirling PDF</h1>
<p class="lead" th:text="#{home.desc}"></p>
<h1 class="display-4" th:text="${@appName}"></h1>
<p class="lead" th:text="${@homeText != 'null' and @homeText != null and @homeText != ''} ? ${@homeText} : #{home.desc}"></p>
</div>
</div>
<!-- Features -->
<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.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>
@@ -62,6 +64,7 @@
<div th:replace="~{fragments/card :: card(cardTitle=#{home.ocr.title}, cardText=#{home.ocr.desc}, cardLink='ocr-pdf')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.extractImages.title}, cardText=#{home.extractImages.desc}, cardLink='extract-images')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfToPDFA.title}, cardText=#{home.pdfToPDFA.desc}, cardLink='pdf-to-pdfa')}"></div>

View File

@@ -16,16 +16,14 @@
<form action="merge-pdfs" method="post" enctype="multipart/form-data">
<div class="form-group">
<label th:text="#{multiPdfDropPrompt}"></label>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" multiple required>
<label class="custom-file-label" th:text="#{pdfPrompt}"></label>
</div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=true, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<ul id="selectedFiles" class="list-group"></ul>
</div>
<div class="form-group text-center">
<button type="submit" class="btn btn-primary" th:text="#{merge.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{merge.submit}"></button>
</div>
</form>
<style>
@@ -61,7 +59,7 @@
</style>
<script>
document.getElementById("fileInput").addEventListener("change", function() {
document.getElementById("fileInput-input").addEventListener("change", function() {
var files = this.files;
var list = document.getElementById("selectedFiles");
list.innerHTML = "";
@@ -121,7 +119,7 @@
}
}
}
document.getElementById("fileInput").files = dataTransfer.files;
document.getElementById("fileInput-input").files = dataTransfer.files;
}
});

View File

@@ -15,26 +15,51 @@
<div class="col-md-6">
<h2 th:text="#{ocr.header}"></h2>
<form action="#" th:action="@{/ocr-pdf}" method="post" enctype="multipart/form-data" class="mb-3">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div class="form-group">
<label for="languages" class="form-label" th:text="#{ocr.selectText.1}"></label>
<div id="languages">
<div th:each="language: ${languages}">
<input type="checkbox" class="form-check-input" th:name="languages" th:value="${language}" th:id="${'language-' + language}" />
<label class="form-check-label" th:for="${'language-' + language}" th:text="${language}"></label>
</div>
</div>
</div>
<!-- <div class="form-group">
<input type="checkbox" class="form-check-input" name="sidecar" id="sidecar" />
<label class="form-check-label" for="sidecar" th:text="#{ocr.selectText.2}"></label>
</div> -->
<button type="submit" class="btn btn-primary" th:text="#{ocr.submit}"></button>
</form>
<p th:text="#{ocr.credit}"></p>
<p th:text="#{ocr.help}"></p>
<a href="https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md">https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md</a>
<form action="#" th:action="@{/ocr-pdf}" method="post" enctype="multipart/form-data" class="mb-3">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="form-group">
<label for="languages" class="form-label" th:text="#{ocr.selectText.1}"></label>
<hr>
<div id="languages">
<div th:each="language, iterStat : ${languages}" >
<input type="checkbox" class="form-check-input" th:name="languages" th:value="${language}" th:id="${'language-' + language}" />
<label class="form-check-label" th:for="${'language-' + language}" th:text=" ${language}"></label>
</div>
</div>
<hr>
</div>
<div class="form-group">
<label th:text="#{ocr.selectText.10}"></label>
<select class="form-control" name="ocrType">
<option value="skip-text" th:text="#{ocr.selectText.6}"></option>
<option value="force-ocr" th:text="#{ocr.selectText.7}"></option>
<option value="Normal" th:text="#{ocr.selectText.8}"></option>
</select>
</div>
<br>
<label for="languages" class="form-label" th:text="#{ocr.selectText.9}"></label>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="sidecar" id="sidecar" />
<label class="form-check-label" for="sidecar" th:text="#{ocr.selectText.2}"></label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="deskew" id="deskew" />
<label class="form-check-label" for="deskew" th:text="#{ocr.selectText.3}"></label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="clean" id="clean" />
<label class="form-check-label" for="clean" th:text="#{ocr.selectText.4}"></label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="clean-final" id="clean-final" />
<label class="form-check-label" for="clean-final" th:text="#{ocr.selectText.5}"></label>
</div>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{ocr.submit}"></button>
</form>
<p th:text="#{ocr.credit}"></p>
<p th:text="#{ocr.help}"></p>
<a href="https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md">https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md</a>
</div>
</div>
</div>

View File

@@ -15,12 +15,12 @@
<h2 th:text="#{pdfOrganiser.header}"></h2>
<form th:action="@{rearrange-pages}" 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, accept='application/pdf')}"></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>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
</form>
</div>

View File

@@ -15,12 +15,12 @@
<h2 th:text="#{pageRemover.header}"></h2>
<form th:action="@{remove-pages}" 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, accept='application/pdf')}"></div>
<div class="form-group">
<label for="pagesToDelete" th:text="#{pageRemover.pagesToDelete}"></label>
<input type="text" class="form-control" id="fileInput" name="pagesToDelete" placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
</div>
<button type="submit" class="btn btn-primary" th:text="#{pageRemover.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pageRemover.submit}"></button>
</form>
</div>
</div>

View File

@@ -16,7 +16,7 @@
<h2 th:text="#{rotate.header}"></h2>
<form action="#" th:action="@{rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<input type="hidden" id="angleInput" name="angle" value="0">
<div id="editSection" style="display: none">
@@ -31,7 +31,7 @@
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
</svg>
</button>
<button type="submit" class="btn btn-primary" th:text="#{rotate.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{rotate.submit}"></button>
<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">
<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" />

View File

@@ -16,7 +16,7 @@
<form action="add-password" method="post" enctype="multipart/form-data">
<div class="form-group">
<label th:text="#{addPassword.selectText.1}"></label>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<label th:text="#{addPassword.selectText.2}"></label> <input type="password" class="form-control" id="password" name="password" required>
@@ -66,7 +66,7 @@
</div>
<br />
<div class="form-group text-center">
<button type="submit" class="btn btn-primary" th:text="#{addPassword.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{addPassword.submit}"></button>
</div>
</form>

View File

@@ -16,7 +16,7 @@
<form method="post" enctype="multipart/form-data" action="add-watermark">
<div class="form-group">
<label th:text="#{watermark.selectText.1}"></label>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<label for="watermarkText" th:text="#{watermark.selectText.2}"></label>
@@ -26,6 +26,45 @@
<label for="fontSize" th:text="#{watermark.selectText.3}"></label>
<input type="text" id="fontSize" name="fontSize" class="form-control" value="30" />
</div>
<div class="form-group">
<label for="opacity" th:text="#{watermark.selectText.7}"></label>
<input type="text" id="opacity" name="opacityText" class="form-control" value="50" onblur="updateopacityValue()" />
<input type="hidden" id="opacityReal" name="opacity" value="0.5">
</div>
<script>
const opacityInput = document.getElementById('opacity');
const opacityRealInput = document.getElementById('opacityReal');
const updateopacityValue = () => {
let percentageValue = parseFloat(opacityInput.value.replace('%', ''));
if (isNaN(percentageValue)) {
percentageValue = 0;
}
percentageValue = Math.min(Math.max(percentageValue, 0), 100);
opacityInput.value = `${percentageValue}`;
opacityRealInput.value = (percentageValue / 100).toFixed(2);
};
const appendPercentageSymbol = () => {
if (!opacityInput.value.endsWith('%')) {
opacityInput.value += '%';
}
};
opacityInput.addEventListener('focus', () => {
opacityInput.value = opacityInput.value.replace('%', '');
});
opacityInput.addEventListener('blur', () => {
updateopacityValue();
appendPercentageSymbol();
});
// Set initial values
updateopacityValue();
appendPercentageSymbol();
</script>
<div class="form-group">
<label for="rotation" th:text="#{watermark.selectText.4}"></label>
<input type="text" id="rotation" name="rotation" class="form-control" value="45" />
@@ -39,7 +78,7 @@
<input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50" />
</div>
<div class="form-group text-center">
<input type="submit" th:value="#{watermark.submit}" class="btn btn-primary" />
<input type="submit" id="submitBtn" th:value="#{watermark.submit}" class="btn btn-primary" />
</div>
</form>
</div>

View File

@@ -14,7 +14,7 @@
<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>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
<div class="form-group-inline form-check">
@@ -83,7 +83,7 @@
<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>
<button class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button>
<script>
const deleteAllCheckbox = document.querySelector("#deleteAll");

View File

@@ -17,7 +17,7 @@
<form action="add-password" method="post" enctype="multipart/form-data">
<div class="form-group">
<label th:text="#{permissions.selectText.1}"></label>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<label th:text="#{permissions.selectText.2}"></label>
@@ -57,7 +57,7 @@
</div>
<br />
<div class="form-group text-center">
<button type="submit" class="btn btn-primary" th:text="#{permissions.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{permissions.submit}"></button>
</div>
</form>

View File

@@ -16,7 +16,7 @@
<form action="remove-password" method="post" enctype="multipart/form-data">
<div class="form-group">
<label th:text="#{removePassword.selectText.1}"></label>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<label th:text="#{removePassword.selectText.2}"></label>
@@ -24,7 +24,7 @@
</div>
<br />
<div class="form-group text-center">
<button type="submit" class="btn btn-primary" th:text="#{removePassword.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{removePassword.submit}"></button>
</div>
</form>
</div>

View File

@@ -16,14 +16,14 @@
<form method="post" enctype="multipart/form-data" action="remove-watermark">
<div class="form-group">
<label th:text="#{remove-watermark.selectText.1}"></label>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<label for="watermarkText" th:text="#{remove-watermark.selectText.2}"></label>
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
</div>
<div class="form-group text-center">
<input type="submit" th:value="#{remove-watermark.submit}" class="btn btn-primary" />
<input type="submit" id="submitBtn" th:value="#{remove-watermark.submit}" class="btn btn-primary" />
</div>
</form>
</div>

View File

@@ -24,14 +24,14 @@
<p th:text="#{split.desc.8}"></p>
<form th:action="@{split-pages}" 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, accept='application/pdf')}"></div>
<div class="form-group">
<label for="pages" th:text="#{split.splitPages}"></label>
<input type="text" class="form-control" id="pages" name="pages" placeholder="1,3,5-10" required>
</div>
<br>
<button type="submit" class="btn btn-primary" th:text="#{split.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split.submit}"></button>
</form>
</div>
</div>