Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4459eb6d6 | ||
|
|
9087a3ebbd | ||
|
|
56669f4247 | ||
|
|
22be1a1104 | ||
|
|
a80c585086 | ||
|
|
6d5dbd9729 | ||
|
|
0b4e3de455 | ||
|
|
8faef235a6 | ||
|
|
559bc7c731 | ||
|
|
3d7fdd0f35 | ||
|
|
726fcf861c | ||
|
|
839105f41d | ||
|
|
a2a27e2216 | ||
|
|
f866c8a61f | ||
|
|
472082cb03 | ||
|
|
b5a59ddb6a | ||
|
|
82da3c0027 | ||
|
|
908e75de39 | ||
|
|
a9145fe84c | ||
|
|
54abb53842 | ||
|
|
c2aa7b27ce | ||
|
|
a90e69366b | ||
|
|
74b94230fd | ||
|
|
e3ab333a59 | ||
|
|
ac9ed63f97 | ||
|
|
3bb7b1343a | ||
|
|
8db58dae2c | ||
|
|
d2aa1c2e39 | ||
|
|
8176eb1372 | ||
|
|
37c37f22d5 | ||
|
|
d2aa442b4a | ||
|
|
3547f3fab9 | ||
|
|
a338ad21cd | ||
|
|
d276e581a1 | ||
|
|
51fe6970ef |
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Ignore all JavaScript files in a directory
|
||||
src/main/resources/static/pdfjs/* linguist-vendored
|
||||
src/main/resources/static/css/bootstrap-icons.css linguist-vendored
|
||||
src/main/resources/static/css/bootstrap.min.css linguist-vendored
|
||||
src/main/resources/static/css/fonts/* linguist-vendored
|
||||
60
Dockerfile
60
Dockerfile
@@ -1,5 +1,61 @@
|
||||
# Build jbig2enc in a separate stage
|
||||
FROM debian:bullseye-slim as jbig2enc_builder
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
automake \
|
||||
autoconf \
|
||||
libtool \
|
||||
libleptonica-dev \
|
||||
pkg-config \
|
||||
ca-certificates \
|
||||
zlib1g-dev \
|
||||
make \
|
||||
g++
|
||||
|
||||
RUN git clone https://github.com/agl/jbig2enc && \
|
||||
cd jbig2enc && \
|
||||
./autogen.sh && \
|
||||
./configure && \
|
||||
make && \
|
||||
make install
|
||||
|
||||
# Main stage
|
||||
FROM openjdk:17-jdk-slim
|
||||
|
||||
# Install necessary dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libreoffice-core \
|
||||
libreoffice-common \
|
||||
libreoffice-writer \
|
||||
libreoffice-calc \
|
||||
libreoffice-impress \
|
||||
python3-uno \
|
||||
python3-pip \
|
||||
unoconv \
|
||||
pngquant \
|
||||
unpaper \
|
||||
ocrmypdf && \
|
||||
pip install --user --upgrade ocrmypdf
|
||||
|
||||
# Copy the jbig2enc binary from the builder stage
|
||||
COPY --from=jbig2enc_builder /usr/local/bin/jbig2 /usr/local/bin/jbig2
|
||||
|
||||
# Copy the application JAR file
|
||||
COPY build/libs/*.jar app.jar
|
||||
|
||||
# Expose the application port
|
||||
EXPOSE 8080
|
||||
ENV LOG_LEVEL=INFO
|
||||
ENTRYPOINT ["java","-jar","/app.jar","-Dlogging.level=${LOG_LEVEL}"]
|
||||
|
||||
# Set environment variables
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
49
HowToUseOCR.md
Normal file
49
HowToUseOCR.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# OCR Language Packs and Setup
|
||||
|
||||
This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker.
|
||||
|
||||
## How does the OCR Work
|
||||
Stirling-PDF uses OCRmyPDF which in turn uses tesseract for its text recognition.
|
||||
All credit goes to them for this awesome work!
|
||||
|
||||
## Language Packs
|
||||
|
||||
Tesseract OCR supports a variety of languages. You can find additional language packs in the Tesseract GitHub repositories:
|
||||
|
||||
- [tessdata_fast](https://github.com/tesseract-ocr/tessdata_fast): These language packs are smaller and faster to load, but may provide lower recognition accuracy.
|
||||
- [tessdata](https://github.com/tesseract-ocr/tessdata): These language packs are larger and provide better recognition accuracy, but may take longer to load.
|
||||
|
||||
Depending on your requirements, you can choose the appropriate language pack for your use case. By default Stirling-PDF uses the tessdata_fast eng but this can be replaced.
|
||||
|
||||
### Installing Language Packs
|
||||
|
||||
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
|
||||
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tesseract-ocr/4.00/tessdata`
|
||||
|
||||
#### Docker
|
||||
|
||||
If you are using Docker, you need to expose the Tesseract tessdata directory as a volume in order to use the additional language packs.
|
||||
#### Docker Compose
|
||||
Modify your `docker-compose.yml` file to include the following volume configuration:
|
||||
|
||||
|
||||
```yaml
|
||||
services:
|
||||
your_service_name:
|
||||
image: your_docker_image_name
|
||||
volumes:
|
||||
- /usr/share/tesseract-ocr/4.00/tessdata:/location/of/trainingData
|
||||
```
|
||||
|
||||
|
||||
#### Docker run
|
||||
Add the following to your existing docker run command
|
||||
```bash
|
||||
-v /usr/share/tesseract-ocr/4.00/tessdata:/location/of/trainingData
|
||||
```
|
||||
|
||||
#### Non-Docker
|
||||
If you are not using Docker, you need to install the OCR components, including the ocrmypdf app.
|
||||
You can see [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html)
|
||||
|
||||
|
||||
32
README.md
32
README.md
@@ -16,7 +16,6 @@ I will support and fix/add things to this if there is a demand [Discord](https:/
|
||||
|
||||

|
||||
|
||||
|
||||
## 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,26 @@ 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
|
||||
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.
|
||||
|
||||
Using the same method you can also change the default language by providing APP_LOCALE with values like de-DE fr-FR or ar-AR to select your default language (Will always default to English on invalid locale)
|
||||
34
build.gradle
34
build.gradle
@@ -1,38 +1,43 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.0.2'
|
||||
id 'org.springframework.boot' version '3.0.5'
|
||||
id 'io.spring.dependency-management' version '1.1.0'
|
||||
}
|
||||
|
||||
group = 'stirling.software'
|
||||
version = '0.3.4'
|
||||
version = '0.4.7'
|
||||
sourceCompatibility = '17'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://repo.e-iceblue.com/nexus/content/groups/public/"
|
||||
name "com.e-iceblue"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
implementation 'org.apache.pdfbox:pdfbox:2.0.27'
|
||||
implementation 'org.apache.logging.log4j:log4j-core:2.19.0'
|
||||
implementation 'e-iceblue:spire.pdf.free:5.1.0'
|
||||
|
||||
implementation 'org.apache.poi:poi:5.2.0'
|
||||
implementation 'org.apache.poi:poi-ooxml:5.2.0'
|
||||
implementation 'com.itextpdf:itextpdf:5.5.13.2'
|
||||
|
||||
|
||||
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")
|
||||
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
manifest {
|
||||
attributes 'Implementation-Title': 'Stirling-PDF',
|
||||
'Implementation-Version': project.version
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
@@ -42,4 +47,3 @@ tasks.named('test') {
|
||||
task printVersion {
|
||||
println project.version
|
||||
}
|
||||
|
||||
|
||||
BIN
images/settings.png
Normal file
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 |
@@ -0,0 +1,94 @@
|
||||
package stirling.software.SPDF;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class LibreOfficeListener {
|
||||
|
||||
private static final LibreOfficeListener INSTANCE = new LibreOfficeListener();
|
||||
|
||||
private static final long ACTIVITY_TIMEOUT = 20 * 60 * 1000; // 20 minutes
|
||||
private static final int LISTENER_PORT = 2002;
|
||||
|
||||
private ExecutorService executorService;
|
||||
private Process process;
|
||||
private long lastActivityTime;
|
||||
|
||||
private LibreOfficeListener() {}
|
||||
|
||||
public static LibreOfficeListener getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void start() throws IOException {
|
||||
// Check if the listener is already running
|
||||
if (process != null && process.isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the listener process
|
||||
process = Runtime.getRuntime().exec("unoconv --listener");
|
||||
lastActivityTime = System.currentTimeMillis();
|
||||
|
||||
// Start a background thread to monitor the activity timeout
|
||||
executorService = Executors.newSingleThreadExecutor();
|
||||
executorService.submit(() -> {
|
||||
while (true) {
|
||||
long idleTime = System.currentTimeMillis() - lastActivityTime;
|
||||
if (idleTime >= ACTIVITY_TIMEOUT) {
|
||||
// If there has been no activity for too long, tear down the listener
|
||||
process.destroy();
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(5000); // Check for inactivity every 5 seconds
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Wait for the listener to start up
|
||||
long startTime = System.currentTimeMillis();
|
||||
long timeout = 30000; // Timeout after 30 seconds
|
||||
while (System.currentTimeMillis() - startTime < timeout) {
|
||||
if (isListenerRunning()) {
|
||||
|
||||
lastActivityTime = System.currentTimeMillis();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} // Check every 1 second
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isListenerRunning() {
|
||||
try {
|
||||
System.out.println("waiting for listener to start");
|
||||
Socket socket = new Socket();
|
||||
socket.connect(new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
|
||||
socket.close();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
// Stop the activity timeout monitor thread
|
||||
executorService.shutdownNow();
|
||||
|
||||
// Stop the listener process
|
||||
if (process != null && process.isAlive()) {
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
43
src/main/java/stirling/software/SPDF/config/AppConfig.java
Normal file
43
src/main/java/stirling/software/SPDF/config/AppConfig.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@Bean(name = "appVersion")
|
||||
public String appVersion() {
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,24 @@ public class Beans implements WebMvcConfigurer {
|
||||
@Bean
|
||||
public LocaleResolver localeResolver() {
|
||||
SessionLocaleResolver slr = new SessionLocaleResolver();
|
||||
slr.setDefaultLocale(Locale.US);
|
||||
|
||||
String appLocaleEnv = System.getProperty("APP_LOCALE");
|
||||
if(appLocaleEnv == null)
|
||||
appLocaleEnv = System.getenv("APP_LOCALE");
|
||||
Locale defaultLocale = Locale.UK; // Fallback to UK locale if environment variable is not set
|
||||
|
||||
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
|
||||
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv);
|
||||
String tempLanguageTag = tempLocale.toLanguageTag();
|
||||
|
||||
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
||||
defaultLocale = tempLocale;
|
||||
} else {
|
||||
System.err.println("Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK.");
|
||||
}
|
||||
}
|
||||
|
||||
slr.setDefaultLocale(defaultLocale);
|
||||
return slr;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
package stirling.software.SPDF.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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;
|
||||
@@ -12,16 +18,9 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.spire.pdf.PdfCompressionLevel;
|
||||
import com.spire.pdf.PdfDocument;
|
||||
import com.spire.pdf.PdfPageBase;
|
||||
import com.spire.pdf.exporting.PdfImageInfo;
|
||||
import com.spire.pdf.graphics.PdfBitmap;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
|
||||
import stirling.software.SPDF.utils.ErrorUtils;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
|
||||
//import com.spire.pdf.*;
|
||||
@Controller
|
||||
public class CompressController {
|
||||
|
||||
@@ -33,36 +32,61 @@ public class CompressController {
|
||||
return "compress-pdf";
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/compress-pdf")
|
||||
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("imageCompressionLevel") String imageCompressionLevel)
|
||||
throws IOException {
|
||||
// Load a sample PDF document
|
||||
PdfDocument document = new PdfDocument();
|
||||
document.loadFromBytes(pdfFile.getBytes());
|
||||
public ResponseEntity<byte[]> optimizePdf(
|
||||
@RequestParam("fileInput") MultipartFile inputFile,
|
||||
@RequestParam("optimizeLevel") int optimizeLevel,
|
||||
@RequestParam(name = "fastWebView", required = false) Boolean fastWebView,
|
||||
@RequestParam(name = "jbig2Lossy", required = false) Boolean jbig2Lossy) throws IOException, InterruptedException {
|
||||
|
||||
// Compress PDF
|
||||
document.getFileInfo().setIncrementalUpdate(false);
|
||||
document.setCompressionLevel(PdfCompressionLevel.Best);
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
inputFile.transferTo(tempInputFile.toFile());
|
||||
|
||||
// compress PDF Images
|
||||
for (int i = 0; i < document.getPages().getCount(); i++) {
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
PdfPageBase page = document.getPages().get(i);
|
||||
PdfImageInfo[] images = page.getImagesInfo();
|
||||
if (images != null && images.length > 0)
|
||||
for (int j = 0; j < images.length; j++) {
|
||||
PdfImageInfo image = images[j];
|
||||
PdfBitmap bp = new PdfBitmap(image.getImage());
|
||||
// bp.setPngDirectToJpeg(true);
|
||||
bp.setQuality(Integer.valueOf(imageCompressionLevel));
|
||||
// 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");
|
||||
|
||||
page.replaceImage(j, bp);
|
||||
|
||||
}
|
||||
if (fastWebView != null && fastWebView) {
|
||||
long fileSize = inputFile.getSize();
|
||||
long fastWebViewSize = (long) (fileSize * 1.25); // 25% higher than file size
|
||||
command.add("--fast-web-view");
|
||||
command.add(String.valueOf(fastWebViewSize));
|
||||
}
|
||||
|
||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_compressed.pdf");
|
||||
if (jbig2Lossy != null && jbig2Lossy) {
|
||||
command.add("--jbig2-lossy");
|
||||
}
|
||||
|
||||
}
|
||||
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("[.][^.]+$", "") + "_Optimized.pdf";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_PDF);
|
||||
headers.setContentDispositionFormData("attachment", outputFilename);
|
||||
return ResponseEntity.ok().headers(headers).body(pdfBytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package stirling.software.SPDF.controller;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
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.graphics.image.PDImageXObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Controller
|
||||
public class ExtractImagesController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
|
||||
|
||||
@GetMapping("/extract-images")
|
||||
public String extractImagesForm(Model model) {
|
||||
model.addAttribute("currentPage", "extract-images");
|
||||
return "extract-images";
|
||||
}
|
||||
|
||||
@PostMapping("/extract-images")
|
||||
public ResponseEntity<Resource> extractImages(@RequestParam("fileInput") MultipartFile file, @RequestParam("format") String format) throws IOException {
|
||||
|
||||
System.out.println(System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
|
||||
PDDocument document = PDDocument.load(file.getBytes());
|
||||
|
||||
// Create ByteArrayOutputStream to write zip file to byte array
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
// Create ZipOutputStream to create zip file
|
||||
ZipOutputStream zos = new ZipOutputStream(baos);
|
||||
|
||||
// Set compression level
|
||||
zos.setLevel(Deflater.BEST_COMPRESSION);
|
||||
|
||||
int imageIndex = 1;
|
||||
|
||||
int pageNum = 1;
|
||||
// Iterate over each page
|
||||
for (PDPage page : document.getPages()) {
|
||||
++pageNum;
|
||||
// Extract images from page
|
||||
for (COSName name : page.getResources().getXObjectNames()) {
|
||||
if (page.getResources().isImageXObject(name)) {
|
||||
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
|
||||
|
||||
// Convert image to desired format
|
||||
RenderedImage renderedImage = image.getImage();
|
||||
BufferedImage bufferedImage = null;
|
||||
if (format.equalsIgnoreCase("png")) {
|
||||
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(),
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
} else if (format.equalsIgnoreCase("jpeg") || format.equalsIgnoreCase("jpg")) {
|
||||
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(),
|
||||
BufferedImage.TYPE_INT_RGB);
|
||||
} else if (format.equalsIgnoreCase("gif")) {
|
||||
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(),
|
||||
BufferedImage.TYPE_BYTE_INDEXED);
|
||||
}
|
||||
|
||||
// Write image to zip file
|
||||
String imageName = "Image " + imageIndex + " (Page " + pageNum + ")." + format;
|
||||
ZipEntry zipEntry = new ZipEntry(imageName);
|
||||
zos.putNextEntry(zipEntry);
|
||||
|
||||
Graphics2D g = bufferedImage.createGraphics();
|
||||
g.drawImage((Image) renderedImage, 0, 0, null);
|
||||
g.dispose();
|
||||
// Write image bytes to zip file
|
||||
ByteArrayOutputStream imageBaos = new ByteArrayOutputStream();
|
||||
ImageIO.write(bufferedImage, format, imageBaos);
|
||||
zos.write(imageBaos.toByteArray());
|
||||
|
||||
|
||||
zos.closeEntry();
|
||||
imageIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close ZipOutputStream and PDDocument
|
||||
zos.close();
|
||||
document.close();
|
||||
|
||||
// Create ByteArrayResource from byte array
|
||||
byte[] zipContents = baos.toByteArray();
|
||||
ByteArrayResource resource = new ByteArrayResource(zipContents);
|
||||
|
||||
// Set content disposition header to indicate that the response should be downloaded as a file
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentLength(zipContents.length);
|
||||
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted-images.zip");
|
||||
|
||||
// Return ResponseEntity with ByteArrayResource and headers
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.OK)
|
||||
.headers(headers)
|
||||
|
||||
.header("Cache-Control", "no-cache")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(resource);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package stirling.software.SPDF.controller;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
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.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.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
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.utils.ProcessExecutor;
|
||||
//import com.spire.pdf.*;
|
||||
import java.util.concurrent.Semaphore;
|
||||
@Controller
|
||||
public class OCRController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
|
||||
|
||||
@GetMapping("/ocr-pdf")
|
||||
public ModelAndView ocrPdfPage() {
|
||||
ModelAndView modelAndView = new ModelAndView("ocr-pdf");
|
||||
modelAndView.addObject("languages", getAvailableTesseractLanguages());
|
||||
modelAndView.addObject("currentPage", "ocr-pdf");
|
||||
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,
|
||||
@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.");
|
||||
}
|
||||
|
||||
// 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 output file path
|
||||
Path sidecarTextPath = null;
|
||||
|
||||
// Run OCR Command
|
||||
String languageOption = String.join("+", selectedLanguages);
|
||||
|
||||
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(sidecarTextPath.toString());
|
||||
}
|
||||
|
||||
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
|
||||
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf";
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
if (sidecar != null && sidecar) {
|
||||
// Create a zip file containing both the PDF and the text file
|
||||
String outputZipFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip";
|
||||
Path tempZipFile = Files.createTempFile("output_", ".zip");
|
||||
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
|
||||
// Add PDF file to the zip
|
||||
ZipEntry pdfEntry = new ZipEntry(outputFilename);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
Files.copy(tempOutputFile, zipOut);
|
||||
zipOut.closeEntry();
|
||||
|
||||
// Add text file to the zip
|
||||
ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt"));
|
||||
zipOut.putNextEntry(txtEntry);
|
||||
Files.copy(sidecarTextPath, zipOut);
|
||||
zipOut.closeEntry();
|
||||
}
|
||||
|
||||
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
||||
|
||||
// Clean up the temporary zip file
|
||||
Files.delete(tempZipFile);
|
||||
Files.delete(tempOutputFile);
|
||||
Files.delete(sidecarTextPath);
|
||||
|
||||
// Return the zip file containing both the PDF and the text file
|
||||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
headers.setContentDispositionFormData("attachment", outputZipFilename);
|
||||
return ResponseEntity.ok().headers(headers).body(zipBytes);
|
||||
} else {
|
||||
// Return the OCR processed PDF as a response
|
||||
Files.delete(tempOutputFile);
|
||||
headers.setContentType(MediaType.APPLICATION_PDF);
|
||||
headers.setContentDispositionFormData("attachment", outputFilename);
|
||||
return ResponseEntity.ok().headers(headers).body(pdfBytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<String> getAvailableTesseractLanguages() {
|
||||
String tessdataDir = "/usr/share/tesseract-ocr/4.00/tessdata";
|
||||
File[] files = new File(tessdataDir).listFiles();
|
||||
if (files == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.stream(files)
|
||||
.filter(file -> file.getName().endsWith(".traineddata"))
|
||||
.map(file -> file.getName().replace(".traineddata", ""))
|
||||
.filter(lang -> !lang.equalsIgnoreCase("osd"))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
public class PdfController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PdfController.class);
|
||||
|
||||
|
||||
@GetMapping("/home")
|
||||
public String root(Model model) {
|
||||
return "redirect:/";
|
||||
@@ -22,4 +22,6 @@ public class PdfController {
|
||||
return "home";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -49,7 +49,6 @@ public class RearrangePagesPDFController {
|
||||
int pageIndex = pagesToRemove.get(i);
|
||||
document.removePage(pageIndex);
|
||||
}
|
||||
|
||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
|
||||
|
||||
}
|
||||
|
||||
@@ -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,34 +106,35 @@ 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=split_documents.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_split.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.contentLength(resource.contentLength()).body(resource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
@@ -61,7 +62,7 @@ public class ConvertImgPDFController {
|
||||
boolean singleImage = singleOrMultiple.equals("single");
|
||||
byte[] result = null;
|
||||
try {
|
||||
result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toLowerCase(), colorTypeResult, singleImage, Integer.valueOf(dpi));
|
||||
result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toUpperCase(), colorTypeResult, singleImage, Integer.valueOf(dpi));
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
@@ -72,13 +73,12 @@ 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 {
|
||||
ByteArrayResource resource = new ByteArrayResource(result);
|
||||
// return the Resource in the response
|
||||
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=converted_documents.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="+ file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToImages.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.contentLength(resource.contentLength()).body(resource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package stirling.software.SPDF.controller.converters;
|
||||
|
||||
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.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
@Controller
|
||||
public class ConvertOfficeController {
|
||||
|
||||
|
||||
@GetMapping("/file-to-pdf")
|
||||
public String convertToPdfForm(Model model) {
|
||||
model.addAttribute("currentPage", "file-to-pdf");
|
||||
return "convert/file-to-pdf";
|
||||
}
|
||||
|
||||
@PostMapping("/file-to-pdf")
|
||||
public ResponseEntity<byte[]> processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException {
|
||||
|
||||
//unused but can start server instance if startup time is to long
|
||||
//LibreOfficeListener.getInstance().start();
|
||||
|
||||
byte[] pdfByteArray = convertToPdf(inputFile);
|
||||
return PdfUtils.bytesToWebResponse(pdfByteArray, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToPDF.pdf");
|
||||
}
|
||||
|
||||
|
||||
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile = Files.createTempFile("input_", "." + getFileExtension(inputFile.getOriginalFilename()));
|
||||
inputFile.transferTo(tempInputFile.toFile());
|
||||
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
// Run the LibreOffice command
|
||||
List<String> command = new ArrayList<>(Arrays.asList("unoconv", "-vvv",
|
||||
"-f",
|
||||
"pdf",
|
||||
"-o",
|
||||
tempOutputFile.toString(),
|
||||
tempInputFile.toString()));
|
||||
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command);
|
||||
|
||||
// Read the converted PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempInputFile);
|
||||
Files.delete(tempOutputFile);
|
||||
|
||||
return pdfBytes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String getFileExtension(String fileName) {
|
||||
int dotIndex = fileName.lastIndexOf('.');
|
||||
if (dotIndex == -1) {
|
||||
return "";
|
||||
}
|
||||
return fileName.substring(dotIndex + 1);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package stirling.software.SPDF.controller.converters;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.ss.usermodel.Color;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
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.text.BaseColor;
|
||||
import com.itextpdf.text.Document;
|
||||
import com.itextpdf.text.DocumentException;
|
||||
import com.itextpdf.text.Element;
|
||||
import com.itextpdf.text.pdf.PdfPCell;
|
||||
import com.itextpdf.text.pdf.PdfPTable;
|
||||
import com.itextpdf.text.pdf.PdfWriter;
|
||||
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
|
||||
@Controller
|
||||
public class ConvertXlsxController {
|
||||
|
||||
|
||||
@GetMapping("/xlsx-to-pdf")
|
||||
public String cinvertToPDF(Model model) {
|
||||
model.addAttribute("currentPage", "xlsx-to-pdf");
|
||||
return "convert/xlsx-to-pdf";
|
||||
}
|
||||
|
||||
@PostMapping("/xlsx-to-pdf")
|
||||
public ResponseEntity<byte[]> convertToPDF(@RequestParam("fileInput") MultipartFile xlsx) throws IOException, DocumentException{
|
||||
// Load Excel file
|
||||
|
||||
Workbook workbook = WorkbookFactory.create(xlsx.getInputStream());
|
||||
|
||||
// Create PDF document
|
||||
Document document = new Document();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
PdfWriter.getInstance(document, outputStream);
|
||||
document.open();
|
||||
|
||||
// Convert each sheet in Excel to a separate page in PDF
|
||||
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
|
||||
PdfPTable table = new PdfPTable(workbook.getSheetAt(i).getRow(0).getPhysicalNumberOfCells());
|
||||
for (int row = 0; row < workbook.getSheetAt(i).getPhysicalNumberOfRows(); row++) {
|
||||
for (int cell = 0; cell < workbook.getSheetAt(i).getRow(row).getPhysicalNumberOfCells(); cell++) {
|
||||
PdfPCell pdfCell = new PdfPCell();
|
||||
pdfCell.addElement(new com.itextpdf.text.Paragraph(workbook.getSheetAt(i).getRow(row).getCell(cell).toString()));
|
||||
|
||||
// Copy cell style, borders, and background color
|
||||
pdfCell.setBorderColor(new BaseColor(workbook.getSheetAt(i).getRow(row).getCell(cell).getCellStyle().getBottomBorderColor()));
|
||||
pdfCell.setBorderColor(new BaseColor(workbook.getSheetAt(i).getRow(row).getCell(cell).getCellStyle().getTopBorderColor()));
|
||||
pdfCell.setBorderColor(new BaseColor(workbook.getSheetAt(i).getRow(row).getCell(cell).getCellStyle().getLeftBorderColor()));
|
||||
pdfCell.setBorderColor(new BaseColor(workbook.getSheetAt(i).getRow(row).getCell(cell).getCellStyle().getRightBorderColor()));
|
||||
Short bc = workbook.getSheetAt(i).getRow(row).getCell(cell).getCellStyle().getFillBackgroundColor();
|
||||
pdfCell.setBackgroundColor(new BaseColor(bc));
|
||||
|
||||
table.addCell(pdfCell);
|
||||
}
|
||||
}
|
||||
document.add(table);
|
||||
}
|
||||
// Close document and output stream
|
||||
document.close();
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
return PdfUtils.boasToWebResponse(outputStream, xlsx.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToPDF.pdf");
|
||||
// Close document and input stream
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,19 @@ package stirling.software.SPDF.controller.security;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||
import org.apache.pdfbox.util.Matrix;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -18,6 +25,8 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
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 {
|
||||
@@ -28,10 +37,17 @@ public class WatermarkController {
|
||||
return "security/add-watermark";
|
||||
}
|
||||
|
||||
@GetMapping("/remove-watermark")
|
||||
public String removeWatermarkForm(Model model) {
|
||||
model.addAttribute("currentPage", "remove-watermark");
|
||||
return "security/remove-watermark";
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -39,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();
|
||||
@@ -71,4 +96,62 @@ public class WatermarkController {
|
||||
}
|
||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@PostMapping("/remove-watermark")
|
||||
public ResponseEntity<byte[]> removeWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText) throws Exception {
|
||||
|
||||
// Load the input PDF
|
||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||
|
||||
// Create a new PDF document for the output
|
||||
PDDocument outputDocument = new PDDocument();
|
||||
|
||||
// Loop through the pages
|
||||
int numPages = document.getNumberOfPages();
|
||||
for (int i = 0; i < numPages; i++) {
|
||||
PDPage page = document.getPage(i);
|
||||
|
||||
// Process the content stream to remove the watermark text
|
||||
WatermarkRemover editor = new WatermarkRemover(watermarkText) {};
|
||||
editor.processPage(page);
|
||||
editor.processPage(page);
|
||||
// Add the page to the output document
|
||||
outputDocument.addPage(page);
|
||||
}
|
||||
|
||||
for (PDPage page : outputDocument.getPages()) {
|
||||
List<PDAnnotation> annotations = page.getAnnotations();
|
||||
List<PDAnnotation> annotationsToRemove = new ArrayList<>();
|
||||
|
||||
for (PDAnnotation annotation : annotations) {
|
||||
if (annotation instanceof PDAnnotationMarkup) {
|
||||
PDAnnotationMarkup markup = (PDAnnotationMarkup) annotation;
|
||||
String contents = markup.getContents();
|
||||
if (contents != null && contents.contains(watermarkText)) {
|
||||
annotationsToRemove.add(markup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
annotations.removeAll(annotationsToRemove);
|
||||
}
|
||||
PDDocumentCatalog catalog = outputDocument.getDocumentCatalog();
|
||||
PDAcroForm acroForm = catalog.getAcroForm();
|
||||
if (acroForm != null) {
|
||||
List<PDField> fields = acroForm.getFields();
|
||||
for (PDField field : fields) {
|
||||
String fieldValue = field.getValueAsString();
|
||||
if (fieldValue.contains(watermarkText)) {
|
||||
field.setValue(fieldValue.replace(watermarkText, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PdfUtils.pdfDocToWebResponse(outputDocument, "removed.pdf");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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,59 +29,110 @@ 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;
|
||||
import com.itextpdf.text.pdf.PdfWriter;
|
||||
import com.spire.pdf.PdfDocument;
|
||||
|
||||
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))) {
|
||||
@@ -107,7 +159,7 @@ public class PdfUtils {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (singleImage) {
|
||||
// Write the image to the output stream
|
||||
ImageIO.write(images.get(0), "PNG", baos);
|
||||
ImageIO.write(images.get(0), imageType, baos);
|
||||
|
||||
// Log that the image was successfully written to the byte array
|
||||
logger.info("Image successfully written to byte array");
|
||||
@@ -117,10 +169,10 @@ public class PdfUtils {
|
||||
for (int i = 0; i < images.size(); i++) {
|
||||
BufferedImage image = images.get(i);
|
||||
try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) {
|
||||
ImageIO.write(image, "PNG", baosImage);
|
||||
ImageIO.write(image, imageType, baosImage);
|
||||
|
||||
// Add the image to the zip file
|
||||
zos.putNextEntry(new ZipEntry(String.format("page_%d.%s", i + 1, "png")));
|
||||
zos.putNextEntry(new ZipEntry(String.format("page_%d.%s", i + 1, imageType.toLowerCase())));
|
||||
zos.write(baosImage.toByteArray());
|
||||
}
|
||||
}
|
||||
@@ -172,16 +224,7 @@ public class PdfUtils {
|
||||
return PdfUtils.boasToWebResponse(baos, docName);
|
||||
}
|
||||
|
||||
public static ResponseEntity<byte[]> pdfDocToWebResponse(PdfDocument document, String docName) throws IOException {
|
||||
|
||||
// Open Byte Array and save document to it
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.saveToStream(baos);
|
||||
// Close the document
|
||||
document.close();
|
||||
|
||||
return PdfUtils.boasToWebResponse(baos, docName);
|
||||
}
|
||||
|
||||
|
||||
public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName) throws IOException {
|
||||
|
||||
|
||||
102
src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java
Normal file
102
src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java
Normal file
@@ -0,0 +1,102 @@
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Semaphore;
|
||||
public class ProcessExecutor {
|
||||
|
||||
public enum Processes {
|
||||
LIBRE_OFFICE,
|
||||
OCR_MY_PDF
|
||||
}
|
||||
|
||||
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
private final Semaphore semaphore;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.pdfbox.contentstream.PDFStreamEngine;
|
||||
import org.apache.pdfbox.contentstream.operator.Operator;
|
||||
import org.apache.pdfbox.cos.COSArray;
|
||||
import org.apache.pdfbox.cos.COSBase;
|
||||
import org.apache.pdfbox.cos.COSString;
|
||||
|
||||
public class WatermarkRemover extends PDFStreamEngine {
|
||||
|
||||
private final String watermarkText;
|
||||
private final Pattern pattern;
|
||||
|
||||
public WatermarkRemover(String watermarkText) {
|
||||
this.watermarkText = watermarkText;
|
||||
this.pattern = Pattern.compile(Pattern.quote(watermarkText));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processOperator(Operator operator, List<COSBase> operands) throws IOException {
|
||||
String operation = operator.getName();
|
||||
|
||||
boolean processText = false;
|
||||
if ("Tj".equals(operation) || "TJ".equals(operation) || "'".equals(operation) || "\"".equals(operation)) {
|
||||
processText = true;
|
||||
}
|
||||
|
||||
if (processText) {
|
||||
for(int j = 0 ; j < operands.size(); ++j) {
|
||||
COSBase operand = operands.get(j);
|
||||
if (operand instanceof COSString) {
|
||||
COSString cosString = (COSString) operand;
|
||||
String string = cosString.getString();
|
||||
Matcher matcher = pattern.matcher(string);
|
||||
if (matcher.find()) {
|
||||
string = matcher.replaceAll("");
|
||||
cosString.setValue(string.getBytes());
|
||||
}
|
||||
} else if (operand instanceof COSArray) {
|
||||
COSArray array = (COSArray) operand;
|
||||
for (int i = 0; i < array.size(); i++) {
|
||||
COSBase item = array.get(i);
|
||||
if (item instanceof COSString) {
|
||||
COSString cosString = (COSString) item;
|
||||
String string = cosString.getString();
|
||||
Matcher matcher = pattern.matcher(string);
|
||||
if (matcher.find()) {
|
||||
System.out.println("operation =" + operation);
|
||||
System.out.println("1 =" + string);
|
||||
string = matcher.replaceAll("");
|
||||
cosString.setValue(string.getBytes());
|
||||
array.set(i, cosString);
|
||||
operands.set(j, array);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
super.processOperator(operator, operands);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -14,4 +14,11 @@ server.error.path=/error
|
||||
server.error.whitelabel.enabled=false
|
||||
server.error.include-stacktrace=always
|
||||
server.error.include-exception=true
|
||||
server.error.include-message=always
|
||||
server.error.include-message=always
|
||||
|
||||
server.servlet.session.tracking-modes=cookie
|
||||
|
||||
spring.devtools.restart.enabled=true
|
||||
spring.devtools.livereload.enabled=true
|
||||
|
||||
spring.thymeleaf.encoding=UTF-8
|
||||
@@ -0,0 +1,11 @@
|
||||
fileToPDF.fileTypesList=Microsoft Word: (DOC, DOCX, DOT, DOTX) <br> \
|
||||
Microsoft Excel: (CSV, XLS, XLSX, XLT, XLTX, SLK, DIF) <br> \
|
||||
Microsoft PowerPoint: (PPT, PPTX) <br> \
|
||||
OpenDocument Formats: (ODT, OTT, ODS, OTS, ODP, OTP, ODG, OTG) <br> \
|
||||
Plain Text: (TXT, TEXT, XML) <br> \
|
||||
Rich Text Format: (RTF) <br> \
|
||||
Images: (BMP, GIF, JPEG, PNG, TIF, PBM, PGM, PPM, RAS, XBM, XPM, SVG, SVM, WMF) <br> \
|
||||
HTML: (HTML) <br> \
|
||||
Lotus Word Pro: (LWP) <br> \
|
||||
StarOffice formats: (SDA, SDC, SDD, SDW, STC, STD, STI, STW, SXD, SXG, SXI, SXW) <br> \
|
||||
Other formats: (DBF, FODS, VSD, VOR, VOR3, VOR4, UOP, PCT, PS, PDF)
|
||||
|
||||
@@ -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
|
||||
@@ -19,6 +19,8 @@ goToPage=اذهب
|
||||
true=\u0635\u062D\u064A\u062D
|
||||
false=\u062E\u0637\u0623
|
||||
unknown=\u063A\u064A\u0631 \u0645\u0639\u0631\u0648\u0641
|
||||
save=\u062D\u0641\u0638
|
||||
close=\u0625\u063A\u0644\u0627\u0642
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
@@ -54,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 الخاص بك
|
||||
@@ -72,11 +74,63 @@ 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 \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
|
||||
|
||||
home.pdfToPDFA.title = \u062A\u062D\u0648\u064A\u0644 \u0645\u0644\u0641\u0627\u062A PDF \u0625\u0644\u0649 PDF / A
|
||||
home.pdfToPDFA.desc = \u062A\u062D\u0648\u064A\u0644 PDF \u0625\u0644\u0649 PDF / A \u0644\u0644\u062A\u062E\u0632\u064A\u0646 \u0637\u0648\u064A\u0644 \u0627\u0644\u0645\u062F\u0649
|
||||
|
||||
|
||||
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.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.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=\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
|
||||
|
||||
|
||||
extractImages.title=\u0627\u0633\u062A\u062E\u0631\u0627\u062C \u0627\u0644\u0635\u0648\u0631
|
||||
extractImages.header=\u0627\u0633\u062A\u062E\u0631\u0627\u062C \u0627\u0644\u0635\u0648\u0631
|
||||
extractImages.selectText=\u062D\u062F\u062F \u062A\u0646\u0633\u064A\u0642 \u0627\u0644\u0635\u0648\u0631\u0629 \u0644\u062A\u062D\u0648\u064A\u0644 \u0627\u0644\u0635\u0648\u0631 \u0627\u0644\u0645\u0633\u062A\u062E\u0631\u062C\u0629 \u0625\u0644\u0649
|
||||
extractImages.submit=\u0627\u0633\u062A\u062E\u0631\u0627\u062C
|
||||
|
||||
#File \u0625\u0644\u0649 PDF
|
||||
fileToPDF.title=\u0645\u0644\u0641 \u0625\u0644\u0649 PDF
|
||||
fileToPDF.header=\u062A\u062D\u0648\u064A\u0644 \u0623\u064A \u0645\u0644\u0641 \u0625\u0644\u0649 PDF
|
||||
fileToPDF.credit=\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\u0644\u062E\u062F\u0645\u0629 \u0644\u064A\u0628\u0631 \u0623\u0648\u0641\u064A\u0633 \u0648\u0623\u0648\u0646\u0648\u0643\u0648\u0646\u0641 \u0644\u062A\u062D\u0648\u064A\u0644 \u0627\u0644\u0645\u0644\u0641\u0627\u062A.
|
||||
fileToPDF.supportedFileTypes=\u064A\u062C\u0628 \u0623\u0646 \u062A\u062A\u0636\u0645\u0646 \u0623\u0646\u0648\u0627\u0639 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0627\u0644\u0645\u062F\u0639\u0648\u0645\u0629 \u0645\u0627 \u064A\u0644\u064A \u0648\u0644\u0643\u0646 \u0644\u0644\u062D\u0635\u0648\u0644 \u0639\u0644\u0649 \u0642\u0627\u0626\u0645\u0629 \u0645\u062D\u062F\u062B\u0629 \u0643\u0627\u0645\u0644\u0629 \u0628\u0627\u0644\u062A\u0646\u0633\u064A\u0642\u0627\u062A \u0627\u0644\u0645\u062F\u0639\u0648\u0645\u0629 \u060C \u064A\u0631\u062C\u0649 \u0627\u0644\u0631\u062C\u0648\u0639 \u0625\u0644\u0649 \u0648\u062B\u0627\u0626\u0642 LibreOffice
|
||||
fileToPDF.submit=\u062A\u062D\u0648\u064A\u0644 \u0625\u0644\u0649 PDF
|
||||
|
||||
home.xlsToPdf.title = \u062A\u062D\u0648\u064A\u0644 Excel (Xls) \u0625\u0644\u0649 PDF
|
||||
home.xlsToPdf.desc = \u0642\u0645 \u0628\u062A\u062D\u0648\u064A\u0644 \u0645\u0633\u062A\u0646\u062F Excel (xls \u060C xlsx) \u0625\u0644\u0649 PDF.
|
||||
|
||||
#Add image
|
||||
addImage.title=إضافة صورة
|
||||
@@ -85,8 +139,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=ضغط
|
||||
|
||||
|
||||
@@ -135,18 +196,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
|
||||
@@ -176,14 +242,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=تغيير الأذونات
|
||||
@@ -208,27 +275,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
|
||||
|
||||
@@ -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
|
||||
@@ -15,6 +15,8 @@ goToPage=Los
|
||||
true=Wahr
|
||||
false=Falsch
|
||||
unknown=Unbekannt
|
||||
save=Speichern
|
||||
close=Schließen
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
@@ -68,11 +70,66 @@ home.removePassword.desc=Den Passwortschutz eines PDFs entfernen.
|
||||
home.compressPdfs.title=PDF komprimieren
|
||||
home.compressPdfs.desc=PDF komprimieren um die Dateigröße zu reduzieren.
|
||||
|
||||
home.changeMetadata.title=Metadaten ändern
|
||||
home.changeMetadata.desc=Ändern/Entfernen/Hinzufügen von Metadaten aus einem PDF-Dokument
|
||||
home.changeMetadata.title=Metadaten ändern
|
||||
home.changeMetadata.desc=Ändern/Entfernen/Hinzufügen von Metadaten aus einem PDF-Dokument
|
||||
|
||||
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=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
|
||||
|
||||
home.pdfToPDFA.title=PDF zu PDF/A konvertieren
|
||||
home.pdfToPDFA.desc=PDF zu PDF/A für Langzeitarchivierung konvertieren
|
||||
|
||||
|
||||
navbar.settings=Einstellungen
|
||||
settings.title=Einstellungen
|
||||
settings.update=Update verfügbar
|
||||
settings.appVersion=App-Version:
|
||||
settings.downloadOption.title=Download-Option wählen (für einzelne Dateien, die keine Zip-Downloads sind):
|
||||
settings.downloadOption.1=Im selben Fenster öffnen
|
||||
settings.downloadOption.2=In neuem Fenster öffnen
|
||||
settings.downloadOption.3=Datei herunterladen
|
||||
settings.zipThreshold=Dateien komprimieren, wenn die Anzahl der heruntergeladenen Dateien überschritten wird
|
||||
|
||||
#OCR
|
||||
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
|
||||
|
||||
|
||||
extractImages.title=Bilder extrahieren
|
||||
extractImages.header=Bilder extrahieren
|
||||
extractImages.selectText=Wählen Sie das Bildformat aus, in das extrahierte Bilder konvertiert werden sollen
|
||||
extractImages.submit=Extrahieren
|
||||
|
||||
|
||||
#File to PDF
|
||||
fileToPDF.title=Datei in PDF
|
||||
fileToPDF.header=Beliebige Dateien in PDF konvertieren
|
||||
fileToPDF.credit=Dieser Dienst verwendet LibreOffice und Unoconv für die Dateikonvertierung.
|
||||
fileToPDF.supportedFileTypes=Unterstützte Dateitypen sollten die folgenden enthalten, eine vollständige aktualisierte Liste der unterstützten Formate finden Sie jedoch in der LibreOffice-Dokumentation
|
||||
fileToPDF.submit=In PDF konvertieren
|
||||
|
||||
|
||||
|
||||
|
||||
home.xlsToPdf.title=Excel (Xls) in PDF
|
||||
home.xlsToPdf.desc=Konvertiere ein Excel-Dokument (xls, xlsx) in PDF.
|
||||
|
||||
#Add image
|
||||
addImage.title=Bild hinzufügen
|
||||
@@ -82,7 +139,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
|
||||
|
||||
|
||||
@@ -131,18 +195,23 @@ 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
|
||||
pdfToImage.header=PDF zu Bild
|
||||
pdfToImage.selectText=Bildformat
|
||||
pdfToImage.singleOrMultiple=Bildergebnistyp
|
||||
pdfToImage.single=Einzelnes großes Bild
|
||||
pdfToImage.single=Einzelnes großes Bild
|
||||
pdfToImage.multi=Mehrere Bilder
|
||||
pdfToImage.colorType=Farbtyp
|
||||
pdfToImage.color=Farbe
|
||||
pdfToImage.grey=Graustufen
|
||||
pdfToImage.blackwhite=Schwarzweiß (Datenverlust möglich!)
|
||||
pdfToImage.blackwhite=Schwarzweiß (Datenverlust möglich!)
|
||||
pdfToImage.submit=Umwandeln
|
||||
|
||||
#addPassword
|
||||
@@ -172,12 +241,13 @@ 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
|
||||
remove-watermark.title=Wasserzeichen entfernen
|
||||
remove-watermark.header=Wasserzeichen entfernen
|
||||
remove-watermark.selectText.1=PDF auswählen, um Wasserzeichen zu entfernen von:
|
||||
remove-watermark.selectText.1=PDF auswählen, um Wasserzeichen zu entfernen von:
|
||||
remove-watermark.selectText.2=Wasserzeichentext:
|
||||
remove-watermark.submit=Wasserzeichen entfernen
|
||||
|
||||
@@ -205,33 +275,36 @@ removePassword.selectText.2=Passwort
|
||||
removePassword.submit=Entfernen
|
||||
|
||||
|
||||
changeMetadata.title=Metadaten ändern
|
||||
changeMetadata.header=Metadaten ändern
|
||||
changeMetadata.selectText.1=Bitte bearbeiten Sie die Variablen, die Sie ändern möchten
|
||||
changeMetadata.selectText.2=Alle Metadaten löschen
|
||||
changeMetadata.title=Metadaten ändern
|
||||
changeMetadata.header=Metadaten ändern
|
||||
changeMetadata.selectText.1=Bitte bearbeiten Sie die Variablen, die Sie ändern möchten
|
||||
changeMetadata.selectText.2=Alle Metadaten löschen
|
||||
changeMetadata.selectText.3=Benutzerdefinierte Metadaten anzeigen:
|
||||
changeMetadata.author=Autor:
|
||||
changeMetadata.creationDate=Erstellungsdatum (jjjj/MM/tt HH:mm:ss):
|
||||
changeMetadata.creator=Ersteller:
|
||||
changeMetadata.keywords=Schlüsselwörter:
|
||||
changeMetadata.modDate=Änderungsdatum (JJJJ/MM/TT HH:mm:ss):
|
||||
changeMetadata.keywords=Schlüsselwörter:
|
||||
changeMetadata.modDate=Änderungsdatum (JJJJ/MM/TT HH:mm:ss):
|
||||
changeMetadata.producer=Produzent:
|
||||
changeMetadata.subject=Betreff:
|
||||
changeMetadata.title=Titel:
|
||||
changeMetadata.trapped=Gefangen:
|
||||
changeMetadata.selectText.4=Andere Metadaten:
|
||||
changeMetadata.selectText.5=Benutzerdefinierten Metadateneintrag hinzufügen
|
||||
changeMetadata.submit=Ändern
|
||||
changeMetadata.selectText.5=Benutzerdefinierten Metadateneintrag hinzufügen
|
||||
changeMetadata.submit=Ändern
|
||||
|
||||
|
||||
|
||||
xlsToPdf.title=Excel in PDF
|
||||
xlsToPdf.header=Excel in PDF
|
||||
xlsToPdf.selectText.1=XLS- oder XLSX-Excel-Tabelle zum Konvertieren auswählen
|
||||
xlsToPdf.selectText.1=XLS- oder XLSX-Excel-Tabelle zum Konvertieren auswählen
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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) :
|
||||
@@ -15,6 +15,9 @@ goToPage=Go
|
||||
true=True
|
||||
false=False
|
||||
unknown=Unknown
|
||||
save=Save
|
||||
close=Close
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -70,20 +73,84 @@ 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.xlsToPdf.title=Excel (Xls) to PDF
|
||||
home.xlsToPdf.desc=Convert a Excel document (xls, xlsx) to PDF.
|
||||
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 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
|
||||
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.zipThreshold=Zip files when the number of downloaded files exceeds
|
||||
|
||||
|
||||
|
||||
|
||||
#OCR
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
#compress
|
||||
compress.title=Compress
|
||||
compress.header=Compress PDF
|
||||
compress.credit=This service uses OCRmyPDF for PDF Compress/Optimisation.
|
||||
compress.selectText.1=Optimization level:
|
||||
compress.selectText.2=0 (No optimization)
|
||||
compress.selectText.3=1 (Default, lossless optimization)
|
||||
compress.selectText.4=2 (Lossy optimization)
|
||||
compress.selectText.5=3 (Lossy optimization, more aggressive)
|
||||
compress.selectText.6=Enable fast web view (linearize PDF)
|
||||
compress.selectText.7=Enable lossy JBIG2 encoding
|
||||
compress.submit=Compress
|
||||
|
||||
|
||||
#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
|
||||
@@ -130,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
|
||||
@@ -171,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
|
||||
@@ -229,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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,254 +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
|
||||
#############
|
||||
# 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.xlsToPdf.title=Excel (Xls) to PDF
|
||||
home.xlsToPdf.desc=Convert a Excel document (xls, xlsx) 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
|
||||
|
||||
|
||||
|
||||
xlsToPdf.title=Excel to PDF
|
||||
xlsToPdf.header=Excel to PDF
|
||||
xlsToPdf.selectText.1=Select XLS or XLSX Excel sheet to convert
|
||||
xlsToPdf.convert=convert
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -13,12 +13,14 @@ multiPdfPrompt=Choisir des PDF (2+)
|
||||
multiPdfDropPrompt=Sélectionnez (ou glissez-déposez) tous les PDF dont vous avez besoin
|
||||
imgPrompt=Choisir une image
|
||||
genericSubmit=Soumettre
|
||||
processTimeWarning=AttentionÂ: ce processus peut prendre jusqu'Ã une minute en fonction de la taille du fichier
|
||||
pageOrderPrompt=Ordre des pages (Entrez une liste de numéros de page séparés par des virgules)Â:
|
||||
processTimeWarning=Attention<EFBFBD>: ce processus peut prendre jusqu'à une minute en fonction de la taille du fichier
|
||||
pageOrderPrompt=Ordre des pages (Entrez une liste de numéros de page séparés par des virgules):
|
||||
goToPage=Aller
|
||||
true=Vrai
|
||||
false=Faux
|
||||
unknown=Inconnu
|
||||
save=Enregistrer
|
||||
close=Fermer
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
@@ -72,11 +74,64 @@ home.removePassword.desc=Supprimez la protection par mot de passe de votre docum
|
||||
home.compressPdfs.title=Compresser les PDF
|
||||
home.compressPdfs.desc=Compressez les PDF pour réduire leur taille de fichier.
|
||||
|
||||
home.changeMetadata.title=Modifier les métadonnées
|
||||
home.changeMetadata.desc=Modifier/Supprimer/Ajouter des métadonnées d'un document PDF
|
||||
home.changeMetadata.title=Modifier les métadonnées
|
||||
home.changeMetadata.desc=Modifier/Supprimer/Ajouter des métadonnées d'un document PDF
|
||||
|
||||
|
||||
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 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
|
||||
|
||||
|
||||
home.pdfToPDFA.title=Convertir PDF en PDF/A
|
||||
home.pdfToPDFA.desc=Convertir un PDF en PDF/A pour un stockage à long terme
|
||||
|
||||
navbar.settings=Paramètres
|
||||
settings.title=Paramètres
|
||||
settings.update=Mise à jour disponible
|
||||
settings.appVersion=Version de l'application :
|
||||
settings.downloadOption.title=Choisissez l'option de téléchargement (pour les téléchargements sans fichier unique) :
|
||||
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.zipThreshold=Zip les fichiers lorsque le nombre de fichiers téléchargés dépasse
|
||||
|
||||
|
||||
#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'utiliser pour d'autres langues et/ou une utilisation non dans docker
|
||||
ocr.credit=Ce service utilise OCRmyPDF et Tesseract pour l'OCR.
|
||||
ocr.submit=Traiter PDF avec OCR
|
||||
|
||||
|
||||
extractImages.title=Extraire les images
|
||||
extractImages.header=Extraire les images
|
||||
extractImages.selectText=Sélectionner le format d'image pour convertir les images extraites en
|
||||
extractImages.submit=Extraire
|
||||
|
||||
#File to PDF
|
||||
fileToPDF.title=Fichier au format PDF
|
||||
fileToPDF.header=Convertir n'importe quel fichier au format PDF
|
||||
fileToPDF.credit=Ce service utilise LibreOffice et Unoconv pour la conversion de fichiers.
|
||||
fileToPDF.supportedFileTypes=Les types de fichiers pris en charge doivent inclure les éléments ci-dessous, mais pour une liste complète et mise à jour des formats pris en charge, veuillez vous référer à la documentation de LibreOffice.
|
||||
fileToPDF.submit=Convertir en PDF
|
||||
|
||||
home.xlsToPdf.title=Excel (Xls) en PDF
|
||||
home.xlsToPdf.desc=Convertir un document Excel (xls, xlsx) en PDF.
|
||||
|
||||
|
||||
#Add image
|
||||
@@ -87,7 +142,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'optimisation :
|
||||
compress.selectText.2=0 (pas d'optimisation)
|
||||
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'affichage Web rapide (linéariser PDF)
|
||||
compress.selectText.7=Activer l'encodage JBIG2 avec perte
|
||||
compress.submit=Compresser
|
||||
|
||||
|
||||
@@ -105,49 +167,50 @@ pdfOrganiser.submit=Réorganiser les pages
|
||||
#pageRemover
|
||||
pageRemover.title=Suppresseur de pages
|
||||
pageRemover.header=Outil de suppression de pages PDF
|
||||
pageRemover.pagesToDelete=Pages à supprimer (Entrez une liste de numéros de page séparés par des virgules)Â:
|
||||
pageRemover.pagesToDelete=Pages à supprimer (Entrez une liste de numéros de page séparés par des virgules):
|
||||
pageRemover.submit=Supprimer des pages
|
||||
|
||||
#rotate
|
||||
rotate.title=Faire pivoter le PDF
|
||||
rotate.header=Faire pivoter le PDF
|
||||
rotate.selectAngle=S\u00e9lectionner l'angle de rotation (en multiples de 90 degr\u00e9s):
|
||||
rotate.selectAngle=Sélectionner l'angle de rotation (en multiples de 90 degrés) :
|
||||
rotate.submit=Rotation
|
||||
|
||||
|
||||
|
||||
|
||||
#merge
|
||||
#Split PDF
|
||||
split.title=Fractionner le PDF
|
||||
split.header=Diviser le PDF
|
||||
split.desc.1=Les numéros que vous sélectionnez sont le numéro de page sur lequel vous souhaitez faire un fractionnement
|
||||
split.desc.2=Ainsi, la sélection de 1,3,7-8 diviserait un document de 10 pages en 6 PDF distincts avecÂ:
|
||||
split.desc.3=Document #1Â: Page 1
|
||||
split.desc.4=Document #2Â: Pages 2 et 3
|
||||
split.desc.5=Document #3Â: Pages 4, 5 et 6
|
||||
split.desc.6=Document #4Â: Page 7
|
||||
split.desc.7=Document #5Â: Page 8
|
||||
split.desc.8=Document #6Â: Pages 9 et 10
|
||||
split.splitPages=Entrez les pages sur lesquelles fractionnerÂ:
|
||||
split.desc.1=Les numéros que vous sélectionnez sont le numéro de page sur lequel vous souhaitez faire un fractionnement.
|
||||
split.desc.2=Ainsi, la sélection de 1,3,7-8 diviserait un document de 10 pages en 6 PDF distincts avec :
|
||||
split.desc.3=Document #1 : Page 1
|
||||
split.desc.4=Document #2 : Pages 2 et 3
|
||||
split.desc.5=Document #3 : Pages 4, 5 et 6
|
||||
split.desc.6=Document #4 : Page 7
|
||||
split.desc.7=Document #5 : Page 8
|
||||
split.desc.8=Document #6 : Pages 9 et 10
|
||||
split.splitPages=Entrez les pages sur lesquelles fractionner :
|
||||
split.submit=Diviser
|
||||
|
||||
|
||||
#merge
|
||||
#imageToPDF
|
||||
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
|
||||
#PDF to Image
|
||||
pdfToImage.title=PDF vers image
|
||||
pdfToImage.header=PDF vers image
|
||||
pdfToImage.selectText=Format d'image
|
||||
pdfToImage.singleOrMultiple=Type de résultat d'image
|
||||
pdfToImage.singleOrMultiple=Type de résultat d'image
|
||||
pdfToImage.single=Une seule grande image
|
||||
pdfToImage.multi=Plusieurs images
|
||||
pdfToImage.colorType=Type de couleur
|
||||
pdfToImage.color=Couleur
|
||||
pdfToImage.grey=Niveaux de gris
|
||||
pdfToImage.blackwhite=Noir et Blanc (Peut perdre des données !)
|
||||
pdfToImage.blackwhite=Noir et Blanc (Peut perdre des données !)
|
||||
pdfToImage.submit=Convertir
|
||||
|
||||
#addPassword
|
||||
@@ -171,35 +234,36 @@ addPassword.submit=Crypter
|
||||
#watermark
|
||||
watermark.title=Ajouter un filigrane
|
||||
watermark.header=Ajouter un filigrane
|
||||
watermark.selectText.1=Sélectionnez le PDF auquel ajouter un filigraneÂ:
|
||||
watermark.selectText.2=Texte du filigraneÂ:
|
||||
watermark.selectText.3=Taille de la policeÂ:
|
||||
watermark.selectText.4=Rotation (0-360)Â:
|
||||
watermark.selectText.5=widthSpacer (Espace entre chaque filigrane horizontalement)Â:
|
||||
watermark.selectText.6=heightSpacer (Espace entre chaque filigrane verticalement)Â:
|
||||
watermark.selectText.1=Sélectionnez le PDF auquel ajouter un filigrane :
|
||||
watermark.selectText.2=Texte du filigrane :
|
||||
watermark.selectText.3=Taille de la police :
|
||||
watermark.selectText.4=Rotation (0-360) :
|
||||
watermark.selectText.5=widthSpacer (Espace entre chaque filigrane horizontalement) :
|
||||
watermark.selectText.6=heightSpacer (Espace entre chaque filigrane verticalement) :
|
||||
watermark.selectText.7=Opacité (0 % - 100 %) :
|
||||
watermark.submit=Ajouter un filigrane
|
||||
|
||||
#remove-watermark
|
||||
remove-watermark.title=Supprimer le filigrane
|
||||
remove-watermark.header=Supprimer le filigrane
|
||||
remove-watermark.selectText.1=Sélectionnez le PDF pour supprimer le filigrane:
|
||||
remove-watermark.selectText.2=Texte du filigrane:
|
||||
remove-watermark.selectText.1=Sélectionnez le PDF pour supprimer le filigrane :
|
||||
remove-watermark.selectText.2=Texte du filigrane :
|
||||
remove-watermark.submit=Supprimer le filigrane
|
||||
|
||||
#Change permissions
|
||||
#Change Permissions
|
||||
permissions.title=Modifier les autorisations
|
||||
permissions.header=Modifier les autorisations
|
||||
permissions.warning=Attention pour que ces permissions soient immuables il est recommandé de les définir avec un mot de passe via la page add-password
|
||||
permissions.selectText.1=Sélectionnez PDF pour modifier les autorisations
|
||||
permissions.selectText.2=Autorisations à définir
|
||||
permissions.warning=Attention pour que ces permissions soient immuables il est recommandé de les définir avec un mot de passe via la page add-password.
|
||||
permissions.selectText.1=Sélectionnez le PDF pour modifier les autorisations :
|
||||
permissions.selectText.2=Autorisations à définir :
|
||||
permissions.selectText.3=Employer l'assemblage du document
|
||||
permissions.selectText.4=Employer l'extraction de contenu
|
||||
permissions.selectText.5=Employer l'extraction pour l'accessibilité
|
||||
permissions.selectText.6=Employer de remplir le formulaire
|
||||
permissions.selectText.7=Employer la modification
|
||||
permissions.selectText.8=Employer la modification des annotations
|
||||
permissions.selectText.9=Employer l'impression
|
||||
permissions.selectText.10=Emp�cher l'impression de diff�rents formats
|
||||
permissions.selectText.6=Employer pour remplir le formulaire
|
||||
permissions.selectText.7=Employer pour la modification
|
||||
permissions.selectText.8=Employer pour la modification des annotations
|
||||
permissions.selectText.9=Employer pour l'impression
|
||||
permissions.selectText.10=Empêcher l'impression de différents formats
|
||||
permissions.submit=Modificateur
|
||||
|
||||
#supprimer le mot de passe
|
||||
@@ -209,27 +273,32 @@ removePassword.selectText.1=Sélectionnez le PDF à déchiffrer
|
||||
removePassword.selectText.2=Mot de passe
|
||||
removePassword.submit=Supprimer
|
||||
|
||||
changeMetadata.title=Modifier les métadonnées
|
||||
changeMetadata.header=Modifier les métadonnées
|
||||
changeMetadata.selectText.1=Veuillez modifier les variables que vous souhaitez modifier
|
||||
changeMetadata.selectText.2=Supprimer toutes les métadonnées
|
||||
changeMetadata.selectText.3=Afficher les métadonnées personnalisées:
|
||||
changeMetadata.author=Auteur:
|
||||
changeMetadata.creationDate=Date de création (aaaa/MM/jj HH:mm:ss):
|
||||
changeMetadata.creator=Créateur:
|
||||
changeMetadata.keywords=Mots clés:
|
||||
changeMetadata.modDate=Date de modification (aaaa/MM/jj HH:mm:ss):
|
||||
changeMetadata.producer=Producteur:
|
||||
changeMetadata.subject=Objet:
|
||||
changeMetadata.title=Titre:
|
||||
changeMetadata.trapped=Piégé:
|
||||
changeMetadata.selectText.4=Autres métadonnées:
|
||||
changeMetadata.selectText.5=Ajouter une entrée de métadonnées personnalisées
|
||||
#Change Metadata
|
||||
changeMetadata.title=Modifier les métadonnées
|
||||
changeMetadata.header=Modifier les métadonnées
|
||||
changeMetadata.selectText.1=Veuillez modifier les variables que vous souhaitez modifier.
|
||||
changeMetadata.selectText.2=Supprimer toutes les métadonnées.
|
||||
changeMetadata.selectText.3=Afficher les métadonnées personnalisées :
|
||||
changeMetadata.author=Auteur :
|
||||
changeMetadata.creationDate=Date de création (aaaa/MM/jj HH:mm:ss) :
|
||||
changeMetadata.creator=Créateur :
|
||||
changeMetadata.keywords=Mots clés :
|
||||
changeMetadata.modDate=Date de modification (aaaa/MM/jj HH:mm:ss) :
|
||||
changeMetadata.producer=Producteur :
|
||||
changeMetadata.subject=Objet :
|
||||
changeMetadata.title=Titre :
|
||||
changeMetadata.trapped=Piégé :
|
||||
changeMetadata.selectText.4=Autres métadonnées :
|
||||
changeMetadata.selectText.5=Ajouter une entrée de métadonnées personnalisées
|
||||
changeMetadata.submit=Modifier
|
||||
|
||||
|
||||
#XLS to PDF
|
||||
xlsToPdf.title=Excel vers PDF
|
||||
xlsToPdf.header=Excel en PDF
|
||||
xlsToPdf.selectText.1=Sélectionnez une feuille Excel XLS ou XLSX à convertir
|
||||
xlsToPdf.convert=convertir
|
||||
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
|
||||
3
src/main/resources/static/images/discord.svg
Normal file
3
src/main/resources/static/images/discord.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="43" height="43" fill="#007bff" class="bi bi-discord" viewBox="0 0 16 16">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612Zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -2,4 +2,5 @@
|
||||
<svg width="50px" height="50px" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke="#007bff" stroke-width="38" d="M 297.507 242.806 L 339.507 242.806 M 247.507 242.806 L 289.507 242.806 M 198.507 242.806 L 240.507 242.806 M 149.507 242.806 L 190.507 242.806 M 99.507 242.806 L 141.507 242.806 M 149.507 196.806 L 190.507 196.806 M 198.507 196.806 L 240.507 196.806 M 247.507 196.806 L 289.507 196.806 M 247.507 150.806 L 289.507 150.806"/>
|
||||
<path fill="#007bff" d="M 473.507 244.806 C 473.507 244.806 455.507 227.806 418.507 233.806 C 414.507 204.806 383.507 187.806 383.507 187.806 C 383.507 187.806 354.507 222.806 375.507 261.806 C 369.507 264.806 359.507 268.806 344.507 268.806 L 69.507 268.806 C 64.507 287.806 64.507 413.806 202.507 413.806 C 301.507 413.806 375.507 367.806 410.507 283.806 C 462.507 287.806 473.507 244.806 473.507 244.806"/>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 919 B After Width: | Height: | Size: 921 B |
13
src/main/resources/static/js/jszip.min.js
vendored
Normal file
13
src/main/resources/static/js/jszip.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
15821
src/main/resources/static/pdfjs/pdf.js
vendored
Normal file
15821
src/main/resources/static/pdfjs/pdf.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
src/main/resources/static/pdfjs/pdf.js.map
vendored
Normal file
1
src/main/resources/static/pdfjs/pdf.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
22
src/main/resources/static/pdfjs/pdf.min.js
vendored
22
src/main/resources/static/pdfjs/pdf.min.js
vendored
File diff suppressed because one or more lines are too long
64477
src/main/resources/static/pdfjs/pdf.worker.js
vendored
Normal file
64477
src/main/resources/static/pdfjs/pdf.worker.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/main/resources/static/pdfjs/pdf.worker.js.map
vendored
Normal file
1
src/main/resources/static/pdfjs/pdf.worker.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7702
src/main/resources/static/pdfjs/pdf_viewer.js
vendored
7702
src/main/resources/static/pdfjs/pdf_viewer.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{addImage.title})}"></th:block>
|
||||
@@ -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,9 +26,8 @@
|
||||
<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>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{compress.title})}"></th:block>
|
||||
@@ -14,16 +14,28 @@
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{compress.header}"></h2>
|
||||
<form action="#" th:action="@{compress-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
|
||||
<p th:text="#{processTimeWarning}"></p>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<div class="form-group">
|
||||
<label for="imageCompressionLevel" th:text="#{compress.compressLevel}"></label>
|
||||
<input type="number" class="form-control" id="imageCompressionLevel" name="imageCompressionLevel" step="1" value="1" min="1" max="100" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{compress.submit}"></button>
|
||||
</form>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
<form action="#" th:action="@{/compress-pdf}" method="post" enctype="multipart/form-data">
|
||||
<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">
|
||||
<option value="0" th:text="#{compress.selectText.2}"></option>
|
||||
<option value="1" selected th:text="#{compress.selectText.3}"></option>
|
||||
<option value="2" th:text="#{compress.selectText.4}"></option>
|
||||
<option value="3" th:text="#{compress.selectText.5}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" name="fastWebView" id="fastWebView">
|
||||
<label for="fastWebView" th:text="#{compress.selectText.6}"></label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" name="jbig2Lossy" id="jbig2Lossy">
|
||||
<label for="jbig2Lossy" th:text="#{compress.selectText.7}"></label>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{compress.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{compress.credit}"></p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
36
src/main/resources/templates/convert/file-to-pdf.html
Normal file
36
src/main/resources/templates/convert/file-to-pdf.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{fileToPDF.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="#{fileToPDF.header}"></h2>
|
||||
<p th:text="#{processTimeWarning}">
|
||||
<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" id="submitBtn" class="btn btn-primary" th:text="#{fileToPDF.submit}"></button>
|
||||
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{fileToPDF.credit}"></p>
|
||||
<p class="mt-3" th:text="#{fileToPDF.supportedFileTypes}"></p>
|
||||
<p th:utext="#{fileToPDF.fileTypesList}"></p>
|
||||
<a href="https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html">https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{imageToPDF.title})}"></th:block>
|
||||
|
||||
@@ -15,15 +15,57 @@
|
||||
<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>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfToImage.title})}"></th:block>
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
<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">
|
||||
<option value="png">PNG</option>
|
||||
<option value="jpg">JPG</option>
|
||||
<option value="gif">GIF</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -42,9 +44,8 @@
|
||||
<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>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
31
src/main/resources/templates/convert/pdf-to-pdfa.html
Normal file
31
src/main/resources/templates/convert/pdf-to-pdfa.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" 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>
|
||||
@@ -1,35 +0,0 @@
|
||||
<!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=#{xlsToPdf.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="#{xlsToPdf.header}"></h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{xlsx-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="#{xlsToPdf.selectText.1}"></label>
|
||||
</div>
|
||||
<br> <br>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
|
||||
|
||||
</form>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<title>Error! :(</title>
|
||||
|
||||
36
src/main/resources/templates/extract-images.html
Normal file
36
src/main/resources/templates/extract-images.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{extractImages.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="#{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, accept='application/pdf')}"></div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{extractImages.selectText}"></label>
|
||||
<select class="form-control" name="format">
|
||||
<option value="png">PNG</option>
|
||||
<option value="jpg">JPG</option>
|
||||
<option value="gif">GIF</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{extractImages.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,13 +2,17 @@
|
||||
|
||||
<!-- 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">
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="js/jquery.min.js"></script>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="js/jszip.min.js"></script>
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<script src="js/popper.min.js"></script>
|
||||
<script src="js/bootstrap.min.js"></script>
|
||||
@@ -16,122 +20,397 @@
|
||||
<link rel="stylesheet" href="css/bootstrap-icons.css">
|
||||
|
||||
<!-- PDF.js -->
|
||||
<script src="pdfjs/pdf.min.js"></script>
|
||||
<link href="pdfjs/pdf_viewer.min.css" rel="stylesheet">
|
||||
<script src="pdfjs/pdf.js"></script>
|
||||
|
||||
<!-- Custom -->
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" th:href="@{css/dark-mode.css}" id="dark-mode-styles">
|
||||
|
||||
<script>
|
||||
function toggleDarkMode() {
|
||||
var checkbox = document.getElementById("toggle-dark-mode");
|
||||
var darkModeStyles = document.getElementById("dark-mode-styles");
|
||||
if (checkbox.checked) {
|
||||
localStorage.setItem("dark-mode", "on");
|
||||
darkModeStyles.disabled = false;
|
||||
} else {
|
||||
localStorage.setItem("dark-mode", "off");
|
||||
darkModeStyles.disabled = true;
|
||||
}
|
||||
}
|
||||
$(document).ready(function() {
|
||||
var darkModeStyles = document.getElementById("dark-mode-styles");
|
||||
var checkbox = document.getElementById("toggle-dark-mode");
|
||||
if (localStorage.getItem("dark-mode") == "on") {
|
||||
darkModeStyles.disabled = false;
|
||||
checkbox.checked = true;
|
||||
}
|
||||
if (localStorage.getItem("dark-mode") == "off") {
|
||||
darkModeStyles.disabled = true;
|
||||
checkbox.checked = false;
|
||||
}
|
||||
function toggleDarkMode() {
|
||||
var checkbox = document.getElementById("toggle-dark-mode");
|
||||
var darkModeStyles = document.getElementById("dark-mode-styles");
|
||||
if (checkbox.checked) {
|
||||
localStorage.setItem("dark-mode", "on");
|
||||
darkModeStyles.disabled = false;
|
||||
} else {
|
||||
localStorage.setItem("dark-mode", "off");
|
||||
darkModeStyles.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
var darkModeStyles = document.getElementById("dark-mode-styles");
|
||||
var checkbox = document.getElementById("toggle-dark-mode");
|
||||
|
||||
// Check if the user has already set a preference
|
||||
if (localStorage.getItem("dark-mode") == "on") {
|
||||
darkModeStyles.disabled = false;
|
||||
checkbox.checked = true;
|
||||
} else if (localStorage.getItem("dark-mode") == "off") {
|
||||
darkModeStyles.disabled = true;
|
||||
checkbox.checked = false;
|
||||
} else {
|
||||
// Check the OS's default dark mode setting
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
darkModeStyles.disabled = false;
|
||||
checkbox.checked = true;
|
||||
} else {
|
||||
darkModeStyles.disabled = true;
|
||||
checkbox.checked = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<th:block th:fragment="filelist">
|
||||
<div id="fileList"></div>
|
||||
<div id="fileList2"></div>
|
||||
<script>
|
||||
var input = document.getElementById("fileInput");
|
||||
var output = document.getElementById("fileList");
|
||||
|
||||
input.addEventListener("change", function() {
|
||||
var files = input.files;
|
||||
var fileNames = "";
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
fileNames += (i + 1) + ". " + files[i].name + "<br>";
|
||||
}
|
||||
|
||||
output.innerHTML = fileNames;
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
var input2 = document.getElementById("fileInput2");
|
||||
var output2 = document.getElementById("fileList2");
|
||||
if (input2 != null && !input2) {
|
||||
input2.addEventListener("change", function() {
|
||||
var files = input2.files;
|
||||
var fileNames = "";
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
fileNames += (i + 1) + ". " + files[i].name + "<br>";
|
||||
}
|
||||
|
||||
output2.innerHTML = fileNames;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
if (dropContainer) {
|
||||
dropContainer.ondragover = dropContainer.ondragenter = function(evt) {
|
||||
evt.preventDefault();
|
||||
};
|
||||
|
||||
dropContainer.ondrop = function(evt) {
|
||||
if (fileInput) {
|
||||
fileInput.files = evt.dataTransfer.files;
|
||||
|
||||
const dT = new DataTransfer();
|
||||
dT.items.add(evt.dataTransfer.files[0]);
|
||||
dT.items.add(evt.dataTransfer.files[3]);
|
||||
fileInput.files = dT.files;
|
||||
|
||||
evt.preventDefault();
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</th:block>
|
||||
|
||||
<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'" th:multiple="${multiple}">
|
||||
<label class="custom-file-label" th:for="${name}+'-input'" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="custom-file">
|
||||
<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>
|
||||
<br>
|
||||
<div id="progressBarContainer" style="display: none; position: relative;">
|
||||
<div class="progress" style="height: 1rem;">
|
||||
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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;
|
||||
console.log(url)
|
||||
event.preventDefault(); // Prevent the default form handling behavior
|
||||
/* Check if ${multiple} is false */
|
||||
var multiple = [[${multiple}]] || false;
|
||||
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 {
|
||||
console.log("start single download")
|
||||
|
||||
// Get the selected download option from localStorage
|
||||
const downloadOption = localStorage.getItem('downloadOption');
|
||||
|
||||
var formData = new FormData($('form')[0]);
|
||||
|
||||
// Send the request to the server using the fetch() API
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => {
|
||||
if (!response) {
|
||||
throw new Error('Received null response for file ' + i);
|
||||
}
|
||||
console.log("load single download")
|
||||
|
||||
|
||||
// Extract the filename from the Content-Disposition header, if present
|
||||
let filename = null;
|
||||
const contentDispositionHeader = response.headers.get('Content-Disposition');
|
||||
console.log(contentDispositionHeader)
|
||||
if (contentDispositionHeader && contentDispositionHeader.indexOf('attachment') !== -1) {
|
||||
filename = contentDispositionHeader.split('filename=')[1].replace(/"/g, '');
|
||||
} else {
|
||||
// If the Content-Disposition header is not present or does not contain the filename, use a default filename
|
||||
filename = 'download';
|
||||
}
|
||||
console.log("filename=" + filename)
|
||||
|
||||
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
console.log("contentType=" + contentType)
|
||||
// Check if the response is a PDF or an image
|
||||
if (contentType.includes('pdf') || contentType.includes('image')) {
|
||||
response.blob().then(blob => {
|
||||
console.log("pdf/image")
|
||||
|
||||
// Perform the appropriate action based on the download option
|
||||
if (downloadOption === 'sameWindow') {
|
||||
console.log("same window")
|
||||
|
||||
// Open the file in the same window
|
||||
window.location.href = URL.createObjectURL(blob);
|
||||
} else if (downloadOption === 'newWindow') {
|
||||
console.log("new window")
|
||||
|
||||
// Open the file in a new window
|
||||
window.open(URL.createObjectURL(blob), '_blank');
|
||||
} else {
|
||||
console.log("else save")
|
||||
|
||||
// Download the file
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
link.click();
|
||||
}
|
||||
});
|
||||
} else if (contentType.includes('json')) {
|
||||
// Handle the JSON response
|
||||
response.json().then(data => {
|
||||
// Format the error message
|
||||
const errorMessage = JSON.stringify(data, null, 2);
|
||||
|
||||
// Display the error message in an alert
|
||||
alert(`An error occurred: ${errorMessage}`);
|
||||
});
|
||||
|
||||
} else {
|
||||
response.blob().then(blob => {
|
||||
console.log("else save 2 zip")
|
||||
|
||||
// For ZIP files or other file types, just download the file
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
link.click();
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("error listener")
|
||||
|
||||
// Extract the error message and stack trace from the response
|
||||
const errorMessage = error.message;
|
||||
const stackTrace = error.stack;
|
||||
|
||||
// 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) {
|
||||
// Get the selected PDF files
|
||||
let files = $('#fileInput-input')[0].files;
|
||||
|
||||
// Get the existing form data
|
||||
let formData = new FormData($('form')[0]);
|
||||
formData.delete('fileInput');
|
||||
|
||||
// Show the progress bar
|
||||
$('#progressBarContainer').show();
|
||||
|
||||
// Initialize the progress bar
|
||||
let progressBar = $('#progressBar');
|
||||
progressBar.css('width', '0%');
|
||||
progressBar.attr('aria-valuenow', 0);
|
||||
progressBar.attr('aria-valuemax', files.length);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Submit each PDF file in parallel
|
||||
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 (let pair of formData.entries()) {
|
||||
fileFormData.append(pair[0], pair[1]);
|
||||
}
|
||||
console.log(fileFormData);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
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() {
|
||||
updateProgressBar(progressBar, files);
|
||||
});
|
||||
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
// Wait for all requests to finish
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
} 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
|
||||
if (zipFiles) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<script th:inline="javascript">
|
||||
$([[${"#"+name+"-input"}]]).on("change", function() {
|
||||
const files = $(this).get(0).files;
|
||||
const fileNames = Array.from(files).map(f => f.name).join(", ");
|
||||
if (fileNames) {
|
||||
$(this).siblings(".custom-file-label").addClass("selected").html(fileNames);
|
||||
} else {
|
||||
$(this).siblings(".custom-file-label").addClass("selected").html([[#{pdfPrompt}]]);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const fileInput = document.getElementById('fileInput-input');
|
||||
|
||||
// Prevent default behavior for drag events
|
||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||
fileInput.addEventListener(eventName, preventDefaults, false);
|
||||
});
|
||||
|
||||
function preventDefaults(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
// Add drop event listener
|
||||
fileInput.addEventListener('drop', handleDrop, false);
|
||||
|
||||
function handleDrop(e) {
|
||||
const dt = e.dataTransfer;
|
||||
const files = dt.files;
|
||||
fileInput.files = files;
|
||||
handleFileInputChange(fileInput)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
$([[${"#"+name+"-input"}]]).on("change", function() {
|
||||
handleFileInputChange(this);
|
||||
});
|
||||
|
||||
function handleFileInputChange(inputElement) {
|
||||
const files = $(inputElement).get(0).files;
|
||||
const fileNames = Array.from(files).map(f => f.name);
|
||||
const selectedFilesContainer = $(inputElement).siblings(".selected-files");
|
||||
selectedFilesContainer.empty();
|
||||
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) {
|
||||
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames.length + " files selected");
|
||||
} else {
|
||||
$(inputElement).siblings(".custom-file-label").addClass("selected").html([[#{pdfPrompt}]]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.custom-file-label {
|
||||
padding-right: 90px;
|
||||
}
|
||||
padding-right: 90px;
|
||||
}
|
||||
|
||||
.selected-files {
|
||||
margin-top: 10px;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
</th:block>
|
||||
@@ -1,4 +1,5 @@
|
||||
<th:block th:fragment="errorBanner">
|
||||
|
||||
<style>
|
||||
#github-button,
|
||||
#discord-button {
|
||||
@@ -17,7 +18,7 @@
|
||||
background-color: #005b7f;
|
||||
}
|
||||
</style>
|
||||
<br th:if="${message}">
|
||||
<br th:if="${message}">
|
||||
<div id="errorContainer" th:if="${message}" class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<h4 class="alert-heading" th:text="'Error: ' + ${status} + ' ' + ${error}"></h4>
|
||||
<p th:text="${message} + ' for path: ' + ${path}"></p>
|
||||
@@ -36,39 +37,53 @@
|
||||
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank">Join our Discord server</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggletrace() {
|
||||
var traceDiv = document.getElementById("trace");
|
||||
if (traceDiv.style.maxHeight === "0px") {
|
||||
traceDiv.style.maxHeight = "500px";
|
||||
} else {
|
||||
traceDiv.style.maxHeight = "0px";
|
||||
<script>
|
||||
var traceVisible = false;
|
||||
|
||||
function toggletrace() {
|
||||
var traceDiv = document.getElementById("trace");
|
||||
if (!traceVisible) {
|
||||
traceDiv.style.maxHeight = "500px";
|
||||
traceVisible = true;
|
||||
} else {
|
||||
traceDiv.style.maxHeight = "0px";
|
||||
traceVisible = false;
|
||||
}
|
||||
adjustContainerHeight();
|
||||
}
|
||||
adjustContainerHeight();
|
||||
}
|
||||
|
||||
function copytrace() {
|
||||
var traceContent = document.getElementById("traceContent");
|
||||
var range = document.createRange();
|
||||
range.selectNode(traceContent);
|
||||
window.getSelection().removeAllRanges();
|
||||
window.getSelection().addRange(range);
|
||||
document.execCommand("copy");
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
function copytrace() {
|
||||
var flip = false
|
||||
if(!traceVisible) {
|
||||
toggletrace()
|
||||
flip = true
|
||||
}
|
||||
var traceContent = document.getElementById("traceContent");
|
||||
var range = document.createRange();
|
||||
range.selectNode(traceContent);
|
||||
window.getSelection().removeAllRanges();
|
||||
window.getSelection().addRange(range);
|
||||
document.execCommand("copy");
|
||||
window.getSelection().removeAllRanges();
|
||||
if(flip){
|
||||
toggletrace()
|
||||
}
|
||||
}
|
||||
|
||||
function dismissError() {
|
||||
var errorContainer = document.getElementById("errorContainer");
|
||||
errorContainer.style.display = "none";
|
||||
errorContainer.style.height ="0";
|
||||
}
|
||||
function dismissError() {
|
||||
var errorContainer = document.getElementById("errorContainer");
|
||||
errorContainer.style.display = "none";
|
||||
errorContainer.style.height ="0";
|
||||
}
|
||||
|
||||
function adjustContainerHeight() {
|
||||
var errorContainer = document.getElementById("errorContainer");
|
||||
var traceDiv = document.getElementById("trace");
|
||||
errorContainer.style.height = errorContainer.scrollHeight - traceDiv.scrollHeight + traceDiv.offsetHeight + "px";
|
||||
}
|
||||
function adjustContainerHeight() {
|
||||
var errorContainer = document.getElementById("errorContainer");
|
||||
var traceDiv = document.getElementById("trace");
|
||||
if (traceVisible) {
|
||||
errorContainer.style.height = errorContainer.scrollHeight - traceDiv.scrollHeight + traceDiv.offsetHeight + "px";
|
||||
} else {
|
||||
errorContainer.style.height = "auto";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</th:block>
|
||||
</th:block>
|
||||
@@ -1,6 +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>
|
||||
</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>
|
||||
@@ -1,133 +1,242 @@
|
||||
<div th:fragment="navbar">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container">
|
||||
<div th:fragment="navbar" class="mx-auto">
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const defaultLocale = document.documentElement.lang || 'en_GB';
|
||||
const storedLocale = localStorage.getItem('languageCode') || defaultLocale;
|
||||
const dropdownItems = document.querySelectorAll('.lang_dropdown-item');
|
||||
|
||||
<a class="navbar-brand" href="#" th:href="@{/}">Stirling PDF</a>
|
||||
for (let i = 0; i < dropdownItems.length; i++) {
|
||||
const item = dropdownItems[i];
|
||||
item.classList.remove('active');
|
||||
if (item.dataset.languageCode === storedLocale) {
|
||||
item.classList.add('active');
|
||||
}
|
||||
item.addEventListener('click', handleDropdownItemClick);
|
||||
}
|
||||
});
|
||||
|
||||
<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>
|
||||
</button>
|
||||
function handleDropdownItemClick(event) {
|
||||
event.preventDefault();
|
||||
const languageCode = this.dataset.languageCode;
|
||||
localStorage.setItem('languageCode', languageCode);
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
|
||||
<ul class="navbar-nav">
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" th:href="@{merge-pdfs}" th:classappend="${currentPage}=='merge-pdfs' ? 'active' : ''" th:text="#{home.merge.title}"></a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" th:href="@{split-pdfs}" th:classappend="${currentPage}=='split-pdfs' ? 'active' : ''" th:text="#{home.split.title}"></a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" th:href="@{pdf-organizer}" th:classappend="${currentPage}=='pdf-organizer' ? 'active' : ''" th:text="#{home.pdfOrganiser.title}"></a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" th:href="@{rotate-pdf}" th:classappend="${currentPage}=='rotate-pdf' ? 'active' : ''" th:text="#{home.rotate.title}"></a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-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="@{xlsx-to-pdf}" th:classappend="${currentPage}=='xlsx-to-pdf' ? 'active' : ''" th:text="#{home.xlsToPdf.title}"></a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='change-permissions' OR ${currentPage}=='add-watermark' OR ${currentPage}=='remove-watermark' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.security}"></a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="#" th:href="@{add-password}" th:classappend="${currentPage}=='add-password' ? 'active' : ''" th:text="#{home.addPassword.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{remove-password}" th:classappend="${currentPage}=='remove-password' ? 'active' : ''" th:text="#{home.removePassword.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{change-permissions}" th:classappend="${currentPage}=='change-permissions' ? 'active' : ''" th:text="#{home.permissions.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{add-watermark}" th:classappend="${currentPage}=='add-watermark' ? 'active' : ''" th:text="#{home.watermark.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{change-metadata}" th:classappend="${currentPage}=='change-metadata' ? 'active' : ''" th:text="#{home.changeMetadata.title}"></a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='add-image' OR ${currentPage}=='compress-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.other}"></a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="#" th:href="@{add-image}" th:classappend="${currentPage}=='add-image' ? 'active' : ''" th:text="#{home.addImage.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''" th:text="#{home.compressPdfs.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{remove-pages}" th:classappend="${currentPage}=='remove-pages' ? 'active' : ''" th:text="#{home.removePages.title}"></a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" th:href="@{add-image}" th:classappend="${currentPage}=='add-image' ? 'active' : ''"></a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''"></a>
|
||||
</li>
|
||||
|
||||
<input type="checkbox" id="toggle-dark-mode" checked="true" th:onclick="javascript:toggleDarkMode()">
|
||||
<a class="nav-link" href="#" for="toggle-dark-mode" th:text="#{navbar.darkmode}"></a>
|
||||
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="bi bi-globe2"></i>
|
||||
</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="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>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Get the dropdown items
|
||||
var dropdownItems = document.querySelectorAll('.lang_dropdown-item');
|
||||
|
||||
// Loop through the dropdown items
|
||||
for (var i = 0; i < dropdownItems.length; i++) {
|
||||
dropdownItems[i].classList.remove('active');
|
||||
if (dropdownItems[i].dataset.languageCode === localStorage.getItem('languageCode')) {
|
||||
dropdownItems[i].classList.add('active');
|
||||
}
|
||||
|
||||
// Add a click event listener to each dropdown item
|
||||
dropdownItems[i].addEventListener('click', function(event) {
|
||||
|
||||
// Prevent the default link behavior
|
||||
event.preventDefault();
|
||||
|
||||
// Get the language code
|
||||
var languageCode = this.dataset.languageCode;
|
||||
|
||||
// Save the language code to local storage
|
||||
localStorage.setItem('languageCode', languageCode);
|
||||
|
||||
// Get the current URL
|
||||
var currentUrl = window.location.href;
|
||||
|
||||
// Check if the URL already contains a "?lang" query parameter
|
||||
if (currentUrl.indexOf('?lang=') === -1) {
|
||||
// Update the URL with the "?lang" query parameter
|
||||
window.location.href = currentUrl + '?lang=' + languageCode;
|
||||
} else {
|
||||
// Replace the "?lang" query parameter with the new value
|
||||
window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
const currentUrl = window.location.href;
|
||||
if (currentUrl.indexOf('?lang=') === -1) {
|
||||
window.location.href = currentUrl + '?lang=' + languageCode;
|
||||
} else {
|
||||
window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<script th:inline="javascript">
|
||||
function compareVersions(version1, version2) {
|
||||
const v1 = version1.split('.');
|
||||
const v2 = version2.split('.');
|
||||
|
||||
for (let i = 0; i < v1.length || i < v2.length; i++) {
|
||||
const n1 = parseInt(v1[i]) || 0;
|
||||
const n2 = parseInt(v2[i]) || 0;
|
||||
|
||||
if (n1 > n2) {
|
||||
return 1;
|
||||
} else if (n1 < n2) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function getLatestReleaseVersion() {
|
||||
const url = "https://api.github.com/repos/Frooodle/Stirling-PDF/releases/latest";
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
return data.tag_name.substring(1);
|
||||
}
|
||||
|
||||
const currentVersion = /*[[${@appVersion}]]*/ ''; // Replace with your current version number
|
||||
|
||||
async function checkForUpdate() {
|
||||
const latestVersion = await getLatestReleaseVersion();
|
||||
console.log("latestVersion=" + latestVersion)
|
||||
console.log("currentVersion=" + currentVersion)
|
||||
console.log("compareVersions(latestVersion, currentVersion) > 0)=" + compareVersions(latestVersion, currentVersion))
|
||||
if (latestVersion != null && latestVersion != "" && compareVersions(latestVersion, currentVersion) > 0) {
|
||||
document.getElementById("update-btn").style.visibility = "visible";
|
||||
console.log("visible")
|
||||
} else {
|
||||
document.getElementById("update-btn").style.visibility = "hidden";
|
||||
console.log("hidden")
|
||||
}
|
||||
}
|
||||
|
||||
checkForUpdate();
|
||||
</script>
|
||||
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container ">
|
||||
|
||||
<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>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
|
||||
<ul class="navbar-nav mr-auto flex-nowrap">
|
||||
|
||||
<li class="nav-item"><a class="nav-link" href="#" th:href="@{merge-pdfs}" th:classappend="${currentPage}=='merge-pdfs' ? 'active' : ''" th:text="#{home.merge.title}"></a></li>
|
||||
|
||||
<li class="nav-item"><a class="nav-link" href="#" th:href="@{split-pdfs}" th:classappend="${currentPage}=='split-pdfs' ? 'active' : ''" th:text="#{home.split.title}"></a></li>
|
||||
|
||||
<li class="nav-item"><a class="nav-link" href="#" th:href="@{pdf-organizer}" th:classappend="${currentPage}=='pdf-organizer' ? 'active' : ''" th:text="#{home.pdfOrganiser.title}"></a></li>
|
||||
|
||||
<li class="nav-item"><a class="nav-link" href="#" th:href="@{rotate-pdf}" th:classappend="${currentPage}=='rotate-pdf' ? 'active' : ''" th:text="#{home.rotate.title}"></a></li>
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='change-permissions' OR ${currentPage}=='add-watermark' OR ${currentPage}=='remove-watermark' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.security}"></a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="#" th:href="@{add-password}" th:classappend="${currentPage}=='add-password' ? 'active' : ''" th:text="#{home.addPassword.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{remove-password}" th:classappend="${currentPage}=='remove-password' ? 'active' : ''" th:text="#{home.removePassword.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{change-permissions}" th:classappend="${currentPage}=='change-permissions' ? 'active' : ''" th:text="#{home.permissions.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{add-watermark}" th:classappend="${currentPage}=='add-watermark' ? 'active' : ''" th:text="#{home.watermark.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{change-metadata}" th:classappend="${currentPage}=='change-metadata' ? 'active' : ''" th:text="#{home.changeMetadata.title}"></a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='add-image' OR ${currentPage}=='ocr-pdf' OR ${currentPage}=='extract-images' OR ${currentPage}=='compress-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.other}"></a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="#" th:href="@{ocr-pdf}" th:classappend="${currentPage}=='ocr-pdf' ? 'active' : ''" th:text="#{home.ocr.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{add-image}" th:classappend="${currentPage}=='add-image' ? 'active' : ''" th:text="#{home.addImage.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''" th:text="#{home.compressPdfs.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{remove-pages}" th:classappend="${currentPage}=='remove-pages' ? 'active' : ''" th:text="#{home.removePages.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{extract-images}" th:classappend="${currentPage}=='extract-images' ? 'active' : ''" th:text="#{home.extractImages.title}"></a>
|
||||
</div></li>
|
||||
|
||||
|
||||
|
||||
<input type="checkbox" id="toggle-dark-mode" checked="true" th:onclick="javascript:toggleDarkMode()">
|
||||
<a class="nav-link" href="#" for="toggle-dark-mode" th:text="#{navbar.darkmode}" ></a>
|
||||
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-globe2" viewBox="0 0 20 20">
|
||||
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.539c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.312.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a13.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.694.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.418.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="languageDropdown">
|
||||
<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>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<!-- Settings Button -->
|
||||
<button type="button" class="btn btn-secondary float-right" data-toggle="modal" data-target="#settingsModal">
|
||||
<span class="glyphicon glyphicon-cog"></span><span th:text="#{navbar.settings}"></span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
<div th:insert="~{fragments/errorBanner.html :: errorBanner}"></div>
|
||||
|
||||
<div class="modal fade" id="settingsModal" tabindex="-1" role="dialog" aria-labelledby="settingsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content dark-card">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="settingsModalLabel" th:text="#{settings.title}"></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<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:utext="#{settings.update}"></button>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="downloadOption" th:utext="#{settings.downloadOption.title}"></label>
|
||||
<select class="form-control" id="downloadOption">
|
||||
<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">
|
||||
<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">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal" th:text="#{close}"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
// Get the download option from local storage, or set it to 'sameWindow' if it doesn't exist
|
||||
var downloadOption = localStorage.getItem('downloadOption')
|
||||
|| 'sameWindow';
|
||||
|
||||
// Set the selected option in the dropdown
|
||||
document.getElementById('downloadOption').value = downloadOption;
|
||||
|
||||
// Save the selected option to local storage when the dropdown value changes
|
||||
document.getElementById('downloadOption').addEventListener(
|
||||
'change',
|
||||
function() {
|
||||
downloadOption = this.value;
|
||||
localStorage.setItem('downloadOption',
|
||||
downloadOption);
|
||||
});
|
||||
|
||||
|
||||
// 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 value of the slider and the display span
|
||||
document.getElementById('zipThreshold').value = zipThreshold;
|
||||
document.getElementById('zipThresholdValue').textContent = zipThreshold;
|
||||
|
||||
// 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>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title='')}"></th:block>
|
||||
@@ -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>
|
||||
|
||||
@@ -50,8 +52,8 @@
|
||||
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.addImage.title}, cardText=#{home.addImage.desc}, cardLink='add-image')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.watermark.title}, cardText=#{home.watermark.desc}, cardLink='add-watermark')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.xlsToPdf.title}, cardText=#{home.xlsToPdf.desc}, cardLink='xlsx-to-pdf')}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.fileToPDF.title}, cardText=#{home.fileToPDF.desc}, cardLink='file-to-pdf')}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.removePages.title}, cardText=#{home.removePages.desc}, cardLink='remove-pages')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.addPassword.title}, cardText=#{home.addPassword.desc}, cardLink='add-password')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.removePassword.title}, cardText=#{home.removePassword.desc}, cardLink='remove-password')}"></div>
|
||||
@@ -60,6 +62,11 @@
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.changeMetadata.title}, cardText=#{home.changeMetadata.desc}, cardLink='change-metadata')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions')}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.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>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{merge.title})}"></th:block>
|
||||
|
||||
@@ -16,106 +16,113 @@
|
||||
<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>
|
||||
|
||||
.list-group-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filename {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.arrows {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.move-up span,
|
||||
.move-down span {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
<script>
|
||||
document
|
||||
.getElementById(
|
||||
"fileInput")
|
||||
.addEventListener(
|
||||
"change",
|
||||
function() {
|
||||
var files = this.files;
|
||||
var list = document
|
||||
.getElementById("selectedFiles");
|
||||
list.innerHTML = "";
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var item = document
|
||||
.createElement("li");
|
||||
item.className = "list-group-item d-flex justify-content-between align-items-center";
|
||||
item.textContent = files[i].name;
|
||||
item.innerHTML += '<div><button class="btn btn-secondary move-up"><i class="fas fa-arrow-up"></i></button> <button class="btn btn-secondary move-down"><i class="fas fa-arrow-down"></i></button></div>';
|
||||
list
|
||||
.appendChild(item);
|
||||
}
|
||||
var moveUpButtons = document
|
||||
.querySelectorAll(".move-up");
|
||||
for (var i = 0; i < moveUpButtons.length; i++) {
|
||||
moveUpButtons[i]
|
||||
.addEventListener(
|
||||
"click",
|
||||
function(
|
||||
event) {
|
||||
event
|
||||
.preventDefault();
|
||||
var parent = this.parentNode.parentNode;
|
||||
var grandParent = parent.parentNode;
|
||||
if (parent.previousElementSibling) {
|
||||
grandParent
|
||||
.insertBefore(
|
||||
parent,
|
||||
parent.previousElementSibling);
|
||||
updateFiles();
|
||||
}
|
||||
});
|
||||
}
|
||||
var moveDownButtons = document
|
||||
.querySelectorAll(".move-down");
|
||||
for (var i = 0; i < moveDownButtons.length; i++) {
|
||||
moveDownButtons[i]
|
||||
.addEventListener(
|
||||
"click",
|
||||
function(
|
||||
event) {
|
||||
event
|
||||
.preventDefault();
|
||||
var parent = this.parentNode.parentNode;
|
||||
var grandParent = parent.parentNode;
|
||||
if (parent.nextElementSibling) {
|
||||
grandParent
|
||||
.insertBefore(
|
||||
parent.nextElementSibling,
|
||||
parent);
|
||||
updateFiles();
|
||||
}
|
||||
});
|
||||
}
|
||||
document.getElementById("fileInput-input").addEventListener("change", function() {
|
||||
var files = this.files;
|
||||
var list = document.getElementById("selectedFiles");
|
||||
list.innerHTML = "";
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var item = document.createElement("li");
|
||||
item.className = "list-group-item";
|
||||
item.innerHTML = `
|
||||
<div class="d-flex justify-content-between align-items-center w-100">
|
||||
<div class="filename">${files[i].name}</div>
|
||||
<div class="arrows d-flex">
|
||||
<button class="btn btn-secondary move-up"><span>↑</span></button>
|
||||
<button class="btn btn-secondary move-down"><span>↓</span></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
}
|
||||
|
||||
function updateFiles() {
|
||||
var dataTransfer = new DataTransfer();
|
||||
var liElements = document
|
||||
.querySelectorAll("#selectedFiles li");
|
||||
var moveUpButtons = document.querySelectorAll(".move-up");
|
||||
for (var i = 0; i < moveUpButtons.length; i++) {
|
||||
moveUpButtons[i].addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
var parent = this.closest(".list-group-item");
|
||||
var grandParent = parent.parentNode;
|
||||
if (parent.previousElementSibling) {
|
||||
grandParent.insertBefore(parent, parent.previousElementSibling);
|
||||
updateFiles();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var moveDownButtons = document.querySelectorAll(".move-down");
|
||||
for (var i = 0; i < moveDownButtons.length; i++) {
|
||||
moveDownButtons[i].addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
var parent = this.closest(".list-group-item");
|
||||
var grandParent = parent.parentNode;
|
||||
if (parent.nextElementSibling) {
|
||||
grandParent.insertBefore(parent.nextElementSibling, parent);
|
||||
updateFiles();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateFiles() {
|
||||
var dataTransfer = new DataTransfer();
|
||||
var liElements = document.querySelectorAll("#selectedFiles li");
|
||||
|
||||
for (var i = 0; i < liElements.length; i++) {
|
||||
var fileNameFromList = liElements[i].querySelector(".filename").innerText;
|
||||
var fileFromFiles;
|
||||
for (var j = 0; j < files.length; j++) {
|
||||
var file = files[j];
|
||||
if (file.name === fileNameFromList) {
|
||||
dataTransfer.items.add(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
document.getElementById("fileInput-input").files = dataTransfer.files;
|
||||
}
|
||||
});
|
||||
|
||||
for (var i = 0; i < liElements.length; i++) {
|
||||
var fileNameFromList = liElements[i].innerText
|
||||
.replace(
|
||||
"\nMove Up Move Down",
|
||||
"");
|
||||
var fileFromFiles
|
||||
for (var j = 0; j < files.length; j++) {
|
||||
var file = files[j];
|
||||
if (file.name === fileNameFromList) {
|
||||
dataTransfer.items
|
||||
.add(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
document
|
||||
.getElementById("fileInput").files = dataTransfer.files;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
71
src/main/resources/templates/ocr-pdf.html
Normal file
71
src/main/resources/templates/ocr-pdf.html
Normal file
@@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{ocr.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="#{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, 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>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfOrganiser.title})}"></th:block>
|
||||
|
||||
@@ -15,14 +15,13 @@
|
||||
<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>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pageRemover.title})}"></th:block>
|
||||
|
||||
@@ -15,15 +15,13 @@
|
||||
<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>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{rotate.title})}"></th:block>
|
||||
@@ -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" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{addPassword.title})}"></th:block>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{watermark.title})}"></th:block>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{changeMetadata.title})}"></th:block>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{permissions.title})}"></th:block>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{removePassword.title})}"></th:block>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{remove-watermark.title})}"></th:block>
|
||||
|
||||
@@ -13,17 +13,17 @@
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{remove-watermark.header}"></h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" action="remove-text">
|
||||
<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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{split.title})}"></th:block>
|
||||
|
||||
@@ -24,16 +24,15 @@
|
||||
<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>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user