Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
c613164003 | ||
|
|
67345d083e | ||
|
|
6135d08154 | ||
|
|
340cbb9acf | ||
|
|
57b67fdeb1 | ||
|
|
b40d324e04 | ||
|
|
54e1ced26a | ||
|
|
c739c9dd2b | ||
|
|
aa9f8329d5 | ||
|
|
9b3aac4a8a | ||
|
|
75d841083c | ||
|
|
a01ad71414 | ||
|
|
9e6771d0f8 | ||
|
|
c83c57a37e | ||
|
|
90ef6deca8 | ||
|
|
83bd2d5915 | ||
|
|
879f558b44 | ||
|
|
f7d320ac32 |
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
|
||||||
17
.github/workflows/codeql.yml
vendored
@@ -7,9 +7,8 @@
|
|||||||
# ******** NOTE ********
|
# ******** NOTE ********
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
# supported CodeQL languages.
|
|
||||||
#
|
name: "Build repo"
|
||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -42,15 +41,15 @@ jobs:
|
|||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
# - name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
# uses: github/codeql-action/init@v2
|
||||||
with:
|
# with:
|
||||||
languages: java
|
# languages: java
|
||||||
|
|
||||||
- uses: gradle/gradle-build-action@v2.3.3
|
- uses: gradle/gradle-build-action@v2.3.3
|
||||||
with:
|
with:
|
||||||
gradle-version: 7.6
|
gradle-version: 7.6
|
||||||
arguments: assemble --no-build-cache
|
arguments: assemble --no-build-cache
|
||||||
|
|
||||||
- name: Perform CodeQL analysis
|
#- name: Perform CodeQL analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
# uses: github/codeql-action/analyze@v2
|
||||||
|
|||||||
32
.github/workflows/push-docker.yml
vendored
@@ -39,18 +39,18 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_HUB_API }}
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
|
||||||
- name: Check if tag exists
|
# - name: Check if tag exists
|
||||||
id: checkIdExists
|
# id: checkIdExists
|
||||||
continue-on-error: true
|
# continue-on-error: true
|
||||||
run: |
|
# run: |
|
||||||
response=$(curl -s https://hub.docker.com/v2/repositories/frooodle/s-pdf/tags/?name=${{ steps.versionNumber.outputs.versionNumber }})
|
# response=$(curl -s https://hub.docker.com/v2/repositories/frooodle/s-pdf/tags/?name=${{ steps.versionNumber.outputs.versionNumber }})
|
||||||
result=$(echo $response | jq ".results")
|
# result=$(echo $response | jq ".results")
|
||||||
if [ "$result" == "[]" ]; then
|
# if [ "$result" == "[]" ]; then
|
||||||
echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} doesnt exist. Continuing with build and push."
|
# echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} doesnt exist. Continuing with build and push."
|
||||||
else
|
# else
|
||||||
echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} already exists. Skipping build and push."
|
# echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} already exists. Skipping build and push."
|
||||||
exit 1;
|
# exit 1;
|
||||||
fi
|
# fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -58,9 +58,15 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
docker buildx create --name mybuilder
|
docker buildx create --name mybuilder
|
||||||
docker buildx use mybuilder
|
docker buildx use mybuilder
|
||||||
|
|
||||||
|
- name: Build and push versioned amd64 and v8
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
run: |
|
||||||
|
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:${{ steps.versionNumber.outputs.versionNumber }}-alpha" .
|
||||||
|
|
||||||
|
|
||||||
- name: Build and push versioned amd64 and v8
|
- name: Build and push versioned amd64 and v8
|
||||||
if: github.ref == 'refs/heads/main' && steps.checkIdExists.outcome != 'failure'
|
if: github.ref == 'refs/heads/master'
|
||||||
run: |
|
run: |
|
||||||
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:${{ steps.versionNumber.outputs.versionNumber }}" .
|
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:${{ steps.versionNumber.outputs.versionNumber }}" .
|
||||||
|
|
||||||
|
|||||||
2
.gitignore
vendored
@@ -108,3 +108,5 @@ local.properties
|
|||||||
*.zip
|
*.zip
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.rar
|
*.rar
|
||||||
|
|
||||||
|
/build
|
||||||
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
|
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
|
COPY build/libs/*.jar app.jar
|
||||||
|
|
||||||
|
# Expose the application port
|
||||||
EXPOSE 8080
|
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
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
38
README.md
@@ -1,6 +1,11 @@
|
|||||||
<p align="center"><img src="https://raw.githubusercontent.com/Frooodle/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1>
|
<p align="center"><img src="https://raw.githubusercontent.com/Frooodle/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
[](https://hub.docker.com/r/frooodle/s-pdf)
|
||||||
|
[](https://discord.gg/Cn8pWhQRxZ)
|
||||||
|
[](https://github.com/Frooodle/Stirling-PDF/)
|
||||||
|
[](https://github.com/Frooodle/stirling-pdf)
|
||||||
|
[](https://www.paypal.com/paypalme/froodleplex)
|
||||||
|
|
||||||
This is a locally hosted web application that allows you to perform various operations on PDF files, such as splitting and adding images.
|
This is a locally hosted web application that allows you to perform various operations on PDF files, such as splitting and adding images.
|
||||||
|
|
||||||
@@ -11,25 +16,31 @@ I will support and fix/add things to this if there is a demand [Discord](https:/
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files.
|
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files.
|
||||||
- Merge multiple PDFs together into a single resultant file
|
- Merge multiple PDFs together into a single resultant file
|
||||||
- Convert PDFs to and from images
|
- Convert PDFs to and from images
|
||||||
- Reorganize PDF pages into different orders.
|
- Reorganize PDF pages into different orders.
|
||||||
- Add images to PDFs at specified locations.
|
- Add images to PDFs at specified locations. (WIP)
|
||||||
- Rotating PDFs in 90 degree increments.
|
- Rotating PDFs in 90 degree increments.
|
||||||
- Compressing PDFs to decrease their filesize.
|
- Compressing PDFs to decrease their filesize. (Using OCRMyPDF)
|
||||||
- Add and remove passwords
|
- Add and remove passwords
|
||||||
- Set PDF Permissions
|
- Set PDF Permissions
|
||||||
- Add watermark(s)
|
- 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.
|
- 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
|
## Technologies used
|
||||||
- Spring Boot + Thymeleaf
|
- Spring Boot + Thymeleaf
|
||||||
- PDFBox
|
- 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
|
- HTML, CSS, JavaScript
|
||||||
- Docker
|
- Docker
|
||||||
|
|
||||||
@@ -63,9 +74,24 @@ services:
|
|||||||
image: frooodle/s-pdf
|
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
|
## How to View
|
||||||
1. Open a web browser and navigate to `http://localhost:8080/`
|
1. Open a web browser and navigate to `http://localhost:8080/`
|
||||||
2. Use the application by following the instructions on the website.
|
2. Use the application by following the instructions on the website.
|
||||||
|
|
||||||
## Note
|
|
||||||
The application is currently not thread-safe
|
## Customize App Name
|
||||||
|
Stirling PDF allows easy customization of the visible application name.
|
||||||
|
Simply use environment variables APP_HOME_NAME, APP_HOME_DESCRIPTION and APP_NAVBAR_NAME with Docker or Java.
|
||||||
|
If running Java directly, you can also pass these as properties using -D arguments.
|
||||||
30
build.gradle
@@ -1,32 +1,43 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
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'
|
id 'io.spring.dependency-management' version '1.1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'stirling.software'
|
group = 'stirling.software'
|
||||||
version = '0.3.2'
|
version = '0.4.6'
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {
|
|
||||||
url "https://repo.e-iceblue.com/nexus/content/groups/public/"
|
|
||||||
name "com.e-iceblue"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
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 'org.apache.logging.log4j:log4j-core:2.20.0'
|
||||||
implementation 'e-iceblue:spire.pdf.free:5.1.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 {
|
jar {
|
||||||
enabled = false
|
enabled = false
|
||||||
|
manifest {
|
||||||
|
attributes 'Implementation-Title': 'Stirling-PDF',
|
||||||
|
'Implementation-Version': project.version
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
@@ -36,4 +47,3 @@ tasks.named('test') {
|
|||||||
task printVersion {
|
task printVersion {
|
||||||
println project.version
|
println project.version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
docs/stirling-pdf.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
images/settings.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 69 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
|
@SpringBootApplication
|
||||||
public class SPdfApplication {
|
public class SPdfApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
public static void main(String[] args) {
|
SpringApplication.run(SPdfApplication.class, args);
|
||||||
SpringApplication.run(SPdfApplication.class, args);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.servlet.LocaleResolver;
|
import org.springframework.web.servlet.LocaleResolver;
|
||||||
@@ -8,15 +10,13 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class Beans implements WebMvcConfigurer {
|
public class Beans implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public LocaleResolver localeResolver() {
|
public LocaleResolver localeResolver() {
|
||||||
SessionLocaleResolver slr = new SessionLocaleResolver();
|
SessionLocaleResolver slr = new SessionLocaleResolver();
|
||||||
slr.setDefaultLocale(Locale.US);
|
slr.setDefaultLocale(Locale.UK);
|
||||||
return slr;
|
return slr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package stirling.software.SPDF.controller;
|
package stirling.software.SPDF.controller;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
@@ -18,57 +18,75 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import com.spire.pdf.PdfCompressionLevel;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
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.PdfUtils;
|
|
||||||
|
|
||||||
//import com.spire.pdf.*;
|
|
||||||
@Controller
|
@Controller
|
||||||
public class CompressController {
|
public class CompressController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
||||||
|
|
||||||
@GetMapping("/compress-pdf")
|
@GetMapping("/compress-pdf")
|
||||||
public String compressPdfForm(Model model) {
|
public String compressPdfForm(Model model) {
|
||||||
model.addAttribute("currentPage", "compress-pdf");
|
model.addAttribute("currentPage", "compress-pdf");
|
||||||
return "compress-pdf";
|
return "compress-pdf";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/compress-pdf")
|
|
||||||
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile pdfFile,
|
@PostMapping("/compress-pdf")
|
||||||
@RequestParam("imageCompressionLevel") String imageCompressionLevel) throws IOException {
|
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 {
|
||||||
|
|
||||||
// Load a sample PDF document
|
// Save the uploaded file to a temporary location
|
||||||
PdfDocument document = new PdfDocument();
|
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
document.loadFromBytes(pdfFile.getBytes());
|
inputFile.transferTo(tempInputFile.toFile());
|
||||||
|
|
||||||
// Compress PDF
|
// Prepare the output file path
|
||||||
document.getFileInfo().setIncrementalUpdate(false);
|
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
document.setCompressionLevel(PdfCompressionLevel.Best);
|
|
||||||
|
|
||||||
// compress PDF Images
|
// Prepare the OCRmyPDF command
|
||||||
for (int i = 0; i < document.getPages().getCount(); i++) {
|
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");
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
if (jbig2Lossy != null && jbig2Lossy) {
|
||||||
}
|
command.add("--jbig2-lossy");
|
||||||
|
}
|
||||||
|
|
||||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_compressed.pdf");
|
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,57 +21,51 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class MergeController {
|
public class MergeController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
||||||
|
|
||||||
@GetMapping("/merge-pdfs")
|
@GetMapping("/merge-pdfs")
|
||||||
public String hello(Model model) {
|
public String hello(Model model) {
|
||||||
model.addAttribute("currentPage", "merge-pdfs");
|
model.addAttribute("currentPage", "merge-pdfs");
|
||||||
return "merge-pdfs";
|
return "merge-pdfs";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/merge-pdfs")
|
@PostMapping("/merge-pdfs")
|
||||||
public ResponseEntity<InputStreamResource> mergePdfs(@RequestParam("fileInput") MultipartFile[] files)
|
public ResponseEntity<byte[]> mergePdfs(@RequestParam("fileInput") MultipartFile[] files) throws IOException {
|
||||||
throws IOException {
|
// Read the input PDF files into PDDocument objects
|
||||||
// Read the input PDF files into PDDocument objects
|
List<PDDocument> documents = new ArrayList<>();
|
||||||
List<PDDocument> documents = new ArrayList<>();
|
|
||||||
|
|
||||||
// Loop through the files array and read each file into a PDDocument
|
// Loop through the files array and read each file into a PDDocument
|
||||||
for (MultipartFile file : files) {
|
for (MultipartFile file : files) {
|
||||||
documents.add(PDDocument.load(file.getInputStream()));
|
documents.add(PDDocument.load(file.getInputStream()));
|
||||||
}
|
}
|
||||||
|
|
||||||
PDDocument mergedDoc = mergeDocuments(documents);
|
PDDocument mergedDoc = mergeDocuments(documents);
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
|
||||||
mergedDoc.save(byteArrayOutputStream);
|
|
||||||
mergedDoc.close();
|
|
||||||
|
|
||||||
// Create an InputStreamResource from the merged PDF
|
// Return the merged PDF as a response
|
||||||
InputStreamResource resource = new InputStreamResource(
|
return PdfUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_merged.pdf");
|
||||||
new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
|
}
|
||||||
|
|
||||||
// Return the merged PDF as a response
|
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
||||||
return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).body(resource);
|
// Create a new empty document
|
||||||
}
|
PDDocument mergedDoc = new PDDocument();
|
||||||
|
|
||||||
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
// Iterate over the list of documents and add their pages to the merged document
|
||||||
// Create a new empty document
|
for (PDDocument doc : documents) {
|
||||||
PDDocument mergedDoc = new PDDocument();
|
// Get all pages from the current document
|
||||||
|
PDPageTree pages = doc.getPages();
|
||||||
|
// Iterate over the pages and add them to the merged document
|
||||||
|
for (PDPage page : pages) {
|
||||||
|
mergedDoc.addPage(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate over the list of documents and add their pages to the merged document
|
// Return the merged document
|
||||||
for (PDDocument doc : documents) {
|
return mergedDoc;
|
||||||
// Get all pages from the current document
|
}
|
||||||
PDPageTree pages = doc.getPages();
|
|
||||||
// Iterate over the pages and add them to the merged document
|
|
||||||
for (PDPage page : pages) {
|
|
||||||
mergedDoc.addPage(page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the merged document
|
|
||||||
return mergedDoc;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,9 +4,7 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
@@ -20,27 +18,26 @@ import stirling.software.SPDF.utils.PdfUtils;
|
|||||||
@Controller
|
@Controller
|
||||||
public class OverlayImageController {
|
public class OverlayImageController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
||||||
|
|
||||||
@GetMapping("/add-image")
|
@GetMapping("/add-image")
|
||||||
public String overlayImage(Model model) {
|
public String overlayImage(Model model) {
|
||||||
model.addAttribute("currentPage", "add-image");
|
model.addAttribute("currentPage", "add-image");
|
||||||
return "add-image";
|
return "add-image";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/add-image")
|
@PostMapping("/add-image")
|
||||||
public ResponseEntity<byte[]> overlayImage(@RequestParam("fileInput") MultipartFile pdfFile,
|
public ResponseEntity<byte[]> overlayImage(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("fileInput2") MultipartFile imageFile, @RequestParam("x") float x,
|
||||||
@RequestParam("fileInput2") MultipartFile imageFile, @RequestParam("x") float x,
|
@RequestParam("y") float y) {
|
||||||
@RequestParam("y") float y) {
|
try {
|
||||||
try {
|
byte[] pdfBytes = pdfFile.getBytes();
|
||||||
byte[] pdfBytes = pdfFile.getBytes();
|
byte[] imageBytes = imageFile.getBytes();
|
||||||
byte[] imageBytes = imageFile.getBytes();
|
byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y);
|
||||||
byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y);
|
|
||||||
|
|
||||||
return PdfUtils.bytesToWebResponse(result, pdfFile.getName() + "_overlayed.pdf");
|
return PdfUtils.bytesToWebResponse(result, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Failed to add image to PDF", e);
|
logger.error("Failed to add image to PDF", e);
|
||||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,27 @@
|
|||||||
package stirling.software.SPDF.controller;
|
package stirling.software.SPDF.controller;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.core.io.InputStreamResource;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class PdfController {
|
public class PdfController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PdfController.class);
|
private static final Logger logger = LoggerFactory.getLogger(PdfController.class);
|
||||||
|
|
||||||
|
@GetMapping("/home")
|
||||||
|
public String root(Model model) {
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/home")
|
@GetMapping("/")
|
||||||
public String root(Model model) {
|
public String home(Model model) {
|
||||||
return "redirect:/";
|
model.addAttribute("currentPage", "home");
|
||||||
}
|
return "home";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/")
|
|
||||||
public String home(Model model) {
|
|
||||||
model.addAttribute("currentPage", "home");
|
|
||||||
return "home";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package stirling.software.SPDF.controller;
|
package stirling.software.SPDF.controller;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -9,9 +8,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
@@ -25,104 +21,101 @@ import stirling.software.SPDF.utils.PdfUtils;
|
|||||||
@Controller
|
@Controller
|
||||||
public class RearrangePagesPDFController {
|
public class RearrangePagesPDFController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
||||||
|
|
||||||
@GetMapping("/pdf-organizer")
|
@GetMapping("/pdf-organizer")
|
||||||
public String pageOrganizer(Model model) {
|
public String pageOrganizer(Model model) {
|
||||||
model.addAttribute("currentPage", "pdf-organizer");
|
model.addAttribute("currentPage", "pdf-organizer");
|
||||||
return "pdf-organizer";
|
return "pdf-organizer";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/remove-pages")
|
@GetMapping("/remove-pages")
|
||||||
public String pageDeleter(Model model) {
|
public String pageDeleter(Model model) {
|
||||||
model.addAttribute("currentPage", "remove-pages");
|
model.addAttribute("currentPage", "remove-pages");
|
||||||
return "remove-pages";
|
return "remove-pages";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/remove-pages")
|
@PostMapping("/remove-pages")
|
||||||
public ResponseEntity<byte[]> deletePages(@RequestParam("fileInput") MultipartFile pdfFile,
|
public ResponseEntity<byte[]> deletePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pagesToDelete") String pagesToDelete) throws IOException {
|
||||||
@RequestParam("pagesToDelete") String pagesToDelete) throws IOException {
|
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
|
||||||
|
|
||||||
// Split the page order string into an array of page numbers or range of numbers
|
|
||||||
String[] pageOrderArr = pagesToDelete.split(",");
|
|
||||||
|
|
||||||
List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages());
|
|
||||||
|
|
||||||
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
|
|
||||||
int pageIndex = pagesToRemove.get(i);
|
|
||||||
document.removePage(pageIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_removed_pages.pdf");
|
|
||||||
|
|
||||||
}
|
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
||||||
|
|
||||||
private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) {
|
// Split the page order string into an array of page numbers or range of numbers
|
||||||
List<Integer> newPageOrder = new ArrayList<Integer>();
|
String[] pageOrderArr = pagesToDelete.split(",");
|
||||||
// loop through the page order array
|
|
||||||
for (String element : pageOrderArr) {
|
|
||||||
// check if the element contains a range of pages
|
|
||||||
if (element.contains("-")) {
|
|
||||||
// split the range into start and end page
|
|
||||||
String[] range = element.split("-");
|
|
||||||
int start = Integer.parseInt(range[0]);
|
|
||||||
int end = Integer.parseInt(range[1]);
|
|
||||||
// check if the end page is greater than total pages
|
|
||||||
if (end > totalPages) {
|
|
||||||
end = totalPages;
|
|
||||||
}
|
|
||||||
// loop through the range of pages
|
|
||||||
for (int j = start; j <= end; j++) {
|
|
||||||
// print the current index
|
|
||||||
newPageOrder.add(j - 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if the element is a single page
|
|
||||||
newPageOrder.add(Integer.parseInt(element) - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newPageOrder;
|
List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages());
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/rearrange-pages")
|
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
|
||||||
public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile,
|
int pageIndex = pagesToRemove.get(i);
|
||||||
@RequestParam("pageOrder") String pageOrder) {
|
document.removePage(pageIndex);
|
||||||
try {
|
}
|
||||||
// Load the input PDF
|
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
|
||||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
|
||||||
|
|
||||||
// Split the page order string into an array of page numbers or range of numbers
|
}
|
||||||
String[] pageOrderArr = pageOrder.split(",");
|
|
||||||
// int[] newPageOrder = new int[pageOrderArr.length];
|
|
||||||
int totalPages = document.getNumberOfPages();
|
|
||||||
|
|
||||||
List<Integer> newPageOrder = pageOrderToString(pageOrderArr, totalPages);
|
private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) {
|
||||||
|
List<Integer> newPageOrder = new ArrayList<>();
|
||||||
// Create a new list to hold the pages in the new order
|
// loop through the page order array
|
||||||
List<PDPage> newPages = new ArrayList<>();
|
for (String element : pageOrderArr) {
|
||||||
for (int i = 0; i < newPageOrder.size(); i++) {
|
// check if the element contains a range of pages
|
||||||
newPages.add(document.getPage(newPageOrder.get(i)));
|
if (element.contains("-")) {
|
||||||
}
|
// split the range into start and end page
|
||||||
|
String[] range = element.split("-");
|
||||||
|
int start = Integer.parseInt(range[0]);
|
||||||
|
int end = Integer.parseInt(range[1]);
|
||||||
|
// check if the end page is greater than total pages
|
||||||
|
if (end > totalPages) {
|
||||||
|
end = totalPages;
|
||||||
|
}
|
||||||
|
// loop through the range of pages
|
||||||
|
for (int j = start; j <= end; j++) {
|
||||||
|
// print the current index
|
||||||
|
newPageOrder.add(j - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if the element is a single page
|
||||||
|
newPageOrder.add(Integer.parseInt(element) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove all the pages from the original document
|
return newPageOrder;
|
||||||
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
|
}
|
||||||
document.removePage(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the pages in the new order
|
@PostMapping("/rearrange-pages")
|
||||||
for (PDPage page : newPages) {
|
public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) {
|
||||||
document.addPage(page);
|
try {
|
||||||
}
|
// Load the input PDF
|
||||||
|
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||||
|
|
||||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_rearranged.pdf");
|
// Split the page order string into an array of page numbers or range of numbers
|
||||||
} catch (IOException e) {
|
String[] pageOrderArr = pageOrder.split(",");
|
||||||
|
// int[] newPageOrder = new int[pageOrderArr.length];
|
||||||
|
int totalPages = document.getNumberOfPages();
|
||||||
|
|
||||||
logger.error("Failed rearranging documents", e);
|
List<Integer> newPageOrder = pageOrderToString(pageOrderArr, totalPages);
|
||||||
return null;
|
|
||||||
}
|
// Create a new list to hold the pages in the new order
|
||||||
}
|
List<PDPage> newPages = new ArrayList<>();
|
||||||
|
for (int i = 0; i < newPageOrder.size(); i++) {
|
||||||
|
newPages.add(document.getPage(newPageOrder.get(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all the pages from the original document
|
||||||
|
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
|
||||||
|
document.removePage(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the pages in the new order
|
||||||
|
for (PDPage page : newPages) {
|
||||||
|
document.addPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf");
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
logger.error("Failed rearranging documents", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
package stirling.software.SPDF.controller;
|
package stirling.software.SPDF.controller;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.ListIterator;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
@@ -27,34 +20,29 @@ import stirling.software.SPDF.utils.PdfUtils;
|
|||||||
@Controller
|
@Controller
|
||||||
public class RotationController {
|
public class RotationController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
|
||||||
|
|
||||||
@GetMapping("/rotate-pdf")
|
@GetMapping("/rotate-pdf")
|
||||||
public String rotatePdfForm(Model model) {
|
public String rotatePdfForm(Model model) {
|
||||||
model.addAttribute("currentPage", "rotate-pdf");
|
model.addAttribute("currentPage", "rotate-pdf");
|
||||||
return "rotate-pdf";
|
return "rotate-pdf";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/rotate-pdf")
|
@PostMapping("/rotate-pdf")
|
||||||
public ResponseEntity<byte[]> rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile,
|
public ResponseEntity<byte[]> rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("angle") Integer angle) throws IOException {
|
||||||
@RequestParam("angle") Integer angle) throws IOException {
|
|
||||||
|
|
||||||
// Load the PDF document
|
// Load the PDF document
|
||||||
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
||||||
|
|
||||||
// Get the list of pages in the document
|
// Get the list of pages in the document
|
||||||
PDPageTree pages = document.getPages();
|
PDPageTree pages = document.getPages();
|
||||||
|
|
||||||
// Rotate all pages by the specified angle
|
for (PDPage page : pages) {
|
||||||
Iterator<PDPage> iterPage = pages.iterator();
|
page.setRotation(page.getRotation() + angle);
|
||||||
|
}
|
||||||
|
|
||||||
while (iterPage.hasNext()) {
|
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf");
|
||||||
PDPage page = iterPage.next();
|
|
||||||
page.setRotation(page.getRotation() + angle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_rotated.pdf");
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller;
|
package stirling.software.SPDF.controller;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@@ -10,7 +9,6 @@ import java.nio.file.FileSystem;
|
|||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -33,110 +31,110 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
@Controller
|
@Controller
|
||||||
public class SplitPDFController {
|
public class SplitPDFController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
|
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
|
||||||
|
|
||||||
@GetMapping("/split-pdfs")
|
@GetMapping("/split-pdfs")
|
||||||
public String splitPdfForm(Model model) {
|
public String splitPdfForm(Model model) {
|
||||||
model.addAttribute("currentPage", "split-pdfs");
|
model.addAttribute("currentPage", "split-pdfs");
|
||||||
return "split-pdfs";
|
return "split-pdfs";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/split-pages")
|
@PostMapping("/split-pages")
|
||||||
public ResponseEntity<Resource> splitPdf(@RequestParam("fileInput") MultipartFile file,
|
public ResponseEntity<Resource> splitPdf(@RequestParam("fileInput") MultipartFile file, @RequestParam("pages") String pages) throws IOException {
|
||||||
@RequestParam("pages") String pages) throws IOException {
|
// parse user input
|
||||||
// parse user input
|
|
||||||
|
|
||||||
// open the pdf document
|
// open the pdf document
|
||||||
InputStream inputStream = file.getInputStream();
|
InputStream inputStream = file.getInputStream();
|
||||||
PDDocument document = PDDocument.load(inputStream);
|
PDDocument document = PDDocument.load(inputStream);
|
||||||
|
|
||||||
List<Integer> pageNumbers = new ArrayList<>();
|
List<Integer> pageNumbers = new ArrayList<>();
|
||||||
pages = pages.replaceAll("\\s+", ""); // remove whitespaces
|
pages = pages.replaceAll("\\s+", ""); // remove whitespaces
|
||||||
if (pages.toLowerCase().equals("all")) {
|
if (pages.toLowerCase().equals("all")) {
|
||||||
for (int i = 0; i < document.getNumberOfPages(); i++) {
|
for (int i = 0; i < document.getNumberOfPages(); i++) {
|
||||||
pageNumbers.add(i);
|
pageNumbers.add(i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(",")));
|
List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(",")));
|
||||||
if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) {
|
if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) {
|
||||||
String lastpage = String.valueOf(document.getNumberOfPages());
|
String lastpage = String.valueOf(document.getNumberOfPages());
|
||||||
pageNumbersStr.add(lastpage);
|
pageNumbersStr.add(lastpage);
|
||||||
}
|
}
|
||||||
for (String page : pageNumbersStr) {
|
for (String page : pageNumbersStr) {
|
||||||
if (page.contains("-")) {
|
if (page.contains("-")) {
|
||||||
String[] range = page.split("-");
|
String[] range = page.split("-");
|
||||||
int start = Integer.parseInt(range[0]);
|
int start = Integer.parseInt(range[0]);
|
||||||
int end = Integer.parseInt(range[1]);
|
int end = Integer.parseInt(range[1]);
|
||||||
for (int i = start; i <= end; i++) {
|
for (int i = start; i <= end; i++) {
|
||||||
pageNumbers.add(i);
|
pageNumbers.add(i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pageNumbers.add(Integer.parseInt(page));
|
pageNumbers.add(Integer.parseInt(page));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Splitting PDF into pages: {}",
|
logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
|
||||||
|
|
||||||
// split the document
|
// split the document
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
for (int pageNumber : pageNumbers) {
|
for (int pageNumber : pageNumbers) {
|
||||||
try (PDDocument splitDocument = new PDDocument()) {
|
try (PDDocument splitDocument = new PDDocument()) {
|
||||||
for (int i = currentPage; i < pageNumber; i++) {
|
for (int i = currentPage; i < pageNumber; i++) {
|
||||||
PDPage page = document.getPage(i);
|
PDPage page = document.getPage(i);
|
||||||
splitDocument.addPage(page);
|
splitDocument.addPage(page);
|
||||||
logger.debug("Adding page {} to split document", i);
|
logger.debug("Adding page {} to split document", i);
|
||||||
}
|
}
|
||||||
currentPage = pageNumber;
|
currentPage = pageNumber;
|
||||||
logger.debug("Setting current page to {}", currentPage);
|
logger.debug("Setting current page to {}", currentPage);
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
splitDocument.save(baos);
|
splitDocument.save(baos);
|
||||||
|
|
||||||
splitDocumentsBoas.add(baos);
|
splitDocumentsBoas.add(baos);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed splitting documents and saving them", e);
|
logger.error("Failed splitting documents and saving them", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// closing the original document
|
// closing the original document
|
||||||
document.close();
|
document.close();
|
||||||
|
|
||||||
// create the zip file
|
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
||||||
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);
|
|
||||||
|
|
||||||
// loop through the split documents and write them to the zip file
|
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||||
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
// loop through the split documents and write them to the zip file
|
||||||
String fileName = "split_document_" + (i + 1) + ".pdf";
|
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
||||||
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
String fileName = "split_document_" + (i + 1) + ".pdf";
|
||||||
byte[] pdf = baos.toByteArray();
|
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||||
Path pathInZipfile = zipfs.getPath(fileName);
|
byte[] pdf = baos.toByteArray();
|
||||||
try (OutputStream os = Files.newOutputStream(pathInZipfile)) {
|
|
||||||
os.write(pdf);
|
// Add PDF file to the zip
|
||||||
logger.info("Wrote split document {} to zip file", fileName);
|
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||||
} catch (Exception e) {
|
zipOut.putNextEntry(pdfEntry);
|
||||||
logger.error("Failed writing to zip", e);
|
zipOut.write(pdf);
|
||||||
throw e;
|
zipOut.closeEntry();
|
||||||
}
|
|
||||||
}
|
logger.info("Wrote split document {} to zip file", fileName);
|
||||||
zipfs.close();
|
}
|
||||||
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
} catch (Exception e) {
|
||||||
byte[] data = Files.readAllBytes(zipFile);
|
logger.error("Failed writing to zip", e);
|
||||||
ByteArrayResource resource = new ByteArrayResource(data);
|
throw e;
|
||||||
new File("split_documents.zip").delete();
|
}
|
||||||
// return the Resource in the response
|
|
||||||
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=split_documents.zip")
|
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
||||||
.contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource);
|
byte[] data = Files.readAllBytes(zipFile);
|
||||||
}
|
ByteArrayResource resource = new ByteArrayResource(data);
|
||||||
|
Files.delete(zipFile);
|
||||||
|
|
||||||
|
// return the Resource in the response
|
||||||
|
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_split.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
.contentLength(resource.contentLength()).body(resource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package stirling.software.SPDF.controller.converters;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -20,51 +23,75 @@ import stirling.software.SPDF.utils.PdfUtils;
|
|||||||
@Controller
|
@Controller
|
||||||
public class ConvertImgPDFController {
|
public class ConvertImgPDFController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
||||||
|
|
||||||
@GetMapping("/img-to-pdf")
|
@GetMapping("/img-to-pdf")
|
||||||
public String convertToPdfForm(Model model) {
|
public String convertToPdfForm(Model model) {
|
||||||
model.addAttribute("currentPage", "img-to-pdf");
|
model.addAttribute("currentPage", "img-to-pdf");
|
||||||
return "convert/img-to-pdf";
|
return "convert/img-to-pdf";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/pdf-to-img")
|
@GetMapping("/pdf-to-img")
|
||||||
public String pdfToimgForm(Model model) {
|
public String pdfToimgForm(Model model) {
|
||||||
model.addAttribute("currentPage", "pdf-to-img");
|
model.addAttribute("currentPage", "pdf-to-img");
|
||||||
return "convert/pdf-to-img";
|
return "convert/pdf-to-img";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/img-to-pdf")
|
@PostMapping("/img-to-pdf")
|
||||||
public ResponseEntity<byte[]> convertToPdf(@RequestParam("fileInput") MultipartFile file) throws IOException {
|
public ResponseEntity<byte[]> convertToPdf(@RequestParam("fileInput") MultipartFile[] file,
|
||||||
// Convert the file to PDF and get the resulting bytes
|
@RequestParam(defaultValue = "false", name = "stretchToFit") boolean stretchToFit,
|
||||||
byte[] bytes = PdfUtils.convertToPdf(file.getInputStream());
|
@RequestParam(defaultValue = "true", name = "autoRotate") boolean autoRotate) throws IOException {
|
||||||
logger.info("File {} successfully converted to pdf", file.getOriginalFilename());
|
// Convert the file to PDF and get the resulting bytes
|
||||||
|
System.out.println(stretchToFit);
|
||||||
|
byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate);
|
||||||
|
return PdfUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_coverted.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
return PdfUtils.bytesToWebResponse(bytes, file.getName() + "_coverted.pdf");
|
@PostMapping("/pdf-to-img")
|
||||||
}
|
public ResponseEntity<Resource> convertToImage(@RequestParam("fileInput") MultipartFile file, @RequestParam("imageFormat") String imageFormat,
|
||||||
|
@RequestParam("singleOrMultiple") String singleOrMultiple, @RequestParam("colorType") String colorType, @RequestParam("dpi") String dpi) throws IOException {
|
||||||
|
|
||||||
@PostMapping("/pdf-to-img")
|
byte[] pdfBytes = file.getBytes();
|
||||||
public ResponseEntity<byte[]> convertToImage(@RequestParam("fileInput") MultipartFile file,
|
ImageType colorTypeResult = ImageType.RGB;
|
||||||
@RequestParam("imageFormat") String imageFormat) throws IOException {
|
if ("greyscale".equals(colorType)) {
|
||||||
byte[] pdfBytes = file.getBytes();
|
colorTypeResult = ImageType.GRAY;
|
||||||
// returns bytes for image
|
} else if ("blackwhite".equals(colorType)) {
|
||||||
byte[] result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toLowerCase());
|
colorTypeResult = ImageType.BINARY;
|
||||||
HttpHeaders headers = new HttpHeaders();
|
}
|
||||||
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
|
// returns bytes for image
|
||||||
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
|
boolean singleImage = singleOrMultiple.equals("single");
|
||||||
ResponseEntity<byte[]> response = new ResponseEntity<>(result, headers, HttpStatus.OK);
|
byte[] result = null;
|
||||||
return response;
|
try {
|
||||||
}
|
result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toUpperCase(), colorTypeResult, singleImage, Integer.valueOf(dpi));
|
||||||
|
} catch (IOException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (singleImage) {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
|
||||||
|
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="+ file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToImages.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
.contentLength(resource.contentLength()).body(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String getMediaType(String imageFormat) {
|
private String getMediaType(String imageFormat) {
|
||||||
if (imageFormat.equalsIgnoreCase("PNG"))
|
if (imageFormat.equalsIgnoreCase("PNG"))
|
||||||
return "image/png";
|
return "image/png";
|
||||||
else if (imageFormat.equalsIgnoreCase("JPEG") || imageFormat.equalsIgnoreCase("JPG"))
|
else if (imageFormat.equalsIgnoreCase("JPEG") || imageFormat.equalsIgnoreCase("JPG"))
|
||||||
return "image/jpeg";
|
return "image/jpeg";
|
||||||
else if (imageFormat.equalsIgnoreCase("GIF"))
|
else if (imageFormat.equalsIgnoreCase("GIF"))
|
||||||
return "image/gif";
|
return "image/gif";
|
||||||
else
|
else
|
||||||
return "application/octet-stream";
|
return "application/octet-stream";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package stirling.software.SPDF.controller.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.cos.COSName;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class MetadataController {
|
||||||
|
|
||||||
|
@GetMapping("/change-metadata")
|
||||||
|
public String addWatermarkForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "change-metadata");
|
||||||
|
return "security/change-metadata";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String checkUndefined(String entry) {
|
||||||
|
// Check if the string is "undefined"
|
||||||
|
if("undefined".equals(entry)) {
|
||||||
|
// Return null if it is
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Return the original string if it's not "undefined"
|
||||||
|
return entry;
|
||||||
|
|
||||||
|
}
|
||||||
|
@PostMapping("/update-metadata")
|
||||||
|
public ResponseEntity<byte[]> metadata(@RequestParam("fileInput") MultipartFile pdfFile,
|
||||||
|
@RequestParam(value = "deleteAll", required = false, defaultValue = "false") Boolean deleteAll, @RequestParam(value = "author", required = false) String author,
|
||||||
|
@RequestParam(value = "creationDate", required = false) String creationDate, @RequestParam(value = "creator", required = false) String creator,
|
||||||
|
@RequestParam(value = "keywords", required = false) String keywords, @RequestParam(value = "modificationDate", required = false) String modificationDate,
|
||||||
|
@RequestParam(value = "producer", required = false) String producer, @RequestParam(value = "subject", required = false) String subject,
|
||||||
|
@RequestParam(value = "title", required = false) String title, @RequestParam(value = "trapped", required = false) String trapped,
|
||||||
|
@RequestParam Map<String, String> allRequestParams) throws IOException {
|
||||||
|
|
||||||
|
// Load the PDF file into a PDDocument
|
||||||
|
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
||||||
|
|
||||||
|
// Get the document information from the PDF
|
||||||
|
PDDocumentInformation info = document.getDocumentInformation();
|
||||||
|
|
||||||
|
// Check if each metadata value is "undefined" and set it to null if it is
|
||||||
|
author = checkUndefined(author);
|
||||||
|
creationDate = checkUndefined(creationDate);
|
||||||
|
creator = checkUndefined(creator);
|
||||||
|
keywords = checkUndefined(keywords);
|
||||||
|
modificationDate = checkUndefined(modificationDate);
|
||||||
|
producer = checkUndefined(producer);
|
||||||
|
subject = checkUndefined(subject);
|
||||||
|
title = checkUndefined(title);
|
||||||
|
trapped = checkUndefined(trapped);
|
||||||
|
|
||||||
|
// If the "deleteAll" flag is set, remove all metadata from the document information
|
||||||
|
if (deleteAll) {
|
||||||
|
for (String key : info.getMetadataKeys()) {
|
||||||
|
info.setCustomMetadataValue(key, null);
|
||||||
|
}
|
||||||
|
// Remove metadata from the PDF history
|
||||||
|
document.getDocumentCatalog().getCOSObject().removeItem(COSName.getPDFName("Metadata"));
|
||||||
|
document.getDocumentCatalog().getCOSObject().removeItem(COSName.getPDFName("PieceInfo"));
|
||||||
|
author = null;
|
||||||
|
creationDate = null;
|
||||||
|
creator = null;
|
||||||
|
keywords = null;
|
||||||
|
modificationDate = null;
|
||||||
|
producer = null;
|
||||||
|
subject = null;
|
||||||
|
title = null;
|
||||||
|
trapped = null;
|
||||||
|
} else {
|
||||||
|
// Iterate through the request parameters and set the metadata values
|
||||||
|
for (Entry<String, String> entry : allRequestParams.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
// Check if the key is a standard metadata key
|
||||||
|
if (!key.equalsIgnoreCase("Author") && !key.equalsIgnoreCase("CreationDate") && !key.equalsIgnoreCase("Creator") && !key.equalsIgnoreCase("Keywords")
|
||||||
|
&& !key.equalsIgnoreCase("modificationDate") && !key.equalsIgnoreCase("Producer") && !key.equalsIgnoreCase("Subject") && !key.equalsIgnoreCase("Title")
|
||||||
|
&& !key.equalsIgnoreCase("Trapped") && !key.contains("customKey") && !key.contains("customValue")) {
|
||||||
|
info.setCustomMetadataValue(key, entry.getValue());
|
||||||
|
} else if (key.contains("customKey")) {
|
||||||
|
int number = Integer.parseInt(key.replaceAll("\\D", ""));
|
||||||
|
String customKey = entry.getValue();
|
||||||
|
String customValue = allRequestParams.get("customValue" + number);
|
||||||
|
info.setCustomMetadataValue(customKey, customValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (creationDate != null && creationDate.length() > 0) {
|
||||||
|
Calendar creationDateCal = Calendar.getInstance();
|
||||||
|
try {
|
||||||
|
creationDateCal.setTime(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate));
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
info.setCreationDate(creationDateCal);
|
||||||
|
} else {
|
||||||
|
info.setCreationDate(null);
|
||||||
|
}
|
||||||
|
if (modificationDate != null && modificationDate.length() > 0) {
|
||||||
|
Calendar modificationDateCal = Calendar.getInstance();
|
||||||
|
try {
|
||||||
|
modificationDateCal.setTime(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate));
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
info.setModificationDate(modificationDateCal);
|
||||||
|
} else {
|
||||||
|
info.setModificationDate(null);
|
||||||
|
}
|
||||||
|
info.setCreator(creator);
|
||||||
|
info.setKeywords(keywords);
|
||||||
|
info.setAuthor(author);
|
||||||
|
info.setProducer(producer);
|
||||||
|
info.setSubject(subject);
|
||||||
|
info.setTitle(title);
|
||||||
|
info.setTrapped(trapped);
|
||||||
|
|
||||||
|
document.setDocumentInformation(info);
|
||||||
|
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_metadata.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,18 +1,12 @@
|
|||||||
package stirling.software.SPDF.controller.security;
|
package stirling.software.SPDF.controller.security;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
||||||
import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
|
|
||||||
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
@@ -26,67 +20,62 @@ import stirling.software.SPDF.utils.PdfUtils;
|
|||||||
@Controller
|
@Controller
|
||||||
public class PasswordController {
|
public class PasswordController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
|
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
|
||||||
|
|
||||||
@GetMapping("/add-password")
|
@GetMapping("/add-password")
|
||||||
public String addPasswordForm(Model model) {
|
public String addPasswordForm(Model model) {
|
||||||
model.addAttribute("currentPage", "add-password");
|
model.addAttribute("currentPage", "add-password");
|
||||||
return "security/add-password";
|
return "security/add-password";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/remove-password")
|
@GetMapping("/remove-password")
|
||||||
public String removePasswordForm(Model model) {
|
public String removePasswordForm(Model model) {
|
||||||
model.addAttribute("currentPage", "remove-password");
|
model.addAttribute("currentPage", "remove-password");
|
||||||
return "security/remove-password";
|
return "security/remove-password";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/change-permissions")
|
@GetMapping("/change-permissions")
|
||||||
public String permissionsForm(Model model) {
|
public String permissionsForm(Model model) {
|
||||||
model.addAttribute("currentPage", "change-permissions");
|
model.addAttribute("currentPage", "change-permissions");
|
||||||
return "security/change-permissions";
|
return "security/change-permissions";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/remove-password")
|
@PostMapping("/remove-password")
|
||||||
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput,
|
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(name = "password") String password) throws IOException {
|
||||||
@RequestParam(name = "password") String password) throws IOException {
|
PDDocument document = PDDocument.load(fileInput.getBytes(), password);
|
||||||
PDDocument document = PDDocument.load(fileInput.getBytes(), password);
|
document.setAllSecurityToBeRemoved(true);
|
||||||
document.setAllSecurityToBeRemoved(true);
|
return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_password_removed.pdf");
|
||||||
return PdfUtils.pdfDocToWebResponse(document, fileInput.getName() + "_password_removed.pdf");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/add-password")
|
@PostMapping("/add-password")
|
||||||
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput,
|
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(defaultValue = "", name = "password") String password,
|
||||||
@RequestParam(defaultValue = "", name = "password") String password,
|
@RequestParam(defaultValue = "128", name = "keyLength") int keyLength, @RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument,
|
||||||
@RequestParam(defaultValue = "128", name = "keyLength") int keyLength,
|
@RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent,
|
||||||
@RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument,
|
@RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility,
|
||||||
@RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent,
|
@RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
|
||||||
@RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility,
|
@RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations,
|
||||||
@RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm,
|
@RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, @RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
|
||||||
@RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
|
throws IOException {
|
||||||
@RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations,
|
|
||||||
@RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint,
|
|
||||||
@RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(fileInput.getBytes());
|
PDDocument document = PDDocument.load(fileInput.getBytes());
|
||||||
AccessPermission ap = new AccessPermission();
|
AccessPermission ap = new AccessPermission();
|
||||||
|
|
||||||
ap.setCanAssembleDocument(!canAssembleDocument);
|
ap.setCanAssembleDocument(!canAssembleDocument);
|
||||||
ap.setCanExtractContent(!canExtractContent);
|
ap.setCanExtractContent(!canExtractContent);
|
||||||
ap.setCanExtractForAccessibility(!canExtractForAccessibility);
|
ap.setCanExtractForAccessibility(!canExtractForAccessibility);
|
||||||
ap.setCanFillInForm(!canFillInForm);
|
ap.setCanFillInForm(!canFillInForm);
|
||||||
ap.setCanModify(!canModify);
|
ap.setCanModify(!canModify);
|
||||||
ap.setCanModifyAnnotations(!canModifyAnnotations);
|
ap.setCanModifyAnnotations(!canModifyAnnotations);
|
||||||
ap.setCanPrint(!canPrint);
|
ap.setCanPrint(!canPrint);
|
||||||
ap.setCanPrintFaithful(!canPrintFaithful);
|
ap.setCanPrintFaithful(!canPrintFaithful);
|
||||||
StandardProtectionPolicy spp = new StandardProtectionPolicy(password, password, ap);
|
StandardProtectionPolicy spp = new StandardProtectionPolicy(password, password, ap);
|
||||||
spp.setEncryptionKeyLength(keyLength);
|
spp.setEncryptionKeyLength(keyLength);
|
||||||
|
|
||||||
spp.setPermissions(ap);
|
spp.setPermissions(ap);
|
||||||
|
|
||||||
document.protect(spp);
|
document.protect(spp);
|
||||||
|
|
||||||
return PdfUtils.pdfDocToWebResponse(document, fileInput.getName() + "_passworded.pdf");
|
return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_passworded.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
package stirling.software.SPDF.controller.security;
|
package stirling.software.SPDF.controller.security;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
import org.apache.pdfbox.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.apache.pdfbox.util.Matrix;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
@@ -23,61 +25,133 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
import stirling.software.SPDF.utils.WatermarkRemover;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class WatermarkController {
|
public class WatermarkController {
|
||||||
|
|
||||||
@GetMapping("/add-watermark")
|
@GetMapping("/add-watermark")
|
||||||
public String addWatermarkForm(Model model) {
|
public String addWatermarkForm(Model model) {
|
||||||
model.addAttribute("currentPage", "add-watermark");
|
model.addAttribute("currentPage", "add-watermark");
|
||||||
return "security/add-watermark";
|
return "security/add-watermark";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/add-watermark")
|
@GetMapping("/remove-watermark")
|
||||||
public ResponseEntity<byte[]> addWatermark(@RequestParam("fileInput") MultipartFile pdfFile,
|
public String removeWatermarkForm(Model model) {
|
||||||
@RequestParam("watermarkText") String watermarkText,
|
model.addAttribute("currentPage", "remove-watermark");
|
||||||
@RequestParam(defaultValue = "30", name = "fontSize") float fontSize,
|
return "security/remove-watermark";
|
||||||
@RequestParam(defaultValue = "0", name = "rotation") float rotation,
|
}
|
||||||
@RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer,
|
|
||||||
@RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer) throws IOException {
|
@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 = "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
|
// Load the input PDF
|
||||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||||
|
|
||||||
// Create a page in the document
|
// Create a page in the document
|
||||||
for (PDPage page : document.getPages()) {
|
for (PDPage page : document.getPages()) {
|
||||||
// Get the page's content stream
|
|
||||||
PDPageContentStream contentStream = new PDPageContentStream(document, page,
|
|
||||||
PDPageContentStream.AppendMode.APPEND, true);
|
|
||||||
|
|
||||||
// Set font of watermark
|
|
||||||
PDFont font = PDType1Font.HELVETICA_BOLD;
|
// Get the page's content stream
|
||||||
contentStream.beginText();
|
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
|
||||||
contentStream.setFont(font, fontSize);
|
|
||||||
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
|
|
||||||
|
|
||||||
// Set size and location of watermark
|
// Set transparency
|
||||||
float pageWidth = page.getMediaBox().getWidth();
|
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
||||||
float pageHeight = page.getMediaBox().getHeight();
|
graphicsState.setNonStrokingAlphaConstant(opacity);
|
||||||
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
|
contentStream.setGraphicsStateParameters(graphicsState);
|
||||||
float watermarkHeight = heightSpacer + fontSize;
|
|
||||||
int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
|
// Set font of watermark
|
||||||
int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
|
PDFont font = PDType1Font.HELVETICA_BOLD;
|
||||||
|
contentStream.beginText();
|
||||||
|
contentStream.setFont(font, fontSize);
|
||||||
|
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
|
||||||
|
|
||||||
// Add the watermark text
|
// Set size and location of watermark
|
||||||
for (int i = 0; i < watermarkRows; i++) {
|
float pageWidth = page.getMediaBox().getWidth();
|
||||||
for (int j = 0; j < watermarkCols; j++) {
|
float pageHeight = page.getMediaBox().getHeight();
|
||||||
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation),
|
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
|
||||||
j * watermarkWidth, i * watermarkHeight));
|
float watermarkHeight = heightSpacer + fontSize;
|
||||||
contentStream.showTextWithPositioning(new Object[] { watermarkText });
|
int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
|
||||||
}
|
int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
|
||||||
}
|
|
||||||
|
|
||||||
contentStream.endText();
|
// Add the watermark text
|
||||||
|
for (int i = 0; i < watermarkRows; i++) {
|
||||||
|
for (int j = 0; j < watermarkCols; j++) {
|
||||||
|
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), j * watermarkWidth, i * watermarkHeight));
|
||||||
|
contentStream.showTextWithPositioning(new Object[] { watermarkText });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Close the content stream
|
contentStream.endText();
|
||||||
contentStream.close();
|
|
||||||
}
|
// Close the content stream
|
||||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_watermarked.pdf");
|
contentStream.close();
|
||||||
}
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/main/java/stirling/software/SPDF/utils/ErrorUtils.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
|
public class ErrorUtils {
|
||||||
|
|
||||||
|
public static Model exceptionToModel(Model model, Exception ex) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
ex.printStackTrace(new PrintWriter(sw));
|
||||||
|
String stackTrace = sw.toString();
|
||||||
|
|
||||||
|
model.addAttribute("errorMessage", ex.getMessage());
|
||||||
|
model.addAttribute("stackTrace", stackTrace);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModelAndView exceptionToModelView(Model model, Exception ex) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
ex.printStackTrace(new PrintWriter(sw));
|
||||||
|
String stackTrace = sw.toString();
|
||||||
|
|
||||||
|
ModelAndView modelAndView = new ModelAndView();
|
||||||
|
modelAndView.addObject("errorMessage", ex.getMessage());
|
||||||
|
modelAndView.addObject("stackTrace", stackTrace);
|
||||||
|
return modelAndView;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
|
import java.awt.Graphics;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -7,12 +8,14 @@ import java.io.File;
|
|||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Iterator;
|
import java.nio.file.Files;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import javax.imageio.IIOImage;
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageWriter;
|
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@@ -26,143 +29,226 @@ import org.springframework.http.HttpHeaders;
|
|||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import com.spire.pdf.PdfDocument;
|
import com.itextpdf.text.Document;
|
||||||
|
import com.itextpdf.text.DocumentException;
|
||||||
|
import com.itextpdf.text.pdf.PdfWriter;
|
||||||
|
|
||||||
public class PdfUtils {
|
public class PdfUtils {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
|
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
|
||||||
|
|
||||||
public static byte[] convertToPdf(InputStream imageStream) throws IOException {
|
public static byte[] imageToPdf(MultipartFile[] files, boolean stretchToFit, boolean autoRotate) throws IOException {
|
||||||
|
try (PDDocument doc = new PDDocument()) {
|
||||||
|
for (MultipartFile file : files) {
|
||||||
|
// Create a temporary file for the image
|
||||||
|
File imageFile = Files.createTempFile("image", ".jpg").toFile();
|
||||||
|
|
||||||
// Create a File object for the image
|
try (FileOutputStream fos = new FileOutputStream(imageFile); InputStream input = file.getInputStream()) {
|
||||||
File imageFile = new File("image.jpg");
|
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 (FileOutputStream fos = new FileOutputStream(imageFile); InputStream input = imageStream) {
|
// Create a new PDF page
|
||||||
byte[] buffer = new byte[1024];
|
PDPage page = new PDPage();
|
||||||
int len;
|
doc.addPage(page);
|
||||||
// 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 (PDDocument doc = new PDDocument()) {
|
// Create an image object from the image file
|
||||||
// Create a new PDF page
|
PDImageXObject image = PDImageXObject.createFromFileByContent(imageFile, doc);
|
||||||
PDPage page = new PDPage();
|
|
||||||
doc.addPage(page);
|
|
||||||
|
|
||||||
// Create an image object from the image file
|
float pageWidth = page.getMediaBox().getWidth();
|
||||||
PDImageXObject image = PDImageXObject.createFromFileByContent(imageFile, doc);
|
float pageHeight = page.getMediaBox().getHeight();
|
||||||
|
|
||||||
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
|
if (autoRotate && ((image.getWidth() > image.getHeight() && pageHeight > pageWidth) || (image.getWidth() < image.getHeight() && pageWidth > pageHeight))) {
|
||||||
// Draw the image onto the page
|
// Rotate the page 90 degrees if the image better fits the page in landscape orientation
|
||||||
contentStream.drawImage(image, 0, 0);
|
page.setRotation(90);
|
||||||
logger.info("Image successfully added to PDF");
|
pageWidth = page.getMediaBox().getHeight();
|
||||||
} catch (IOException e) {
|
pageHeight = page.getMediaBox().getWidth();
|
||||||
logger.error("Error adding image to PDF", e);
|
}
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a ByteArrayOutputStream to save the PDF to
|
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
if (stretchToFit) {
|
||||||
doc.save(byteArrayOutputStream);
|
if (page.getRotation() == 0 || page.getRotation() == 180) {
|
||||||
logger.info("PDF successfully saved to byte array");
|
// Stretch the image to fit the whole page
|
||||||
return byteArrayOutputStream.toByteArray();
|
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;
|
||||||
|
|
||||||
public static byte[] convertFromPdf(byte[] inputStream, String imageType) throws IOException {
|
// Determine the scale factor to fit the image onto the page
|
||||||
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
|
float scaleFactor = 1.0f;
|
||||||
// Create a PDFRenderer to convert the PDF to an image
|
if (imageAspectRatio > pageAspectRatio) {
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
// Image is wider than the page, scale to fit the width
|
||||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(0, 300, ImageType.RGB);
|
scaleFactor = pageWidth / image.getWidth();
|
||||||
|
} else {
|
||||||
|
// Image is taller than the page, scale to fit the height
|
||||||
|
scaleFactor = pageHeight / image.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
// Get an ImageWriter for the specified image type
|
// Calculate the position of the image on the page
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(imageType);
|
float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2;
|
||||||
ImageWriter writer = writers.next();
|
float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2;
|
||||||
|
|
||||||
// Create a ByteArrayOutputStream to save the image to
|
// Draw the image onto the page
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
if (page.getRotation() == 0 || page.getRotation() == 180) {
|
||||||
try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) {
|
contentStream.drawImage(image, xPos, yPos, image.getWidth() * scaleFactor, image.getHeight() * scaleFactor);
|
||||||
writer.setOutput(ios);
|
} else {
|
||||||
// Write the image to the output stream
|
// Adjust the width and height of the page when rotated
|
||||||
writer.write(new IIOImage(bim, null, null));
|
contentStream.drawImage(image, yPos, xPos, image.getHeight() * scaleFactor, image.getWidth() * scaleFactor);
|
||||||
// Log that the image was successfully written to the byte array
|
}
|
||||||
logger.info("Image successfully written to byte array");
|
logger.info("Image successfully added to PDF, maintaining aspect ratio");
|
||||||
}
|
}
|
||||||
return baos.toByteArray();
|
} catch (IOException e) {
|
||||||
} catch (IOException e) {
|
logger.error("Error adding image to PDF", e);
|
||||||
// Log an error message if there is an issue converting the PDF to an image
|
throw e;
|
||||||
logger.error("Error converting PDF to image", e);
|
}
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] overlayImage(byte[] pdfBytes, byte[] imageBytes, float x, float y) throws IOException {
|
// Delete the temporary file
|
||||||
|
imageFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) {
|
// Create a ByteArrayOutputStream to save the PDF to
|
||||||
// Get the first page of the PDF
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
PDPage page = document.getPage(0);
|
doc.save(byteArrayOutputStream);
|
||||||
try (PDPageContentStream contentStream = new PDPageContentStream(document, page,
|
logger.info("PDF successfully saved to byte array");
|
||||||
PDPageContentStream.AppendMode.APPEND, true)) {
|
|
||||||
// Create an image object from the image bytes
|
|
||||||
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "");
|
|
||||||
// Draw the image onto the page at the specified x and y coordinates
|
|
||||||
contentStream.drawImage(image, x, y);
|
|
||||||
logger.info("Image successfully overlayed onto PDF");
|
|
||||||
}
|
|
||||||
// Create a ByteArrayOutputStream to save the PDF to
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
document.save(baos);
|
|
||||||
logger.info("PDF successfully saved to byte array");
|
|
||||||
return baos.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Log an error message if there is an issue overlaying the image onto the PDF
|
|
||||||
logger.error("Error overlaying image onto PDF", e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> pdfDocToWebResponse(PdfDocument document, String docName) throws IOException {
|
return byteArrayOutputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
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))) {
|
||||||
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
|
int pageCount = document.getNumberOfPages();
|
||||||
|
List<BufferedImage> images = new ArrayList<>();
|
||||||
|
|
||||||
// Open Byte Array and save document to it
|
// Create images of all pages
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
for (int i = 0; i < pageCount; i++) {
|
||||||
document.save(baos);
|
images.add(pdfRenderer.renderImageWithDPI(i, 300, colorType));
|
||||||
// Close the document
|
}
|
||||||
document.close();
|
|
||||||
|
if (singleImage) {
|
||||||
|
// Combine all images into a single big image
|
||||||
|
BufferedImage combined = new BufferedImage(images.get(0).getWidth(), images.get(0).getHeight() * pageCount, BufferedImage.TYPE_INT_RGB);
|
||||||
|
Graphics g = combined.getGraphics();
|
||||||
|
for (int i = 0; i < images.size(); i++) {
|
||||||
|
g.drawImage(images.get(i), 0, i * images.get(0).getHeight(), null);
|
||||||
|
}
|
||||||
|
images = Arrays.asList(combined);
|
||||||
|
}
|
||||||
|
|
||||||
return PdfUtils.boasToWebResponse(baos, docName);
|
// Create a ByteArrayOutputStream to save the image(s) to
|
||||||
}
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
if (singleImage) {
|
||||||
|
// Write the image to the output stream
|
||||||
|
ImageIO.write(images.get(0), imageType, baos);
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName)
|
// Log that the image was successfully written to the byte array
|
||||||
throws IOException {
|
logger.info("Image successfully written to byte array");
|
||||||
return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName);
|
} else {
|
||||||
|
// Zip the images and return as byte array
|
||||||
|
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
|
||||||
|
for (int i = 0; i < images.size(); i++) {
|
||||||
|
BufferedImage image = images.get(i);
|
||||||
|
try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) {
|
||||||
|
ImageIO.write(image, imageType, baosImage);
|
||||||
|
|
||||||
}
|
// Add the image to the zip file
|
||||||
|
zos.putNextEntry(new ZipEntry(String.format("page_%d.%s", i + 1, imageType.toLowerCase())));
|
||||||
|
zos.write(baosImage.toByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Log that the images were successfully written to the byte array
|
||||||
|
logger.info("Images successfully written to byte array as a zip");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return baos.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Log an error message if there is an issue converting the PDF to an image
|
||||||
|
logger.error("Error converting PDF to image", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) throws IOException {
|
public static byte[] overlayImage(byte[] pdfBytes, byte[] imageBytes, float x, float y) throws IOException {
|
||||||
|
|
||||||
// Return the PDF as a response
|
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
// Get the first page of the PDF
|
||||||
headers.setContentType(MediaType.APPLICATION_PDF);
|
PDPage page = document.getPage(0);
|
||||||
headers.setContentLength(bytes.length);
|
try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true)) {
|
||||||
headers.setContentDispositionFormData("attachment", docName);
|
// Create an image object from the image bytes
|
||||||
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
|
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "");
|
||||||
}
|
// Draw the image onto the page at the specified x and y coordinates
|
||||||
|
contentStream.drawImage(image, x, y);
|
||||||
|
logger.info("Image successfully overlayed onto PDF");
|
||||||
|
}
|
||||||
|
// Create a ByteArrayOutputStream to save the PDF to
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
document.save(baos);
|
||||||
|
logger.info("PDF successfully saved to byte array");
|
||||||
|
return baos.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Log an error message if there is an issue overlaying the image onto the PDF
|
||||||
|
logger.error("Error overlaying image onto PDF", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResponseEntity<byte[]> iTextDocToWebResponse(Document document, String docName) throws IOException, DocumentException {
|
||||||
|
// Close the document
|
||||||
|
document.close();
|
||||||
|
|
||||||
|
// Open Byte Array and save document to it
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
PdfWriter.getInstance(document, baos).close();
|
||||||
|
|
||||||
|
|
||||||
|
return PdfUtils.boasToWebResponse(baos, docName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName) throws IOException {
|
||||||
|
|
||||||
|
// Open Byte Array and save document to it
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
document.save(baos);
|
||||||
|
// Close the document
|
||||||
|
document.close();
|
||||||
|
|
||||||
|
return PdfUtils.boasToWebResponse(baos, docName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException {
|
||||||
|
return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) throws IOException {
|
||||||
|
|
||||||
|
// Return the PDF as a response
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_PDF);
|
||||||
|
headers.setContentLength(bytes.length);
|
||||||
|
headers.setContentDispositionFormData("attachment", docName);
|
||||||
|
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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,11 +1,24 @@
|
|||||||
spring.http.multipart.max-file-size=1GB
|
spring.http.multipart.max-file-size=2GB
|
||||||
spring.http.multipart.max-request-size=1GB
|
spring.http.multipart.max-request-size=2GB
|
||||||
|
|
||||||
multipart.enabled=true
|
multipart.enabled=true
|
||||||
multipart.max-file-size=1000MB
|
multipart.max-file-size=2000MB
|
||||||
multipart.max-request-size=1000MB
|
multipart.max-request-size=2000MB
|
||||||
|
|
||||||
spring.servlet.multipart.max-file-size=1000MB
|
spring.servlet.multipart.max-file-size=2000MB
|
||||||
spring.servlet.multipart.max-request-size=1000MB
|
spring.servlet.multipart.max-request-size=2000MB
|
||||||
|
|
||||||
server.forward-headers-strategy=NATIVE
|
server.forward-headers-strategy=NATIVE
|
||||||
|
|
||||||
|
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.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)
|
||||||
|
|||||||
302
src/main/resources/messages_ar_AR.properties
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
|
||||||
|
# Translated by Google Translate #
|
||||||
|
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
|
||||||
|
|
||||||
|
###########
|
||||||
|
# Generic #
|
||||||
|
###########
|
||||||
|
# the direction that the language is written (ltr=left to right, rtl=right to left)
|
||||||
|
language.direction=rtl
|
||||||
|
|
||||||
|
pdfPrompt=اختر PDF
|
||||||
|
multiPdfPrompt=اختر ملفات PDF (2+)
|
||||||
|
multiPdfDropPrompt=حدد (أو اسحب وأفلت) جميع ملفات PDF التي تحتاجها
|
||||||
|
imgPrompt=اختر صورة
|
||||||
|
genericSubmit=إرسال
|
||||||
|
processTimeWarning=تحذير: يمكن أن تستغرق هذه العملية ما يصل إلى دقيقة حسب حجم الملف
|
||||||
|
pageOrderPrompt=ترتيب الصفحات (أدخل قائمة بأرقام الصفحات مفصولة بفواصل):
|
||||||
|
goToPage=اذهب
|
||||||
|
true=\u0635\u062D\u064A\u062D
|
||||||
|
false=\u062E\u0637\u0623
|
||||||
|
unknown=\u063A\u064A\u0631 \u0645\u0639\u0631\u0648\u0641
|
||||||
|
save=\u062D\u0641\u0638
|
||||||
|
close=\u0625\u063A\u0644\u0627\u0642
|
||||||
|
|
||||||
|
#############
|
||||||
|
# HOME-PAGE #
|
||||||
|
#############
|
||||||
|
home.desc=متجرك الشامل المستضاف محليًا لجميع احتياجات PDF الخاصة بك.
|
||||||
|
|
||||||
|
navbar.convert=تحويل
|
||||||
|
navbar.security=الأمان
|
||||||
|
navbar.other=أخرى
|
||||||
|
navbar.darkmode=الوضع الداكن
|
||||||
|
|
||||||
|
home.merge.title=دمج ملفات PDF
|
||||||
|
home.merge.desc=دمج ملفات PDF متعددة في ملف واحد بسهولة.
|
||||||
|
|
||||||
|
home.split.title=انقسام ملفات PDF
|
||||||
|
home.split.desc=تقسيم ملفات PDF إلى مستندات متعددة
|
||||||
|
|
||||||
|
home.rotate.title=تدوير ملفات PDF
|
||||||
|
home.rotate.desc=قم بتدوير ملفات PDF الخاصة بك بسهولة.
|
||||||
|
|
||||||
|
home.imageToPdf.title=صورة إلى PDF
|
||||||
|
home.imageToPdf.desc=تحويل الصور (PNG ، JPEG ، GIF) إلى PDF.
|
||||||
|
|
||||||
|
home.pdfToImage.title=تحويل PDF إلى صورة
|
||||||
|
home.pdfToImage.desc=تحويل ملف PDF إلى صورة. (PNG ، JPEG ، GIF)
|
||||||
|
|
||||||
|
home.pdfOrganiser.title=منظم PDF
|
||||||
|
home.pdfOrganiser.desc=إزالة / إعادة ترتيب الصفحات بأي ترتيب
|
||||||
|
|
||||||
|
home.addImage.title=إضافة صورة إلى ملف PDF
|
||||||
|
home.addImage.desc=إضافة صورة إلى موقع معين في PDF (العمل قيد التقدم)
|
||||||
|
|
||||||
|
home.watermark.title=إضافة علامة مائية
|
||||||
|
home.watermark.desc=أضف علامة مائية مخصصة إلى مستند PDF الخاص بك.
|
||||||
|
|
||||||
|
home.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 الخاص بك
|
||||||
|
|
||||||
|
home.removePages.title=إزالة الصفحات
|
||||||
|
home.removePages.desc=حذف الصفحات غير المرغوب فيها من مستند PDF الخاص بك.
|
||||||
|
|
||||||
|
home.addPassword.title=إضافة كلمة مرور
|
||||||
|
home.addPassword.desc=تشفير مستند PDF الخاص بك بكلمة مرور.
|
||||||
|
|
||||||
|
home.removePassword.title=إزالة كلمة المرور
|
||||||
|
home.removePassword.desc=إزالة الحماية بكلمة مرور من مستند PDF الخاص بك.
|
||||||
|
|
||||||
|
home.compressPdfs.title=ضغط ملفات PDF
|
||||||
|
home.compressPdfs.desc=ضغط ملفات PDF لتقليل حجم الملف.
|
||||||
|
|
||||||
|
home.changeMetadata.title=\u062A\u063A\u064A\u064A\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0648\u0635\u0641\u064A\u0629
|
||||||
|
home.changeMetadata.desc=\u062A\u063A\u064A\u064A\u0631 / \u0625\u0632\u0627\u0644\u0629 / \u0625\u0636\u0627\u0641\u0629 \u0628\u064A\u0627\u0646\u0627\u062A \u0623\u0648\u0644\u064A\u0629 \u0645\u0646 \u0645\u0633\u062A\u0646\u062F PDF
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
#Add image
|
||||||
|
addImage.title=إضافة صورة
|
||||||
|
addImage.header=إضافة صورة إلى PDF (العمل قيد التقدم)
|
||||||
|
addImage.submit=إضافة صورة
|
||||||
|
|
||||||
|
#compress
|
||||||
|
compress.title=ضغط
|
||||||
|
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=ضغط
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
merge.title=دمج
|
||||||
|
merge.header=دمج ملفات PDF متعددة (2+)
|
||||||
|
merge.submit=دمج
|
||||||
|
|
||||||
|
#pdfOrganiser
|
||||||
|
pdfOrganiser.title=منظم الصفحة
|
||||||
|
pdfOrganiser.header=منظم صفحات PDF
|
||||||
|
pdfOrganiser.submit=إعادة ترتيب الصفحات
|
||||||
|
|
||||||
|
|
||||||
|
#pageRemover
|
||||||
|
pageRemover.title=مزيل الصفحة
|
||||||
|
pageRemover.header=مزيل صفحة PDF
|
||||||
|
pageRemover.pagesToDelete=الصفحات المراد حذفها (أدخل قائمة بأرقام الصفحات مفصولة بفواصل):
|
||||||
|
pageRemover.submit=حذف الصفحات
|
||||||
|
|
||||||
|
#rotate
|
||||||
|
rotate.title=تدوير PDF
|
||||||
|
rotate.header=تدوير PDF
|
||||||
|
rotate.selectAngle=حدد زاوية الدوران (بمضاعفات 90 درجة):
|
||||||
|
rotate.submit=استدارة
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
split.title=انقسام PDF
|
||||||
|
split.header=تقسيم PDF
|
||||||
|
split.desc.1=الأرقام التي تحددها هي رقم الصفحة التي تريد تقسيمها
|
||||||
|
split.desc.2=على هذا النحو ، سيؤدي تحديد 1،3،7-8 إلى تقسيم مستند من 10 صفحات إلى 6 PDFS منفصلة مع:
|
||||||
|
split.desc.3=المستند رقم 1: الصفحة 1
|
||||||
|
split.desc.4=المستند رقم 2: الصفحتان 2 و 3
|
||||||
|
split.desc.5=المستند رقم 3: الصفحة 4 و 5 و 6
|
||||||
|
split.desc.6=المستند رقم 4: الصفحة 7
|
||||||
|
split.desc.7=المستند رقم 5: الصفحة 8
|
||||||
|
split.desc.8=المستند رقم 6: الصفحتان 9 و 10
|
||||||
|
split.splitPages=أدخل الصفحات المراد تقسيمها:
|
||||||
|
split.submit=Split
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
imageToPDF.title=صورة إلى PDF
|
||||||
|
imageToPDF.header=صورة إلى PDF
|
||||||
|
imageToPDF.submit=تحول
|
||||||
|
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.submit=تحول
|
||||||
|
|
||||||
|
#addPassword
|
||||||
|
addPassword.title=إضافة كلمة مرور
|
||||||
|
addPassword.header=إضافة كلمة مرور (تشفير)
|
||||||
|
addPassword.selectText.1=حدد ملف PDF للتشفير
|
||||||
|
addPassword.selectText.2=كلمة المرور
|
||||||
|
addPassword.selectText.3=طول مفتاح التشفير
|
||||||
|
addPassword.selectText.4=القيم الأعلى تكون أقوى ، لكن القيم الأقل لها توافق أفضل.
|
||||||
|
addPassword.selectText.5=أذونات للتعيين
|
||||||
|
addPassword.selectText.6=منع تجميع المستند
|
||||||
|
addPassword.selectText.7=منع استخراج المحتوى
|
||||||
|
addPassword.selectText.8=منع الاستخراج للوصول
|
||||||
|
addPassword.selectText.9=منع ملء النموذج
|
||||||
|
addPassword.selectText.10=منع التعديل
|
||||||
|
addPassword.selectText.11=منع تعديل التعليقات التوضيحية
|
||||||
|
addPassword.selectText.12=منع الطباعة
|
||||||
|
addPassword.selectText.13=منع طباعة تنسيقات مختلفة
|
||||||
|
addPassword.submit=تشفير
|
||||||
|
|
||||||
|
#watermark
|
||||||
|
watermark.title=إضافة علامة مائية
|
||||||
|
watermark.header=إضافة علامة مائية
|
||||||
|
watermark.selectText.1=حدد PDF لإضافة العلامة المائية إلى:
|
||||||
|
watermark.selectText.2=نص العلامة المائية:
|
||||||
|
watermark.selectText.3=حجم الخط:
|
||||||
|
watermark.selectText.4=دوران (0-360):
|
||||||
|
watermark.selectText.5=widthSpacer (مسافة بين كل علامة مائية أفقيًا):
|
||||||
|
watermark.selectText.6=heightSpacer (مسافة بين كل علامة مائية عموديًا):
|
||||||
|
watermark.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
|
||||||
|
|
||||||
|
#Change permissions
|
||||||
|
permissions.title=تغيير الأذونات
|
||||||
|
permissions.header=تغيير الأذونات
|
||||||
|
permissions.warning=تحذير من أن تكون هذه الأذونات غير قابلة للتغيير ، يوصى بتعيينها بكلمة مرور عبر صفحة إضافة كلمة المرور
|
||||||
|
permissions.selectText.1=حدد ملف PDF لتغيير الأذونات
|
||||||
|
permissions.selectText.2=أذونات لتعيينها
|
||||||
|
permissions.selectText.3=منع تجميع المستند
|
||||||
|
permissions.selectText.4=منع استخراج المحتوى
|
||||||
|
permissions.selectText.5=منع الاستخراج للوصول
|
||||||
|
permissions.selectText.6=منع ملء النموذج
|
||||||
|
permissions.selectText.7=منع التعديل
|
||||||
|
permissions.selectText.8=منع تعديل التعليق التوضيحي
|
||||||
|
permissions.selectText.9=منع الطباعة
|
||||||
|
permissions.selectText.10=منع طباعة التنسيقات المختلفة
|
||||||
|
permissions.submit=تغيير
|
||||||
|
|
||||||
|
#remove password
|
||||||
|
removePassword.title=إزالة كلمة المرور
|
||||||
|
removePassword.header=إزالة كلمة المرور (فك التشفير)
|
||||||
|
removePassword.selectText.1=حدد PDF لفك التشفير
|
||||||
|
removePassword.selectText.2=كلمة المرور
|
||||||
|
removePassword.submit=إزالة
|
||||||
|
|
||||||
|
changeMetadata.title=\u062A\u063A\u064A\u064A\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0648\u0635\u0641\u064A\u0629
|
||||||
|
changeMetadata.header=\u062A\u063A\u064A\u064A\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0648\u0635\u0641\u064A\u0629
|
||||||
|
changeMetadata.selectText.1=\u064A\u0631\u062C\u0649 \u062A\u0639\u062F\u064A\u0644 \u0627\u0644\u0645\u062A\u063A\u064A\u0631\u0627\u062A \u0627\u0644\u062A\u064A \u062A\u0631\u063A\u0628 \u0641\u064A \u062A\u063A\u064A\u064A\u0631\u0647\u0627
|
||||||
|
changeMetadata.selectText.2=\u062D\u0630\u0641 \u0643\u0644 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0623\u0648\u0644\u064A\u0629
|
||||||
|
changeMetadata.selectText.3=\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0623\u0648\u0644\u064A\u0629 \u0627\u0644\u0645\u062E\u0635\u0635\u0629:
|
||||||
|
changeMetadata.author=\u0627\u0644\u0645\u0624\u0644\u0641:
|
||||||
|
changeMetadata.creationDate=\u062A\u0627\u0631\u064A\u062E \u0627\u0644\u0625\u0646\u0634\u0627\u0621 (yyyy / MM / dd HH: mm: ss):
|
||||||
|
changeMetadata.creator=\u0627\u0644\u0645\u0646\u0634\u0626:
|
||||||
|
changeMetadata.keywords=\u0627\u0644\u0643\u0644\u0645\u0627\u062A \u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629:
|
||||||
|
changeMetadata.modDate=\u062A\u0627\u0631\u064A\u062E \u0627\u0644\u062A\u0639\u062F\u064A\u0644 (yyyy / MM / dd HH: mm: ss):
|
||||||
|
changeMetadata.producer=\u0627\u0644\u0645\u0646\u062A\u062C:
|
||||||
|
changeMetadata.subject=\u0627\u0644\u0645\u0648\u0636\u0648\u0639:
|
||||||
|
changeMetadata.title=\u0627\u0644\u0639\u0646\u0648\u0627\u0646:
|
||||||
|
changeMetadata.trapped=\u0645\u062D\u0627\u0635\u0631:
|
||||||
|
changeMetadata.selectText.4=\u0628\u064A\u0627\u0646\u0627\u062A \u0648\u0635\u0641\u064A\u0629 \u0623\u062E\u0631\u0649:
|
||||||
|
changeMetadata.selectText.5=\u0625\u0636\u0627\u0641\u0629 \u0625\u062F\u062E\u0627\u0644 \u0628\u064A\u0627\u0646\u0627\u062A \u0623\u0648\u0644\u064A\u0629 \u0645\u062E\u0635\u0635
|
||||||
|
changeMetadata.submit=\u062A\u063A\u064A\u064A\u0631
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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,6 +1,9 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
|
# the direction that the language is written (ltr=left to right, rtl=right to left)
|
||||||
|
language.direction=ltr
|
||||||
|
|
||||||
pdfPrompt=PDF auswählen
|
pdfPrompt=PDF auswählen
|
||||||
multiPdfPrompt=PDFs auswählen(2+)
|
multiPdfPrompt=PDFs auswählen(2+)
|
||||||
multiPdfDropPrompt=Wählen Sie alle gewünschten PDFs aus (oder ziehen Sie sie per Drag & Drop hierhin)
|
multiPdfDropPrompt=Wählen Sie alle gewünschten PDFs aus (oder ziehen Sie sie per Drag & Drop hierhin)
|
||||||
@@ -9,6 +12,12 @@ genericSubmit=Einreichen
|
|||||||
processTimeWarning=Achtung: Abhängig von der Dateigröße kann dieser Prozess bis zu einer Minute dauern
|
processTimeWarning=Achtung: Abhängig von der Dateigröße kann dieser Prozess bis zu einer Minute dauern
|
||||||
pageOrderPrompt=Seitenreihenfolge (Geben Sie eine durch Komma getrennte Liste von Seitenzahlen ein):
|
pageOrderPrompt=Seitenreihenfolge (Geben Sie eine durch Komma getrennte Liste von Seitenzahlen ein):
|
||||||
goToPage=Los
|
goToPage=Los
|
||||||
|
true=Wahr
|
||||||
|
false=Falsch
|
||||||
|
unknown=Unbekannt
|
||||||
|
save=Speichern
|
||||||
|
close=Schließen
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
#############
|
#############
|
||||||
@@ -43,6 +52,9 @@ home.addImage.desc=Fügt ein Bild an eine bestimmte Stelle im PDF ein (Work in p
|
|||||||
home.watermark.title=Wasserzeichen hinzufügen
|
home.watermark.title=Wasserzeichen hinzufügen
|
||||||
home.watermark.desc=Fügen Sie ein eigenes Wasserzeichen zu Ihrem PDF hinzu.
|
home.watermark.desc=Fügen Sie ein eigenes Wasserzeichen zu Ihrem PDF hinzu.
|
||||||
|
|
||||||
|
home.remove-watermark.title=Wasserzeichen entfernen
|
||||||
|
home.remove-watermark.desc=Wasserzeichen aus Ihrem PDF-Dokument entfernen.
|
||||||
|
|
||||||
home.permissions.title=Berechtigungen ändern
|
home.permissions.title=Berechtigungen ändern
|
||||||
home.permissions.desc=Die Berechtigungen für Ihr PDF-Dokument verändern.
|
home.permissions.desc=Die Berechtigungen für Ihr PDF-Dokument verändern.
|
||||||
|
|
||||||
@@ -58,6 +70,61 @@ home.removePassword.desc=Den Passwortschutz eines PDFs entfernen.
|
|||||||
home.compressPdfs.title=PDF komprimieren
|
home.compressPdfs.title=PDF komprimieren
|
||||||
home.compressPdfs.desc=PDF komprimieren um die Dateigröße zu reduzieren.
|
home.compressPdfs.desc=PDF komprimieren um die Dateigröße zu reduzieren.
|
||||||
|
|
||||||
|
home.changeMetadata.title=Metadaten ändern
|
||||||
|
home.changeMetadata.desc=Ändern/Entfernen/Hinzufügen von Metadaten aus einem PDF-Dokument
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#Add image
|
#Add image
|
||||||
@@ -68,7 +135,14 @@ addImage.submit=Bild hinzufügen
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Komprimieren
|
compress.title=Komprimieren
|
||||||
compress.header=PDF 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
|
compress.submit=Komprimieren
|
||||||
|
|
||||||
|
|
||||||
@@ -80,7 +154,6 @@ merge.submit=Zusammenführen
|
|||||||
#pdfOrganiser
|
#pdfOrganiser
|
||||||
pdfOrganiser.title=Seiten anordnen
|
pdfOrganiser.title=Seiten anordnen
|
||||||
pdfOrganiser.header=PDF Seitenorganisation
|
pdfOrganiser.header=PDF Seitenorganisation
|
||||||
#pdfOrganiser.pagesToOrganize=Seitenanordnung (geben Sie eine Kommagetrennte Liste der Seitenzahlen an): # may have forgotten to add this as a translation option?
|
|
||||||
pdfOrganiser.submit=Seiten anordnen
|
pdfOrganiser.submit=Seiten anordnen
|
||||||
|
|
||||||
|
|
||||||
@@ -118,11 +191,23 @@ split.submit=Aufteilen
|
|||||||
imageToPDF.title=Bild zu PDF
|
imageToPDF.title=Bild zu PDF
|
||||||
imageToPDF.header=Bild zu PDF
|
imageToPDF.header=Bild zu PDF
|
||||||
imageToPDF.submit=Umwandeln
|
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
|
||||||
pdfToImage.title=PDF zu Bild
|
pdfToImage.title=PDF zu Bild
|
||||||
pdfToImage.header=PDF zu Bild
|
pdfToImage.header=PDF zu Bild
|
||||||
pdfToImage.selectText=Bildformat
|
pdfToImage.selectText=Bildformat
|
||||||
|
pdfToImage.singleOrMultiple=Bildergebnistyp
|
||||||
|
pdfToImage.single=Einzelnes großes Bild
|
||||||
|
pdfToImage.multi=Mehrere Bilder
|
||||||
|
pdfToImage.colorType=Farbtyp
|
||||||
|
pdfToImage.color=Farbe
|
||||||
|
pdfToImage.grey=Graustufen
|
||||||
|
pdfToImage.blackwhite=Schwarzweiß (Datenverlust möglich!)
|
||||||
pdfToImage.submit=Umwandeln
|
pdfToImage.submit=Umwandeln
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
@@ -152,8 +237,15 @@ watermark.selectText.3=Schriftgröße:
|
|||||||
watermark.selectText.4=Drehung (0-360):
|
watermark.selectText.4=Drehung (0-360):
|
||||||
watermark.selectText.5=breiteSpacer (horizontaler Abstand zwischen den einzelnen Wasserzeichen):
|
watermark.selectText.5=breiteSpacer (horizontaler Abstand zwischen den einzelnen Wasserzeichen):
|
||||||
watermark.selectText.6=höheSpacer (vertikaler 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
|
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.2=Wasserzeichentext:
|
||||||
|
remove-watermark.submit=Wasserzeichen entfernen
|
||||||
|
|
||||||
#Change permissions
|
#Change permissions
|
||||||
permissions.title=Berechtigungen ändern
|
permissions.title=Berechtigungen ändern
|
||||||
@@ -179,14 +271,36 @@ removePassword.selectText.2=Passwort
|
|||||||
removePassword.submit=Entfernen
|
removePassword.submit=Entfernen
|
||||||
|
|
||||||
|
|
||||||
|
changeMetadata.title=Metadaten ändern
|
||||||
|
changeMetadata.header=Metadaten ändern
|
||||||
|
changeMetadata.selectText.1=Bitte bearbeiten Sie die Variablen, die Sie ändern möchten
|
||||||
|
changeMetadata.selectText.2=Alle Metadaten löschen
|
||||||
|
changeMetadata.selectText.3=Benutzerdefinierte Metadaten anzeigen:
|
||||||
|
changeMetadata.author=Autor:
|
||||||
|
changeMetadata.creationDate=Erstellungsdatum (jjjj/MM/tt HH:mm:ss):
|
||||||
|
changeMetadata.creator=Ersteller:
|
||||||
|
changeMetadata.keywords=Schlüsselwörter:
|
||||||
|
changeMetadata.modDate=Änderungsdatum (JJJJ/MM/TT HH:mm:ss):
|
||||||
|
changeMetadata.producer=Produzent:
|
||||||
|
changeMetadata.subject=Betreff:
|
||||||
|
changeMetadata.title=Titel:
|
||||||
|
changeMetadata.trapped=Gefangen:
|
||||||
|
changeMetadata.selectText.4=Andere Metadaten:
|
||||||
|
changeMetadata.selectText.5=Benutzerdefinierten Metadateneintrag hinzufügen
|
||||||
|
changeMetadata.submit=Ändern
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
xlsToPdf.title=Excel in PDF
|
||||||
|
xlsToPdf.header=Excel in PDF
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
pdfPrompt=Choose PDF
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
multiPdfPrompt=Choose PDFs (2+)
|
language.direction=ltr
|
||||||
|
|
||||||
|
pdfPrompt=Select PDF(s)
|
||||||
|
multiPdfPrompt=Select PDFs (2+)
|
||||||
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
|
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
|
||||||
imgPrompt=Choose Image
|
imgPrompt=Select Image(s)
|
||||||
genericSubmit=Submit
|
genericSubmit=Submit
|
||||||
processTimeWarning=Warning: This process can take up to a minute depending on file-size
|
processTimeWarning=Warning: This process can take up to a minute depending on file-size
|
||||||
pageOrderPrompt=Page Order (Enter a comma-separated list of page numbers) :
|
pageOrderPrompt=Page Order (Enter a comma-separated list of page numbers) :
|
||||||
goToPage=go
|
goToPage=Go
|
||||||
|
true=True
|
||||||
|
false=False
|
||||||
|
unknown=Unknown
|
||||||
|
save=Save
|
||||||
|
close=Close
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
#############
|
#############
|
||||||
@@ -29,7 +38,7 @@ home.rotate.title=Rotate PDFs
|
|||||||
home.rotate.desc=Easily rotate your PDFs.
|
home.rotate.desc=Easily rotate your PDFs.
|
||||||
|
|
||||||
home.imageToPdf.title=Image to PDF
|
home.imageToPdf.title=Image to PDF
|
||||||
home.imageToPdf.desc=Convert a images (PNG, JPEG, GIF) to PDF.
|
home.imageToPdf.desc=Convert a image (PNG, JPEG, GIF) to PDF.
|
||||||
|
|
||||||
home.pdfToImage.title=PDF to Image
|
home.pdfToImage.title=PDF to Image
|
||||||
home.pdfToImage.desc=Convert a PDF to a image. (PNG, JPEG, GIF)
|
home.pdfToImage.desc=Convert a PDF to a image. (PNG, JPEG, GIF)
|
||||||
@@ -43,6 +52,9 @@ home.addImage.desc=Adds a image onto a set location on the PDF (Work in progress
|
|||||||
home.watermark.title=Add Watermark
|
home.watermark.title=Add Watermark
|
||||||
home.watermark.desc=Add a custom watermark to your PDF document.
|
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.title=Change Permissions
|
||||||
home.permissions.desc=Change the permissions of your PDF document
|
home.permissions.desc=Change the permissions of your PDF document
|
||||||
|
|
||||||
@@ -58,6 +70,80 @@ home.removePassword.desc=Remove password protection from your PDF document.
|
|||||||
home.compressPdfs.title=Compress PDFs
|
home.compressPdfs.title=Compress PDFs
|
||||||
home.compressPdfs.desc=Compress PDFs to reduce their file size.
|
home.compressPdfs.desc=Compress PDFs to reduce their file size.
|
||||||
|
|
||||||
|
home.changeMetadata.title=Change Metadata
|
||||||
|
home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document
|
||||||
|
|
||||||
|
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
|
#Add image
|
||||||
@@ -65,12 +151,6 @@ addImage.title=Add Image
|
|||||||
addImage.header=Add image to PDF (Work in progress)
|
addImage.header=Add image to PDF (Work in progress)
|
||||||
addImage.submit=Add image
|
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
|
||||||
merge.title=Merge
|
merge.title=Merge
|
||||||
@@ -117,11 +197,23 @@ split.submit=Split
|
|||||||
imageToPDF.title=Image to PDF
|
imageToPDF.title=Image to PDF
|
||||||
imageToPDF.header=Image to PDF
|
imageToPDF.header=Image to PDF
|
||||||
imageToPDF.submit=Convert
|
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
|
||||||
pdfToImage.title=PDF to Image
|
pdfToImage.title=PDF to Image
|
||||||
pdfToImage.header=PDF to Image
|
pdfToImage.header=PDF to Image
|
||||||
pdfToImage.selectText=Image Format
|
pdfToImage.selectText=Image Format
|
||||||
|
pdfToImage.singleOrMultiple=Image result type
|
||||||
|
pdfToImage.single=Single Big Image
|
||||||
|
pdfToImage.multi=Multiple Images
|
||||||
|
pdfToImage.colorType=Colour type
|
||||||
|
pdfToImage.color=Colour
|
||||||
|
pdfToImage.grey=Greyscale
|
||||||
|
pdfToImage.blackwhite=Black and White (May lose data!)
|
||||||
pdfToImage.submit=Convert
|
pdfToImage.submit=Convert
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
@@ -151,8 +243,15 @@ watermark.selectText.3=Font Size:
|
|||||||
watermark.selectText.4=Rotation (0-360):
|
watermark.selectText.4=Rotation (0-360):
|
||||||
watermark.selectText.5=widthSpacer (Space between each watermark horizontally):
|
watermark.selectText.5=widthSpacer (Space between each watermark horizontally):
|
||||||
watermark.selectText.6=heightSpacer (Space between each watermark vertically):
|
watermark.selectText.6=heightSpacer (Space between each watermark vertically):
|
||||||
|
watermark.selectText.7=Opacity (0% - 100%):
|
||||||
watermark.submit=Add Watermark
|
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
|
#Change permissions
|
||||||
permissions.title=Change Permissions
|
permissions.title=Change Permissions
|
||||||
@@ -177,15 +276,36 @@ removePassword.selectText.1=Select PDF to Decrypt
|
|||||||
removePassword.selectText.2=Password
|
removePassword.selectText.2=Password
|
||||||
removePassword.submit=Remove
|
removePassword.submit=Remove
|
||||||
|
|
||||||
|
changeMetadata.title=Change Metadata
|
||||||
|
changeMetadata.header=Change Metadata
|
||||||
|
changeMetadata.selectText.1=Please edit the variables you wish to change
|
||||||
|
changeMetadata.selectText.2=Delete all metadata
|
||||||
|
changeMetadata.selectText.3=Show Custom Metadata:
|
||||||
|
changeMetadata.author=Author:
|
||||||
|
changeMetadata.creationDate=Creation Date (yyyy/MM/dd HH:mm:ss):
|
||||||
|
changeMetadata.creator=Creator:
|
||||||
|
changeMetadata.keywords=Keywords:
|
||||||
|
changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss):
|
||||||
|
changeMetadata.producer=Producer:
|
||||||
|
changeMetadata.subject=Subject:
|
||||||
|
changeMetadata.title=Title:
|
||||||
|
changeMetadata.trapped=Trapped:
|
||||||
|
changeMetadata.selectText.4=Other Metadata:
|
||||||
|
changeMetadata.selectText.5=Add Custom Metadata Entry
|
||||||
|
changeMetadata.submit=Change
|
||||||
|
|
||||||
|
xlsToPdf.title=Excel to PDF
|
||||||
|
xlsToPdf.header=Excel to PDF
|
||||||
|
xlsToPdf.selectText.1=Select XLS or XLSX Excel sheet to convert
|
||||||
|
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,209 +0,0 @@
|
|||||||
###########
|
|
||||||
# Generic #
|
|
||||||
###########
|
|
||||||
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
|
|
||||||
#############
|
|
||||||
# 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.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.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#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.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
|
|
||||||
|
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
306
src/main/resources/messages_fr_FR.properties
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
|
||||||
|
# Translated by Google Translate #
|
||||||
|
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
|
||||||
|
|
||||||
|
###########
|
||||||
|
# Generic #
|
||||||
|
###########
|
||||||
|
# the direction that the language is written (ltr=left to right, rtl=right to left)
|
||||||
|
language.direction=ltr
|
||||||
|
|
||||||
|
pdfPrompt=Choisir PDF
|
||||||
|
multiPdfPrompt=Choisir des PDF (2+)
|
||||||
|
multiPdfDropPrompt=Sélectionnez (ou glissez-déposez) tous les PDF dont vous avez besoin
|
||||||
|
imgPrompt=Choisir une image
|
||||||
|
genericSubmit=Soumettre
|
||||||
|
processTimeWarning=AttentionÂ: ce processus peut prendre jusqu'Ã une minute en fonction de la taille du fichier
|
||||||
|
pageOrderPrompt=Ordre des pages (Entrez une liste de numéros de page séparés par des virgules)Â:
|
||||||
|
goToPage=Aller
|
||||||
|
true=Vrai
|
||||||
|
false=Faux
|
||||||
|
unknown=Inconnu
|
||||||
|
save=Enregistrer
|
||||||
|
close=Fermer
|
||||||
|
|
||||||
|
#############
|
||||||
|
# HOME-PAGE #
|
||||||
|
#############
|
||||||
|
home.desc=Votre guichet unique hébergé localement pour tous vos besoins PDF.
|
||||||
|
|
||||||
|
navbar.convert=Convertir
|
||||||
|
navbar.security=Sécurité
|
||||||
|
navbar.other=Autre
|
||||||
|
navbar.darkmode=Mode sombre
|
||||||
|
|
||||||
|
home.merge.title=Fusionner des PDF
|
||||||
|
home.merge.desc=Fusionnez facilement plusieurs PDF en un seul.
|
||||||
|
|
||||||
|
home.split.title=Fractionner les PDF
|
||||||
|
home.split.desc=Diviser les PDF en plusieurs documents
|
||||||
|
|
||||||
|
home.rotate.title=Faire pivoter les PDF
|
||||||
|
home.rotate.desc=Faites pivoter facilement vos PDF.
|
||||||
|
|
||||||
|
home.imageToPdf.title=Image au format PDF
|
||||||
|
home.imageToPdf.desc=Convertir une image (PNG, JPEG, GIF) en PDF.
|
||||||
|
|
||||||
|
home.pdfToImage.title=PDF vers image
|
||||||
|
home.pdfToImage.desc=Convertir un PDF en image. (PNG, JPEG, GIF)
|
||||||
|
|
||||||
|
home.pdfOrganiser.title=Organisateur PDF
|
||||||
|
home.pdfOrganiser.desc=Supprimer/Réorganiser les pages dans n'importe quel ordre
|
||||||
|
|
||||||
|
home.addImage.title=Ajouter une image au PDF
|
||||||
|
home.addImage.desc=Ajoute une image à un emplacement défini sur le PDF (Travail en cours)
|
||||||
|
|
||||||
|
home.watermark.title=Ajouter un filigrane
|
||||||
|
home.watermark.desc=Ajoutez un filigrane personnalisé à votre document PDF.
|
||||||
|
|
||||||
|
home.remove-watermark.title=Supprimer le filigrane
|
||||||
|
home.remove-watermark.desc=Supprimez les filigranes de votre document PDF.
|
||||||
|
|
||||||
|
home.permissions.title=Modifier les autorisations
|
||||||
|
home.permissions.desc=Modifier les permissions de votre document PDF
|
||||||
|
|
||||||
|
home.removePages.title=Supprimer des pages
|
||||||
|
home.removePages.desc=Supprimez les pages inutiles de votre document PDF.
|
||||||
|
|
||||||
|
home.addPassword.title=Ajouter un mot de passe
|
||||||
|
home.addPassword.desc=Cryptez votre document PDF avec un mot de passe.
|
||||||
|
|
||||||
|
home.removePassword.title=Supprimer le mot de passe
|
||||||
|
home.removePassword.desc=Supprimez la protection par mot de passe de votre document PDF.
|
||||||
|
|
||||||
|
home.compressPdfs.title=Compresser les PDF
|
||||||
|
home.compressPdfs.desc=Compressez les PDF pour réduire leur taille de fichier.
|
||||||
|
|
||||||
|
home.changeMetadata.title=Modifier les métadonnées
|
||||||
|
home.changeMetadata.desc=Modifier/Supprimer/Ajouter des métadonnées d'un document PDF
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
navbar.settings=Paramètres
|
||||||
|
settings.title=Paramètres
|
||||||
|
settings.update=Mise à jour disponible
|
||||||
|
settings.appVersion=Version de l\u2019application :
|
||||||
|
settings.downloadOption.title=Choisissez l\u2019option 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\u2019utiliser pour d\u2019autres langues et/ou une utilisation non dans docker
|
||||||
|
ocr.credit=Ce service utilise OCRmyPDF et Tesseract pour l\u2019OCR.
|
||||||
|
ocr.submit=Traiter PDF avec OCR
|
||||||
|
|
||||||
|
|
||||||
|
extractImages.title=Extraire les images
|
||||||
|
extractImages.header=Extraire les images
|
||||||
|
extractImages.selectText=Sélectionner le format d\u2019image pour convertir les images extraites en
|
||||||
|
extractImages.submit=Extrait
|
||||||
|
|
||||||
|
|
||||||
|
#File au format PDF
|
||||||
|
fileToPDF.title=Fichier au PDF
|
||||||
|
fileToPDF.header=Convertir n\u2019importe 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#Add image
|
||||||
|
addImage.title=Ajouter une image
|
||||||
|
addImage.header=Ajouter une image au PDF (Travail en cours)
|
||||||
|
addImage.submit=Ajouter une image
|
||||||
|
|
||||||
|
#compress
|
||||||
|
compress.title=Compresser
|
||||||
|
compress.header=Compresser le PDF
|
||||||
|
compress.credit=Ce service utilise OCRmyPDF pour la compression/optimisation PDF.
|
||||||
|
compress.selectText.1=Niveau d\u2019optimisation :
|
||||||
|
compress.selectText.2=0 (pas d\u2019optimisation)
|
||||||
|
compress.selectText.3=1 (par défaut, optimisation sans perte)
|
||||||
|
compress.selectText.4=2 (optimisation avec perte)
|
||||||
|
compress.selectText.5=3 (optimisation avec perte, plus agressive)
|
||||||
|
compress.selectText.6=Activer l\u2019affichage Web rapide (linéariser PDF)
|
||||||
|
compress.selectText.7=Activer l\u2019encodage JBIG2 avec perte
|
||||||
|
compress.submit=Compresser
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
merge.title=Fusionner
|
||||||
|
merge.header=Fusionner plusieurs PDF (2+)
|
||||||
|
merge.submit=Fusionner
|
||||||
|
|
||||||
|
#pdfOrganiser
|
||||||
|
pdfOrganiser.title=Organisateur de pages
|
||||||
|
pdfOrganiser.header=Organisateur de pages PDF
|
||||||
|
pdfOrganiser.submit=Réorganiser les pages
|
||||||
|
|
||||||
|
|
||||||
|
#pageRemover
|
||||||
|
pageRemover.title=Suppresseur de pages
|
||||||
|
pageRemover.header=Outil de suppression de pages PDF
|
||||||
|
pageRemover.pagesToDelete=Pages à supprimer (Entrez une liste de numéros de page séparés par des virgules)Â:
|
||||||
|
pageRemover.submit=Supprimer des pages
|
||||||
|
|
||||||
|
#rotate
|
||||||
|
rotate.title=Faire pivoter le PDF
|
||||||
|
rotate.header=Faire pivoter le PDF
|
||||||
|
rotate.selectAngle=S\u00e9lectionner l'angle de rotation (en multiples de 90 degr\u00e9s):
|
||||||
|
rotate.submit=Rotation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
split.title=Fractionner le PDF
|
||||||
|
split.header=Diviser le PDF
|
||||||
|
split.desc.1=Les numéros que vous sélectionnez sont le numéro de page sur lequel vous souhaitez faire un fractionnement
|
||||||
|
split.desc.2=Ainsi, la sélection de 1,3,7-8 diviserait un document de 10 pages en 6 PDF distincts avecÂ:
|
||||||
|
split.desc.3=Document #1Â: Page 1
|
||||||
|
split.desc.4=Document #2Â: Pages 2 et 3
|
||||||
|
split.desc.5=Document #3Â: Pages 4, 5 et 6
|
||||||
|
split.desc.6=Document #4Â: Page 7
|
||||||
|
split.desc.7=Document #5Â: Page 8
|
||||||
|
split.desc.8=Document #6Â: Pages 9 et 10
|
||||||
|
split.splitPages=Entrez les pages sur lesquelles fractionnerÂ:
|
||||||
|
split.submit=Diviser
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
imageToPDF.title=Image au format PDF
|
||||||
|
imageToPDF.header=Image au format PDF
|
||||||
|
imageToPDF.submit=Convertir
|
||||||
|
imageToPDF.selectText.1=Étirer pour s'adapter
|
||||||
|
imageToPDF.selectText.2=Rotation automatique du PDF
|
||||||
|
imageToPDF.selectText.3=Logique de fichiers multiples (activé uniquement si vous travaillez avec plusieurs images)
|
||||||
|
imageToPDF.selectText.4= Fusionner en un seul PDF
|
||||||
|
imageToPDF.selectText.5= Convertir en PDFs distincts
|
||||||
|
|
||||||
|
#pdfToImage
|
||||||
|
pdfToImage.title=PDF vers image
|
||||||
|
pdfToImage.header=PDF vers image
|
||||||
|
pdfToImage.selectText=Format d'image
|
||||||
|
pdfToImage.singleOrMultiple=Type de résultat d'image
|
||||||
|
pdfToImage.single=Une seule grande image
|
||||||
|
pdfToImage.multi=Plusieurs images
|
||||||
|
pdfToImage.colorType=Type de couleur
|
||||||
|
pdfToImage.color=Couleur
|
||||||
|
pdfToImage.grey=Niveaux de gris
|
||||||
|
pdfToImage.blackwhite=Noir et Blanc (Peut perdre des données !)
|
||||||
|
pdfToImage.submit=Convertir
|
||||||
|
|
||||||
|
#addPassword
|
||||||
|
addPassword.title=Ajouter un mot de passe
|
||||||
|
addPassword.header=Ajouter un mot de passe (chiffrer)
|
||||||
|
addPassword.selectText.1=Sélectionnez le PDF à chiffrer
|
||||||
|
addPassword.selectText.2=Mot de passe
|
||||||
|
addPassword.selectText.3=Longueur de la clé de chiffrement
|
||||||
|
addPassword.selectText.4=Les valeurs supérieures sont plus fortes, mais les valeurs inférieures ont une meilleure compatibilité.
|
||||||
|
addPassword.selectText.5=Autorisations à définir
|
||||||
|
addPassword.selectText.6=Empêcher l'assemblage du document
|
||||||
|
addPassword.selectText.7=Empêcher l'extraction de contenu
|
||||||
|
addPassword.selectText.8=Empêcher l'extraction pour l'accessibilité
|
||||||
|
addPassword.selectText.9=Empêcher de remplir le formulaire
|
||||||
|
addPassword.selectText.10=Empêcher la modification
|
||||||
|
addPassword.selectText.11=Empêcher la modification des annotations
|
||||||
|
addPassword.selectText.12=Empêcher l'impression
|
||||||
|
addPassword.selectText.13=Empêcher l'impression de différents formats
|
||||||
|
addPassword.submit=Crypter
|
||||||
|
|
||||||
|
#watermark
|
||||||
|
watermark.title=Ajouter un filigrane
|
||||||
|
watermark.header=Ajouter un filigrane
|
||||||
|
watermark.selectText.1=Sélectionnez le PDF auquel ajouter un filigraneÂ:
|
||||||
|
watermark.selectText.2=Texte du filigraneÂ:
|
||||||
|
watermark.selectText.3=Taille de la policeÂ:
|
||||||
|
watermark.selectText.4=Rotation (0-360)Â:
|
||||||
|
watermark.selectText.5=widthSpacer (Espace entre chaque filigrane horizontalement)Â:
|
||||||
|
watermark.selectText.6=heightSpacer (Espace entre chaque filigrane verticalement)Â:
|
||||||
|
watermark.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.submit=Supprimer le filigrane
|
||||||
|
|
||||||
|
#Change permissions
|
||||||
|
permissions.title=Modifier les autorisations
|
||||||
|
permissions.header=Modifier les autorisations
|
||||||
|
permissions.warning=Attention pour que ces permissions soient immuables il est recommandé de les définir avec un mot de passe via la page add-password
|
||||||
|
permissions.selectText.1=Sélectionnez PDF pour modifier les autorisations
|
||||||
|
permissions.selectText.2=Autorisations à définir
|
||||||
|
permissions.selectText.3=Employer l'assemblage du document
|
||||||
|
permissions.selectText.4=Employer l'extraction de contenu
|
||||||
|
permissions.selectText.5=Employer l'extraction pour l'accessibilité
|
||||||
|
permissions.selectText.6=Employer de remplir le formulaire
|
||||||
|
permissions.selectText.7=Employer la modification
|
||||||
|
permissions.selectText.8=Employer la modification des annotations
|
||||||
|
permissions.selectText.9=Employer l'impression
|
||||||
|
permissions.selectText.10=Emp�cher l'impression de diff�rents formats
|
||||||
|
permissions.submit=Modificateur
|
||||||
|
|
||||||
|
#supprimer le mot de passe
|
||||||
|
removePassword.title=Supprimer le mot de passe
|
||||||
|
removePassword.header=Supprimer le mot de passe (Déchiffrer)
|
||||||
|
removePassword.selectText.1=Sélectionnez le PDF à déchiffrer
|
||||||
|
removePassword.selectText.2=Mot de passe
|
||||||
|
removePassword.submit=Supprimer
|
||||||
|
|
||||||
|
changeMetadata.title=Modifier les métadonnées
|
||||||
|
changeMetadata.header=Modifier les métadonnées
|
||||||
|
changeMetadata.selectText.1=Veuillez modifier les variables que vous souhaitez modifier
|
||||||
|
changeMetadata.selectText.2=Supprimer toutes les métadonnées
|
||||||
|
changeMetadata.selectText.3=Afficher les métadonnées personnalisées:
|
||||||
|
changeMetadata.author=Auteur:
|
||||||
|
changeMetadata.creationDate=Date de création (aaaa/MM/jj HH:mm:ss):
|
||||||
|
changeMetadata.creator=Créateur:
|
||||||
|
changeMetadata.keywords=Mots clés:
|
||||||
|
changeMetadata.modDate=Date de modification (aaaa/MM/jj HH:mm:ss):
|
||||||
|
changeMetadata.producer=Producteur:
|
||||||
|
changeMetadata.subject=Objet:
|
||||||
|
changeMetadata.title=Titre:
|
||||||
|
changeMetadata.trapped=Piégé:
|
||||||
|
changeMetadata.selectText.4=Autres métadonnées:
|
||||||
|
changeMetadata.selectText.5=Ajouter une entrée de métadonnées personnalisées
|
||||||
|
changeMetadata.submit=Modifier
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
2018
src/main/resources/static/css/bootstrap-icons.css
vendored
Normal file
6
src/main/resources/static/css/bootstrap.min.css
vendored
Normal file
@@ -1,16 +1,16 @@
|
|||||||
/* Dark Mode Styles */
|
/* Dark Mode Styles */
|
||||||
body {
|
body {
|
||||||
background-color: #333;
|
background-color: #333 !important;
|
||||||
color: #fff;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-card {
|
.dark-card {
|
||||||
background-color: #333 !important;
|
background-color: #333 !important;
|
||||||
color: white;
|
color: white !important;
|
||||||
}
|
}
|
||||||
.jumbotron {
|
.jumbotron {
|
||||||
background-color: #222; /* or any other dark color */
|
background-color: #222; /* or any other dark color */
|
||||||
color: #fff; /* or any other light color */
|
color: #fff !important; /* or any other light color */
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group {
|
.list-group {
|
||||||
@@ -20,4 +20,7 @@ body {
|
|||||||
.list-group-item {
|
.list-group-item {
|
||||||
background-color: #222 !important;
|
background-color: #222 !important;
|
||||||
color: fff !important;
|
color: fff !important;
|
||||||
|
}
|
||||||
|
#support-section {
|
||||||
|
background-color: #444 !important;
|
||||||
}
|
}
|
||||||
BIN
src/main/resources/static/css/fonts/bootstrap-icons.woff
vendored
Normal file
BIN
src/main/resources/static/css/fonts/bootstrap-icons.woff2
vendored
Normal file
23
src/main/resources/static/css/general.css
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#page-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content-wrap {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[lang-direction=ltr] * {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[lang-direction=rtl] * {
|
||||||
|
direction: rtl;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#page-container {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content-wrap {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#footer {
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
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 |
6
src/main/resources/static/images/docker.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="50px" height="50px" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke="#007bff" stroke-width="38" d="M 297.507 242.806 L 339.507 242.806 M 247.507 242.806 L 289.507 242.806 M 198.507 242.806 L 240.507 242.806 M 149.507 242.806 L 190.507 242.806 M 99.507 242.806 L 141.507 242.806 M 149.507 196.806 L 190.507 196.806 M 198.507 196.806 L 240.507 196.806 M 247.507 196.806 L 289.507 196.806 M 247.507 150.806 L 289.507 150.806"/>
|
||||||
|
<path fill="#007bff" d="M 473.507 244.806 C 473.507 244.806 455.507 227.806 418.507 233.806 C 414.507 204.806 383.507 187.806 383.507 187.806 C 383.507 187.806 354.507 222.806 375.507 261.806 C 369.507 264.806 359.507 268.806 344.507 268.806 L 69.507 268.806 C 64.507 287.806 64.507 413.806 202.507 413.806 C 301.507 413.806 375.507 367.806 410.507 283.806 C 462.507 287.806 473.507 244.806 473.507 244.806"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 921 B |
17
src/main/resources/static/images/github.svg
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="35px" height="35px" viewBox="0 -0.5 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
|
||||||
|
<title>Github-color</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
|
||||||
|
</defs>
|
||||||
|
<g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Color-" transform="translate(-700.000000, -560.000000)" fill="#007bff">
|
||||||
|
<path d="M723.9985,560 C710.746,560 700,570.787092 700,584.096644 C700,594.740671 706.876,603.77183 716.4145,606.958412 C717.6145,607.179786 718.0525,606.435849 718.0525,605.797328 C718.0525,605.225068 718.0315,603.710086 718.0195,601.699648 C711.343,603.155898 709.9345,598.469394 709.9345,598.469394 C708.844,595.686405 707.2705,594.94548 707.2705,594.94548 C705.091,593.450075 707.4355,593.480194 707.4355,593.480194 C709.843,593.650366 711.1105,595.963499 711.1105,595.963499 C713.2525,599.645538 716.728,598.58234 718.096,597.964902 C718.3135,596.407754 718.9345,595.346062 719.62,594.743683 C714.2905,594.135281 708.688,592.069123 708.688,582.836167 C708.688,580.205279 709.6225,578.054788 711.1585,576.369634 C710.911,575.759726 710.0875,573.311058 711.3925,569.993458 C711.3925,569.993458 713.4085,569.345902 717.9925,572.46321 C719.908,571.928599 721.96,571.662047 724.0015,571.651505 C726.04,571.662047 728.0935,571.928599 730.0105,572.46321 C734.5915,569.345902 736.603,569.993458 736.603,569.993458 C737.9125,573.311058 737.089,575.759726 736.8415,576.369634 C738.3805,578.054788 739.309,580.205279 739.309,582.836167 C739.309,592.091712 733.6975,594.129257 728.3515,594.725612 C729.2125,595.469549 729.9805,596.939353 729.9805,599.18773 C729.9805,602.408949 729.9505,605.006706 729.9505,605.797328 C729.9505,606.441873 730.3825,607.191834 731.6005,606.9554 C741.13,603.762794 748,594.737659 748,584.096644 C748,570.787092 737.254,560 723.9985,560" id="Github">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
6
src/main/resources/static/js/bootstrap.min.js
vendored
Normal file
2
src/main/resources/static/js/jquery.min.js
vendored
Normal file
13
src/main/resources/static/js/jszip.min.js
vendored
Normal file
6
src/main/resources/static/js/popper.min.js
vendored
Normal file
15821
src/main/resources/static/pdfjs/pdf.js
vendored
Normal file
1
src/main/resources/static/pdfjs/pdf.js.map
vendored
Normal file
64477
src/main/resources/static/pdfjs/pdf.worker.js
vendored
Normal file
1
src/main/resources/static/pdfjs/pdf.worker.js.map
vendored
Normal file
@@ -1,54 +1,39 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{addImage.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{addImage.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<br>
|
<div class="container">
|
||||||
<div class="container">
|
<div class="row justify-content-center">
|
||||||
<div class="row justify-content-center">
|
<div class="col-md-6">
|
||||||
<div class="col-md-6">
|
<h2 th:text="#{addImage.header}"></h2>
|
||||||
<h2 th:text="#{addImage.header}"></h2>
|
<form method="post" th:action="@{add-image}" enctype="multipart/form-data">
|
||||||
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
|
<div class="custom-file">
|
||||||
|
<input type="file" class="custom-file-input" id="fileInput2" name="fileInput2" accept="image/*" required>
|
||||||
<form method="post" th:action="@{add-image}"
|
<label class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label>
|
||||||
enctype="multipart/form-data">
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
<div class="custom-file">
|
<label for="x">X</label> <input type="number" class="form-control" id="x" name="x" step="0.01" required>
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
</div>
|
||||||
name="fileInput" required> <label
|
<div class="form-group">
|
||||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
<label for="y">Y</label> <input type="number" class="form-control" id="y" name="y" step="0.01" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="custom-file">
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{addImage.submit}"></button>
|
||||||
<input type="file" class="custom-file-input" id="fileInput2"
|
</form>
|
||||||
name="fileInput2" required> <label
|
</div>
|
||||||
class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label for="x">X</label> <input type="number" class="form-control"
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
id="x" name="x" step="0.01" required>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="y">Y</label> <input type="number" class="form-control"
|
|
||||||
id="y" name="y" step="0.01" required>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{addImage.submit}"></button>
|
|
||||||
</form>
|
|
||||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,44 +1,48 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{compress.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{compress.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
<body> <div id="page-container">
|
<body>
|
||||||
<div id="content-wrap">
|
<div id="page-container">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div id="content-wrap">
|
||||||
<br>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{compress.header}"></h2>
|
<h2 th:text="#{compress.header}"></h2>
|
||||||
<form action="#" th:action="@{compress-pdf}"
|
<form action="#" th:action="@{/compress-pdf}" method="post" enctype="multipart/form-data">
|
||||||
th:object="${rotateForm}" method="post"
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
enctype="multipart/form-data">
|
<div>
|
||||||
<p th:text="#{processTimeWarning}"></p>
|
<label for="optimizeLevel" th:text="#{compress.selectText.1}"></label>
|
||||||
<div class="custom-file">
|
<select name="optimizeLevel" id="optimizeLevel">
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
<option value="0" th:text="#{compress.selectText.2}"></option>
|
||||||
name="fileInput" required> <label
|
<option value="1" selected th:text="#{compress.selectText.3}"></option>
|
||||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
<option value="2" th:text="#{compress.selectText.4}"></option>
|
||||||
</div>
|
<option value="3" th:text="#{compress.selectText.5}"></option>
|
||||||
<div class="form-group">
|
</select>
|
||||||
<label for="imageCompressionLevel" th:text="#{compress.compressLevel}"></label> <input type="number" class="form-control"
|
</div>
|
||||||
id="imageCompressionLevel" name="imageCompressionLevel" step="1"
|
<div>
|
||||||
value="1" min="1" max="100" required>
|
<input type="checkbox" name="fastWebView" id="fastWebView">
|
||||||
</div>
|
<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>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{compress.submit}"></button>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
36
src/main/resources/templates/convert/file-to-pdf.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!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=#{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,38 +1,77 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{imageToPDF.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{imageToPDF.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
<body> <div id="page-container">
|
<body>
|
||||||
<div id="content-wrap">
|
<div id="page-container">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div id="content-wrap">
|
||||||
<br>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{imageToPDF.header}"></h2>
|
<h2 th:text="#{imageToPDF.header}"></h2>
|
||||||
|
|
||||||
<form method="post" enctype="multipart/form-data"
|
<form method="post" enctype="multipart/form-data" th:action="@{img-to-pdf}">
|
||||||
th:action="@{img-to-pdf}">
|
|
||||||
<div class="custom-file">
|
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
|
||||||
name="fileInput" required> <label
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='image/*', inputText=#{imgPrompt})}"></div>
|
||||||
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>
|
|
||||||
|
|
||||||
</form>
|
<div class="form-check">
|
||||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
<input type="checkbox" class="form-check-input" name="stretchToFit" id="stretchToFit">
|
||||||
</div>
|
<label class="ml-3" for="stretchToFit" th:text=#{imageToPDF.selectText.1}></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" name="autoRotate" id="autoRotate">
|
||||||
|
<label class="ml-3" for="autoRotate" th:text=#{imageToPDF.selectText.2}></label>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<input type="hidden" id="override" name="override" value="multi">
|
||||||
|
<div class="form-group">
|
||||||
|
<label th:text=#{imageToPDF.selectText.3}></label>
|
||||||
|
<select class="form-control" id="conversionType" name="conversionType" disabled>
|
||||||
|
<option value="merge" th:text=#{imageToPDF.selectText.4}></option>
|
||||||
|
<option value="convert" th:text=#{imageToPDF.selectText.5} selected></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br> <br>
|
||||||
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
|
||||||
|
<script>
|
||||||
|
$('#fileInput-input').on('change', function() {
|
||||||
|
var files = document.getElementById("fileInput-input").files;
|
||||||
|
var conversionType = document.getElementById("conversionType");
|
||||||
|
console.log("files.length=" + files.length)
|
||||||
|
if (files.length > 1) {
|
||||||
|
conversionType.disabled = false;
|
||||||
|
} else {
|
||||||
|
conversionType.disabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#conversionType').change(function() {
|
||||||
|
var selectedValue = $(this).val();
|
||||||
|
var override = document.getElementById("override");
|
||||||
|
console.log("selectedValue=" + selectedValue)
|
||||||
|
if (selectedValue === 'merge') {
|
||||||
|
override.value = "single";
|
||||||
|
} else if (selectedValue === 'convert') {
|
||||||
|
override.value = "multi";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,43 +1,58 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfToImage})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{pdfToImage.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
<body> <div id="page-container">
|
<body>
|
||||||
<div id="content-wrap">
|
<div id="page-container">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div id="content-wrap">
|
||||||
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
||||||
<br>
|
<br> <br>
|
||||||
<br>
|
<div class="container">
|
||||||
<div class="container">
|
<div class="row justify-content-center">
|
||||||
<div class="row justify-content-center">
|
<div class="col-md-6">
|
||||||
<div class="col-md-6">
|
<h2 th:text="#{pdfToImage.header}"></h2>
|
||||||
<h2 th:text="#{pdfToImage.header}"></h2>
|
<p th:text="#{processTimeWarning}"></p>
|
||||||
<form method="post" enctype="multipart/form-data"
|
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-img}">
|
||||||
th:action="@{pdf-to-img}">
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
<div class="custom-file">
|
<div class="form-group">
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
<label th:text="#{pdfToImage.selectText}"></label>
|
||||||
name="fileInput" required> <label
|
<select class="form-control" name="imageFormat">
|
||||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
<option value="png">PNG</option>
|
||||||
</div>
|
<option value="jpg">JPG</option>
|
||||||
<div class="form-group">
|
<option value="gif">GIF</option>
|
||||||
<label th:text="#{pdfToImage.selectText}"></label> <select class="form-control"
|
</select>
|
||||||
name="imageFormat">
|
</div>
|
||||||
<option value="jpg">JPEG</option>
|
<div class="form-group">
|
||||||
<option value="png">PNG</option>
|
<label th:text="#{pdfToImage.singleOrMultiple}"></label>
|
||||||
<option value="gif">GIF</option>
|
<select class="form-control" name="singleOrMultiple">
|
||||||
</select>
|
<option value="single" th:text="#{pdfToImage.single}"></option>
|
||||||
</div>
|
<option value="multiple" th:text="#{pdfToImage.multi}"></option>
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{pdfToImage.submit}"></button>
|
</select>
|
||||||
</form>
|
</div>
|
||||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
<div class="form-group">
|
||||||
|
<label th:text="#{pdfToImage.colorType}"></label>
|
||||||
|
<select class="form-control" name="colorType">
|
||||||
|
<option value="color" th:text="#{pdfToImage.color}"></option>
|
||||||
|
<option value="greyscale" th:text="#{pdfToImage.grey}"></option>
|
||||||
|
<option value="blackwhite" th:text="#{pdfToImage.blackwhite}"></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="dpi">DPI:</label>
|
||||||
|
<input type="number" name="dpi" class="form-control" id="dpi" min="1" max="100" step="1" value="30" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToImage.submit}"></button>
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
</div>
|
||||||
</div>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
31
src/main/resources/templates/convert/pdf-to-pdfa.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
<th:block th:insert="~{fragments/common :: head(title=#{pdfToPDFA.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="page-container">
|
||||||
|
<div id="content-wrap">
|
||||||
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
<br> <br>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2 th:text="#{pdfToPDFA.header}"></h2>
|
||||||
|
<p>Currently doesn't work for multiple inputs at once</p>
|
||||||
|
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-pdfa}">
|
||||||
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
|
<br>
|
||||||
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToPDFA.submit}"></button>
|
||||||
|
</form>
|
||||||
|
<p class="mt-3" th:text="#{pdfToPDFA.credit}"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
131
src/main/resources/templates/error.html
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Error! :(</title>
|
||||||
|
<th:block th:insert="~{fragments/common :: head(title='')}"></th:block>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
background-color: #005b7f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr));
|
||||||
|
gap: 25px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
border: 1px solid rgba(0, 0, 0, .125);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card .card-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#support-section {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
padding: 4rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#support-section h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#support-section p {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#button-group {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#github-button, #discord-button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
margin: 1rem;
|
||||||
|
background-color: #008CBA;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 3rem;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#github-button:hover, #discord-button:hover, #home-button:hover {
|
||||||
|
background-color: #005b7f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#home-button {
|
||||||
|
display: block;
|
||||||
|
width: 200px;
|
||||||
|
height: 50px;
|
||||||
|
margin: 2em auto;
|
||||||
|
background-color: #008CBA;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 50px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 25px;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="page-container">
|
||||||
|
<div id="content-wrap">
|
||||||
|
|
||||||
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div id="support-section">
|
||||||
|
<h1 class="display-2">Oops!</h1>
|
||||||
|
<p class="lead" th:if="${param.status == '404'}">We can't seem to find the page you're looking for.</p>
|
||||||
|
<p class="lead" th:unless="${param.status == '404'}">Something went wrong</p>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<h2>Need help / Found a issue?</h2>
|
||||||
|
<p>If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:</p>
|
||||||
|
<div id="button-group">
|
||||||
|
<a href="https://github.com/Frooodle/Stirling-PDF/issues" id="github-button" target="_blank">Submit a ticket on GitHub</a>
|
||||||
|
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank">Join our Discord server</a>
|
||||||
|
</div>
|
||||||
|
<a href="/" id="home-button">Go back to homepage</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
src/main/resources/templates/extract-images.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!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=#{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>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<div th:fragment="card" class="feature-card">
|
<div th:fragment="card" class="feature-card">
|
||||||
<h5 class="card-title" th:text="${cardTitle}"></h5>
|
<h5 class="card-title" th:text="${cardTitle}"></h5>
|
||||||
<p class="card-text" th:text="${cardText}"></p>
|
<p class="card-text" th:text="${cardText}"></p>
|
||||||
<a class="btn btn-primary" th:href="${cardLink}" th:text="#{goToPage}"></a>
|
<a class="btn btn-primary" th:href="${cardLink}" th:text="#{goToPage}"></a>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,120 +1,416 @@
|
|||||||
<head th:fragment="head">
|
<head th:fragment="head">
|
||||||
<link rel="shortcut icon" href="favicon.svg">
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
|
<!-- Metadata -->
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<title th:text="${@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">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.3.122/build/pdf.min.js"></script>
|
<!-- jQuery -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.3.122/web/pdf_viewer.min.css" rel="stylesheet">
|
<script src="js/jquery.min.js"></script>
|
||||||
|
|
||||||
|
<!-- jQuery -->
|
||||||
|
<script src="js/jszip.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Bootstrap -->
|
||||||
|
<script src="js/popper.min.js"></script>
|
||||||
|
<script src="js/bootstrap.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="css/bootstrap-icons.css">
|
||||||
|
|
||||||
|
<!-- PDF.js -->
|
||||||
|
<script src="pdfjs/pdf.js"></script>
|
||||||
|
|
||||||
|
<!-- Custom -->
|
||||||
|
<link rel="stylesheet" href="css/general.css">
|
||||||
|
<link rel="stylesheet" th:href="@{css/dark-mode.css}" id="dark-mode-styles">
|
||||||
|
|
||||||
<link rel="stylesheet" th:href="@{dark-mode.css}" id="dark-mode-styles">
|
|
||||||
<script>
|
<script>
|
||||||
function toggleDarkMode() {
|
function toggleDarkMode() {
|
||||||
var checkbox = document.getElementById("toggle-dark-mode");
|
var checkbox = document.getElementById("toggle-dark-mode");
|
||||||
var darkModeStyles = document.getElementById("dark-mode-styles");
|
var darkModeStyles = document.getElementById("dark-mode-styles");
|
||||||
if (checkbox.checked) {
|
if (checkbox.checked) {
|
||||||
localStorage.setItem("dark-mode", "on");
|
localStorage.setItem("dark-mode", "on");
|
||||||
darkModeStyles.disabled = false;
|
darkModeStyles.disabled = false;
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem("dark-mode", "off");
|
localStorage.setItem("dark-mode", "off");
|
||||||
darkModeStyles.disabled = true;
|
darkModeStyles.disabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var darkModeStyles = document.getElementById("dark-mode-styles");
|
var darkModeStyles = document.getElementById("dark-mode-styles");
|
||||||
var checkbox = document.getElementById("toggle-dark-mode");
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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>
|
|
||||||
<title th:text="'S-PDF ' + ${title}"></title>
|
</script>
|
||||||
<link rel="stylesheet" href="general.css">
|
|
||||||
</head>
|
</head>
|
||||||
|
<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: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>
|
||||||
|
|
||||||
<th:block th:fragment="filelist">
|
<script>
|
||||||
<div id="fileList"></div>
|
|
||||||
<div id="fileList2"></div>
|
$('form').submit(function(event) {
|
||||||
<script>
|
var processing = "Processing..."
|
||||||
var input = document.getElementById("fileInput");
|
var submitButtonText = $('#submitBtn').text()
|
||||||
var output = document.getElementById("fileList");
|
|
||||||
|
$('#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")
|
||||||
|
|
||||||
input.addEventListener("change", function() {
|
// Get the selected download option from localStorage
|
||||||
var files = input.files;
|
const downloadOption = localStorage.getItem('downloadOption');
|
||||||
var fileNames = "";
|
|
||||||
|
|
||||||
for (var i = 0; i < files.length; i++) {
|
var formData = new FormData($('form')[0]);
|
||||||
fileNames += (i + 1) + ". " + files[i].name + "<br>";
|
|
||||||
}
|
// 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")
|
||||||
|
|
||||||
output.innerHTML = fileNames;
|
|
||||||
});
|
// Extract the filename from the Content-Disposition header, if present
|
||||||
</script>
|
let filename = null;
|
||||||
<script>
|
const contentDispositionHeader = response.headers.get('Content-Disposition');
|
||||||
var input2 = document.getElementById("fileInput2");
|
console.log(contentDispositionHeader)
|
||||||
var output2 = document.getElementById("fileList2");
|
if (contentDispositionHeader && contentDispositionHeader.indexOf('attachment') !== -1) {
|
||||||
if (input2 != null && !input2) {
|
filename = contentDispositionHeader.split('filename=')[1].replace(/"/g, '');
|
||||||
input2.addEventListener("change", function() {
|
} else {
|
||||||
var files = input2.files;
|
// If the Content-Disposition header is not present or does not contain the filename, use a default filename
|
||||||
var fileNames = "";
|
filename = 'download';
|
||||||
|
}
|
||||||
|
console.log("filename=" + filename)
|
||||||
|
|
||||||
for (var i = 0; i < files.length; i++) {
|
|
||||||
fileNames += (i + 1) + ". " + files[i].name + "<br>";
|
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">
|
||||||
|
|
||||||
|
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}]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
output2.innerHTML = fileNames;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.custom-file-label {
|
||||||
|
padding-right: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
<script>
|
.selected-files {
|
||||||
if (dropContainer) {
|
margin-top: 10px;
|
||||||
dropContainer.ondragover = dropContainer.ondragenter = function(evt) {
|
max-height: 150px;
|
||||||
evt.preventDefault();
|
overflow-y: auto;
|
||||||
};
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
dropContainer.ondrop = function(evt) {
|
</style>
|
||||||
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)">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<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}]]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</th:block>
|
</th:block>
|
||||||
89
src/main/resources/templates/fragments/errorBanner.html
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<th:block th:fragment="errorBanner">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#github-button,
|
||||||
|
#discord-button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
background-color: #008CBA;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 3rem;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#github-button:hover,
|
||||||
|
#discord-button:hover {
|
||||||
|
background-color: #005b7f;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<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>
|
||||||
|
<button type="button" class="btn btn-danger" th:if="${trace}" onclick="toggletrace()">Show Stack Trace</button>
|
||||||
|
<button type="button" class="btn btn-secondary" th:if="${trace}" onclick="copytrace()">Copy Stack Trace</button>
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close" onclick="dismissError()">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
<!-- Stack trace section -->
|
||||||
|
<div id="trace" th:if="${trace}" style="max-height: 0; overflow: hidden;">
|
||||||
|
<div style="background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 3px; padding: 10px; margin-top: 5px;">
|
||||||
|
<pre id="traceContent" th:text="${trace}"></pre>
|
||||||
|
</div>
|
||||||
|
<!-- Buttons to submit a ticket on GitHub and join Discord server -->
|
||||||
|
<a href="https://github.com/Frooodle/Stirling-PDF/issues" id="github-button" target="_blank">Submit a ticket on GitHub</a>
|
||||||
|
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank">Join our Discord server</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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>
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
<div th:fragment="footer">
|
<div th:fragment="footer">
|
||||||
<link rel="stylesheet"
|
<footer id="footer" class="text-center py-3">
|
||||||
href="https://use.fontawesome.com/releases/v5.6.1/css/all.css">
|
<a href="https://github.com/Frooodle/Stirling-PDF" target="_blank" class="mx-1"><img src="images/github.svg"></img></a>
|
||||||
<footer id="footer" class="text-center py-3">
|
<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://github.com/Frooodle/Stirling-PDF" target="_blank"
|
<a href="https://discord.gg/Cn8pWhQRxZ" target="_blank" class="mx-1"><img src="images/discord.svg"></img></a>
|
||||||
class="mx-1"> <i class="fab fa-github fa-2x"></i>
|
<div th:if="${@appName} != 'Stirling PDF'" class="mt-2" style="color: grey;">Powered by Stirling PDF</div>
|
||||||
</a> <a href="https://hub.docker.com/r/frooodle/s-pdf" target="_blank"
|
</footer>
|
||||||
class="mx-1"> <i class="fab fa-docker fa-2x"></i>
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
@@ -1,132 +1,273 @@
|
|||||||
<div th:fragment="navbar">
|
<div th:fragment="navbar" class="mx-auto">
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
<script>
|
||||||
<div class="container">
|
document
|
||||||
<a class="navbar-brand" href="#" th:href="@{/}">Stirling PDF</a>
|
.addEventListener(
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse"
|
'DOMContentLoaded',
|
||||||
data-target="#navbarNav" aria-controls="navbarNav"
|
function() {
|
||||||
aria-expanded="false" aria-label="Toggle navigation">
|
// Get the dropdown items
|
||||||
<span class="navbar-toggler-icon"></span>
|
var dropdownItems = document
|
||||||
</button>
|
.querySelectorAll('.lang_dropdown-item');
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav">
|
// Loop through the dropdown items
|
||||||
|
for (var i = 0; i < dropdownItems.length; i++) {
|
||||||
<li class="nav-item"><a class="nav-link" href="#"
|
dropdownItems[i].classList
|
||||||
th:href="@{merge-pdfs}"
|
.remove('active');
|
||||||
th:classappend="${currentPage}=='merge-pdfs' ? 'active' : ''" th:text="#{home.merge.title}"></a></li>
|
if (dropdownItems[i].dataset.languageCode === localStorage
|
||||||
<li class="nav-item"><a class="nav-link" href="#"
|
.getItem('languageCode')) {
|
||||||
th:href="@{split-pdfs}"
|
dropdownItems[i].classList
|
||||||
th:classappend="${currentPage}=='split-pdfs' ? 'active' : ''" th:text="#{home.split.title}"></a></li>
|
.add('active');
|
||||||
<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>
|
// Add a click event listener to each dropdown item
|
||||||
|
dropdownItems[i]
|
||||||
<li class="nav-item"><a class="nav-link" href="#"
|
.addEventListener(
|
||||||
th:href="@{rotate-pdf}"
|
'click',
|
||||||
th:classappend="${currentPage}=='rotate-pdf' ? 'active' : ''" th:text="#{home.rotate.title}"></a></li>
|
function(event) {
|
||||||
|
|
||||||
|
// Prevent the default link behavior
|
||||||
|
event
|
||||||
|
.preventDefault();
|
||||||
|
|
||||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-to-pdf' ? 'active' : ''">
|
// Get the language code
|
||||||
<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>
|
var languageCode = this.dataset.languageCode;
|
||||||
<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>
|
// Save the language code to local storage
|
||||||
<a class="dropdown-item" href="#" th:href="@{img-to-pdf}" th:classappend="${currentPage}=='img-to-pdf' ? 'active' : ''" th:text="#{home.imageToPdf.title}"></a>
|
localStorage
|
||||||
</div>
|
.setItem(
|
||||||
</li>
|
'languageCode',
|
||||||
|
languageCode);
|
||||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='change-permissions' OR ${currentPage}=='add-watermark' ? 'active' : ''">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.security}"></a>
|
// Get the current URL
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
var currentUrl = window.location.href;
|
||||||
<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>
|
// Check if the URL already contains a "?lang" query parameter
|
||||||
<a class="dropdown-item" href="#" th:href="@{change-permissions}" th:classappend="${currentPage}=='change-permissions' ? 'active' : ''" th:text="#{home.permissions.title}"></a>
|
if (currentUrl
|
||||||
<a class="dropdown-item" href="#" th:href="@{add-watermark}" th:classappend="${currentPage}=='add-watermark' ? 'active' : ''" th:text="#{home.watermark.title}"></a>
|
.indexOf('?lang=') === -1) {
|
||||||
</div>
|
// Update the URL with the "?lang" query parameter
|
||||||
</li>
|
window.location.href = currentUrl
|
||||||
|
+ '?lang='
|
||||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='add-image' OR ${currentPage}=='compress-pdf' ? 'active' : ''">
|
+ languageCode;
|
||||||
<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>
|
} else {
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
// Replace the "?lang" query parameter with the new value
|
||||||
<a class="dropdown-item" href="#" th:href="@{add-image}" th:classappend="${currentPage}=='add-image' ? 'active' : ''" th:text="#{home.addImage.title}"></a>
|
window.location.href = currentUrl
|
||||||
<a class="dropdown-item" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''" th:text="#{home.compressPdfs.title}"></a>
|
.replace(
|
||||||
<a class="dropdown-item" href="#" th:href="@{remove-pages}" th:classappend="${currentPage}=='remove-pages' ? 'active' : ''" th:text="#{home.removePages.title}"></a>
|
/\?lang=\w{2,}/,
|
||||||
</div>
|
'?lang='
|
||||||
</li>
|
+ languageCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
<li class="nav-item"><a class="nav-link" href="#"
|
});
|
||||||
th:href="@{add-image}"
|
</script>
|
||||||
th:classappend="${currentPage}=='add-image' ? 'active' : ''"></a></li>
|
|
||||||
|
<script th:inline="javascript">
|
||||||
<li class="nav-item"><a class="nav-link" href="#"
|
function compareVersions(version1, version2) {
|
||||||
th:href="@{compress-pdf}"
|
const v1 = version1.split('.');
|
||||||
th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''"></a></li>
|
const v2 = version2.split('.');
|
||||||
|
|
||||||
<input type="checkbox" id="toggle-dark-mode" checked="true"
|
for (let i = 0; i < v1.length || i < v2.length; i++) {
|
||||||
th:onclick="javascript:toggleDarkMode()">
|
const n1 = parseInt(v1[i]) || 0;
|
||||||
<a class="nav-link" href="#" for="toggle-dark-mode" th:text="#{navbar.darkmode}"></a>
|
const n2 = parseInt(v2[i]) || 0;
|
||||||
|
|
||||||
|
if (n1 > n2) {
|
||||||
<li class="nav-item dropdown">
|
return 1;
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
} else if (n1 < n2) {
|
||||||
Language
|
return -1;
|
||||||
</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>
|
return 0;
|
||||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="de_DE">German</a>
|
}
|
||||||
|
|
||||||
</div>
|
async function getLatestReleaseVersion() {
|
||||||
</li>
|
const url = "https://api.github.com/repos/Frooodle/Stirling-PDF/releases/latest";
|
||||||
|
const response = await fetch(url);
|
||||||
</ul>
|
const data = await response.json();
|
||||||
|
return data.tag_name.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
<script>
|
const currentVersion = /*[[${@appVersion}]]*/ ''; // Replace with your current version number
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Get the dropdown items
|
async function checkForUpdate() {
|
||||||
var dropdownItems = document.querySelectorAll('.lang_dropdown-item');
|
const latestVersion = await getLatestReleaseVersion();
|
||||||
|
console.log("latestVersion=" + latestVersion)
|
||||||
// Loop through the dropdown items
|
console.log("currentVersion=" + currentVersion)
|
||||||
for (var i = 0; i < dropdownItems.length; i++) {
|
console.log("compareVersions(latestVersion, currentVersion) > 0)=" + compareVersions(latestVersion, currentVersion))
|
||||||
dropdownItems[i].classList.remove('active');
|
if (latestVersion != null && latestVersion != "" && compareVersions(latestVersion, currentVersion) > 0) {
|
||||||
if(dropdownItems[i].dataset.languageCode === localStorage.getItem('languageCode')){
|
document.getElementById("update-btn").style.visibility = "visible";
|
||||||
dropdownItems[i].classList.add('active');
|
console.log("visible")
|
||||||
}
|
} else {
|
||||||
// Add a click event listener to each dropdown item
|
document.getElementById("update-btn").style.visibility = "hidden";
|
||||||
dropdownItems[i].addEventListener('click', function(event) {
|
console.log("hidden")
|
||||||
|
}
|
||||||
|
}
|
||||||
// Prevent the default link behavior
|
|
||||||
event.preventDefault();
|
checkForUpdate();
|
||||||
|
</script>
|
||||||
// Get the language code
|
|
||||||
var languageCode = this.dataset.languageCode;
|
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||||
// Save the language code to local storage
|
<div class="container ">
|
||||||
localStorage.setItem('languageCode', languageCode);
|
|
||||||
|
<a class="navbar-brand" href="#" th:href="@{/}" th:text="${@navBarText}"></a>
|
||||||
// Get the current URL
|
|
||||||
var currentUrl = window.location.href;
|
<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>
|
||||||
// Check if the URL already contains a "?lang" query parameter
|
</button>
|
||||||
if (currentUrl.indexOf('?lang=') === -1) {
|
|
||||||
// Update the URL with the "?lang" query parameter
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
window.location.href = currentUrl + '?lang=' + languageCode;
|
|
||||||
} else {
|
<ul class="navbar-nav mr-auto flex-nowrap">
|
||||||
// Replace the "?lang" query parameter with the new value
|
|
||||||
window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode);
|
<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>
|
||||||
|
|
||||||
</script>
|
<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>
|
||||||
</div>
|
|
||||||
</div>
|
<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"
|
||||||
</nav>
|
aria-haspopup="true" aria-expanded="false" th:text="#{navbar.convert}"></a>
|
||||||
</div>
|
<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,66 +1,77 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title='')}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title='')}"></th:block>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.features-container {
|
.features-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr));
|
grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr));
|
||||||
gap: 25px 30px;
|
gap: 25px 30px;
|
||||||
}
|
}
|
||||||
.feature-card {
|
|
||||||
border: 1px solid rgba(0,0,0,.125);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
padding: 1.25rem;
|
|
||||||
|
|
||||||
display: flex;
|
.feature-card {
|
||||||
flex-direction: column;
|
border: 1px solid rgba(0, 0, 0, .125);
|
||||||
align-items: flex-start;
|
border-radius: 0.25rem;
|
||||||
}
|
padding: 1.25rem;
|
||||||
.feature-card .card-text {
|
display: flex;
|
||||||
flex: 1;
|
flex-direction: column;
|
||||||
}
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card .card-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<!-- Jumbotron -->
|
<!-- Jumbotron -->
|
||||||
<div class="jumbotron jumbotron-fluid" id="jumbotron">
|
<div class="jumbotron jumbotron-fluid" id="jumbotron">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="display-4">Stirling PDF</h1>
|
<h1 class="display-4" th:text="${@appName}"></h1>
|
||||||
<p class="lead" th:text="#{home.desc}"></p>
|
<p class="lead" th:text="${@homeText != 'null' and @homeText != null and @homeText != ''} ? ${@homeText} : #{home.desc}"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Features -->
|
<!-- Features -->
|
||||||
<div class="features-container container">
|
<div class="features-container container">
|
||||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs')}"></div>
|
|
||||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs')}"></div>
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs')}"></div>
|
||||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.rotate.title}, cardText=#{home.rotate.desc}, cardLink='rotate-pdf')}"></div>
|
|
||||||
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs')}"></div>
|
||||||
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.rotate.title}, cardText=#{home.rotate.desc}, cardLink='rotate-pdf')}"></div>
|
||||||
|
|
||||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.imageToPdf.title}, cardText=#{home.imageToPdf.desc}, cardLink='img-to-pdf')}"></div>
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.imageToPdf.title}, cardText=#{home.imageToPdf.desc}, cardLink='img-to-pdf')}"></div>
|
||||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfToImage.title}, cardText=#{home.pdfToImage.desc}, cardLink='pdf-to-img')}"></div>
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfToImage.title}, cardText=#{home.pdfToImage.desc}, cardLink='pdf-to-img')}"></div>
|
||||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfOrganiser.title}, cardText=#{home.pdfOrganiser.desc}, cardLink='pdf-organizer')}"></div>
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfOrganiser.title}, cardText=#{home.pdfOrganiser.desc}, cardLink='pdf-organizer')}"></div>
|
||||||
|
|
||||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.addImage.title}, cardText=#{home.addImage.desc}, cardLink='add-image')}"></div>
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.addImage.title}, cardText=#{home.addImage.desc}, cardLink='add-image')}"></div>
|
||||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.watermark.title}, cardText=#{home.watermark.desc}, cardLink='add-watermark')}"></div>
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.watermark.title}, cardText=#{home.watermark.desc}, cardLink='add-watermark')}"></div>
|
||||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions')}"></div>
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.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>
|
||||||
|
|
||||||
<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.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf')}"></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.changeMetadata.title}, cardText=#{home.changeMetadata.desc}, cardLink='change-metadata')}"></div>
|
||||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.removePassword.title}, cardText=#{home.removePassword.desc}, cardLink='remove-password')}"></div>
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions')}"></div>
|
||||||
|
|
||||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf')}"></div>
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.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>
|
<div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfToPDFA.title}, cardText=#{home.pdfToPDFA.desc}, cardLink='pdf-to-pdfa')}"></div>
|
||||||
</div>
|
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
|
||||||
</div>
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,133 +1,134 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{merge.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{merge.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
<body> <div id="page-container">
|
<body>
|
||||||
<div id="content-wrap">
|
<div id="page-container">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div id="content-wrap">
|
||||||
<br>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<div class="container" id="dropContainer">
|
<div class="container" id="dropContainer">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{merge.header}"></h2>
|
<h2 th:text="#{merge.header}"></h2>
|
||||||
<form action="merge-pdfs" method="post"
|
<form action="merge-pdfs" method="post" enctype="multipart/form-data">
|
||||||
enctype="multipart/form-data">
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label th:text="#{multiPdfDropPrompt}"></label>
|
||||||
<label th:text="#{multiPdfDropPrompt}"></label>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=true, accept='application/pdf')}"></div>
|
||||||
<div class="custom-file">
|
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
</div>
|
||||||
name="fileInput" multiple required> <label
|
<div class="form-group">
|
||||||
class="custom-file-label" th:text="#{pdfPrompt}">s</label>
|
<ul id="selectedFiles" class="list-group"></ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group text-center">
|
||||||
<div class="form-group">
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{merge.submit}"></button>
|
||||||
<ul id="selectedFiles" class="list-group"></ul>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
<div class="form-group text-center">
|
<style>
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{merge.submit}"></button>
|
|
||||||
</div>
|
.list-group-item {
|
||||||
</form>
|
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-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);
|
||||||
|
}
|
||||||
|
|
||||||
<script>
|
var moveUpButtons = document.querySelectorAll(".move-up");
|
||||||
document
|
for (var i = 0; i < moveUpButtons.length; i++) {
|
||||||
.getElementById("fileInput")
|
moveUpButtons[i].addEventListener("click", function(event) {
|
||||||
.addEventListener(
|
event.preventDefault();
|
||||||
"change",
|
var parent = this.closest(".list-group-item");
|
||||||
function() {
|
var grandParent = parent.parentNode;
|
||||||
var files = this.files;
|
if (parent.previousElementSibling) {
|
||||||
var list = document
|
grandParent.insertBefore(parent, parent.previousElementSibling);
|
||||||
.getElementById("selectedFiles");
|
updateFiles();
|
||||||
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">Move Up</button> <button class="btn btn-secondary move-down">Move Down</button></div>';
|
|
||||||
list.appendChild(item);
|
|
||||||
}
|
|
||||||
var moveUpButtons = document
|
|
||||||
.querySelectorAll(".move-up");
|
|
||||||
for (var i = 0; i < moveUpButtons.length; i++) {
|
|
||||||
moveUpButtons[i]
|
|
||||||
.addEventListener(
|
|
||||||
"click",
|
|
||||||
function(event) {
|
|
||||||
event
|
|
||||||
.preventDefault();
|
|
||||||
var parent = this.parentNode.parentNode;
|
|
||||||
var grandParent = parent.parentNode;
|
|
||||||
if (parent.previousElementSibling) {
|
|
||||||
grandParent
|
|
||||||
.insertBefore(
|
|
||||||
parent,
|
|
||||||
parent.previousElementSibling);
|
|
||||||
updateFiles();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var moveDownButtons = document
|
|
||||||
.querySelectorAll(".move-down");
|
|
||||||
for (var i = 0; i < moveDownButtons.length; i++) {
|
|
||||||
moveDownButtons[i]
|
|
||||||
.addEventListener(
|
|
||||||
"click",
|
|
||||||
function(event) {
|
|
||||||
event
|
|
||||||
.preventDefault();
|
|
||||||
var parent = this.parentNode.parentNode;
|
|
||||||
var grandParent = parent.parentNode;
|
|
||||||
if (parent.nextElementSibling) {
|
|
||||||
grandParent
|
|
||||||
.insertBefore(
|
|
||||||
parent.nextElementSibling,
|
|
||||||
parent);
|
|
||||||
updateFiles();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFiles() {
|
var moveDownButtons = document.querySelectorAll(".move-down");
|
||||||
var dataTransfer = new DataTransfer();
|
for (var i = 0; i < moveDownButtons.length; i++) {
|
||||||
var liElements = document
|
moveDownButtons[i].addEventListener("click", function(event) {
|
||||||
.querySelectorAll("#selectedFiles li");
|
event.preventDefault();
|
||||||
|
var parent = this.closest(".list-group-item");
|
||||||
|
var grandParent = parent.parentNode;
|
||||||
|
if (parent.nextElementSibling) {
|
||||||
|
grandParent.insertBefore(parent.nextElementSibling, parent);
|
||||||
|
updateFiles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < liElements.length; i++) {
|
function updateFiles() {
|
||||||
var fileNameFromList = liElements[i].innerText
|
var dataTransfer = new DataTransfer();
|
||||||
.replace(
|
var liElements = document.querySelectorAll("#selectedFiles li");
|
||||||
"\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>
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
71
src/main/resources/templates/ocr-pdf.html
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<!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=#{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,40 +1,33 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfOrganiser.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{pdfOrganiser.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
<body> <div id="page-container">
|
<body>
|
||||||
<div id="content-wrap">
|
<div id="page-container">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div id="content-wrap">
|
||||||
<br>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{pdfOrganiser.header}"></h2>
|
<h2 th:text="#{pdfOrganiser.header}"></h2>
|
||||||
|
|
||||||
<form th:action="@{rearrange-pages}" method="post"
|
|
||||||
enctype="multipart/form-data">
|
|
||||||
<div class="custom-file">
|
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
|
||||||
name="fileInput" required> <label
|
|
||||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label> <input type="text" class="form-control"
|
|
||||||
id="fileInput" name="pageOrder"
|
|
||||||
placeholder="(e.g. 1,3,2 or 4-8,2,10-12)" required>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
|
|
||||||
</form>
|
|
||||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
|
||||||
|
|
||||||
</div>
|
<form th:action="@{rearrange-pages}" method="post" enctype="multipart/form-data">
|
||||||
</div>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
</div>
|
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
<input type="text" class="form-control" id="fileInput" name="pageOrder" placeholder="(e.g. 1,3,2 or 4-8,2,10-12)" required>
|
||||||
</div>
|
</div>
|
||||||
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,40 +1,32 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{pageRemover.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{pageRemover.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
<body> <div id="page-container">
|
<body>
|
||||||
<div id="content-wrap">
|
<div id="page-container">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div id="content-wrap">
|
||||||
<br>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{pageRemover.header}"></h2>
|
<h2 th:text="#{pageRemover.header}"></h2>
|
||||||
|
|
||||||
<form th:action="@{remove-pages}" method="post"
|
<form th:action="@{remove-pages}" method="post" enctype="multipart/form-data">
|
||||||
enctype="multipart/form-data">
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
<div class="custom-file">
|
<div class="form-group">
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
<label for="pagesToDelete" th:text="#{pageRemover.pagesToDelete}"></label>
|
||||||
name="fileInput" required> <label
|
<input type="text" class="form-control" id="fileInput" name="pagesToDelete" placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
|
||||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
</div>
|
||||||
</div>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pageRemover.submit}"></button>
|
||||||
<div class="form-group">
|
</form>
|
||||||
<label for="pagesToDelete" th:text="#{pageRemover.pagesToDelete}"></label> <input type="text" class="form-control"
|
</div>
|
||||||
id="fileInput" name="pagesToDelete"
|
</div>
|
||||||
placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{pageRemover.submit}"></button>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
</form>
|
</div>
|
||||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,55 +1,55 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{rotate.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{rotate.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
<body> <div id="page-container">
|
<body>
|
||||||
<div id="content-wrap">
|
<div id="page-container">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div id="content-wrap">
|
||||||
<br>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{rotate.header}"></h2>
|
<h2 th:text="#{rotate.header}"></h2>
|
||||||
|
|
||||||
<form action="#" th:action="@{rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
|
<form action="#" th:action="@{rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
|
||||||
<div th:replace="fragments/common :: fileSelector(name='fileInput', multiple=false)"></div>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
<input type="hidden" id="angleInput" name="angle" value="0">
|
<input type="hidden" id="angleInput" name="angle" value="0">
|
||||||
|
|
||||||
<div id="editSection" style="display: none">
|
<div id="editSection" style="display: none">
|
||||||
<div class="previewContainer">
|
<div class="previewContainer">
|
||||||
<img id="pdf-preview"/>
|
<img id="pdf-preview" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="buttonContainer">
|
<div class="buttonContainer">
|
||||||
<button type="button" class="btn btn-secondary" onclick="rotate(-90)">
|
<button type="button" class="btn btn-secondary" onclick="rotate(-90)">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
|
||||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z"/>
|
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" />
|
||||||
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z"/>
|
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{rotate.submit}"></button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{rotate.submit}"></button>
|
||||||
<button type="button" class="btn btn-secondary" onclick="rotate(90)">
|
<button type="button" class="btn btn-secondary" onclick="rotate(90)">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
|
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
|
||||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
|
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const angleInput = document.getElementById("angleInput");
|
const angleInput = document.getElementById("angleInput");
|
||||||
const fileInput = document.getElementById("fileInput-input");
|
const fileInput = document.getElementById("fileInput-input");
|
||||||
const preview = document.getElementById("pdf-preview");
|
const preview = document.getElementById("pdf-preview");
|
||||||
@@ -96,36 +96,37 @@
|
|||||||
angleInput.value = newAngle;
|
angleInput.value = newAngle;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
#pdf-preview {
|
#pdf-preview {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
max-width: calc(100% - 30px);
|
max-width: calc(100% - 30px);
|
||||||
max-height: calc(100% - 30px);
|
max-height: calc(100% - 30px);
|
||||||
box-shadow: 0 0 4px rgba(100,100,100,.25);
|
box-shadow: 0 0 4px rgba(100, 100, 100, .25);
|
||||||
transition: rotate .3s;
|
transition: rotate .3s;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
translate: -50% -50%;
|
translate: -50% -50%;
|
||||||
}
|
}
|
||||||
.previewContainer {
|
|
||||||
aspect-ratio: 1;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid rgba(0,0,0,.125);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
margin: 1rem 0;
|
|
||||||
padding: 15px;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonContainer {
|
.previewContainer {
|
||||||
display: flex;
|
aspect-ratio: 1;
|
||||||
justify-content: space-around;
|
width: 100%;
|
||||||
}
|
border: 1px solid rgba(0, 0, 0, .125);
|
||||||
</style>
|
border-radius: 0.25rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 15px;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,96 +1,80 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{addPassword.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{addPassword.title})}"></th:block>
|
||||||
|
|
||||||
<body> <div id="page-container">
|
<body>
|
||||||
<div id="content-wrap">
|
<div id="page-container">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div id="content-wrap">
|
||||||
<br>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{addPassword.header}"></h2>
|
<h2 th:text="#{addPassword.header}"></h2>
|
||||||
|
|
||||||
<form action="add-password" method="post"
|
<form action="add-password" method="post" enctype="multipart/form-data">
|
||||||
enctype="multipart/form-data">
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label th:text="#{addPassword.selectText.1}"></label>
|
||||||
<label th:text="#{addPassword.selectText.1}"></label>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
<div class="custom-file">
|
</div>
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
<div class="form-group">
|
||||||
name="fileInput" required> <label
|
<label th:text="#{addPassword.selectText.2}"></label> <input type="password" class="form-control" id="password" name="password" required>
|
||||||
class="custom-file-label" th:text="#{pdfPrompt}"></label>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
</div>
|
<label th:text="#{addPassword.selectText.3}"></label> <select class="form-control" id="keyLength" name="keyLength">
|
||||||
<div class="form-group">
|
<option value="40">40</option>
|
||||||
<label th:text="#{addPassword.selectText.2}"></label> <input type="password"
|
<option value="128">128</option>
|
||||||
class="form-control" id="password" name="password" required>
|
<option value="256">256</option>
|
||||||
</div>
|
</select> <small class="form-text text-muted" th:text="#{addPassword.selectText.4}"></small>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label th:text="#{addPassword.selectText.3}"></label> <select class="form-control"
|
<div class="form-group">
|
||||||
id="keyLength" name="keyLength">
|
<label th:text="#{addPassword.selectText.5}"></label>
|
||||||
<option value="40">40</option>
|
<div class="form-check">
|
||||||
<option value="128">128</option>
|
<input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
|
||||||
<option value="256">256</option>
|
<label class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label>
|
||||||
</select> <small class="form-text text-muted" th:text="#{addPassword.selectText.4}"></small>
|
</div>
|
||||||
</div>
|
<div class="form-check">
|
||||||
<div class="form-group">
|
<input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
|
||||||
<label th:text="#{addPassword.selectText.5}"></label>
|
<label class="form-check-label" for="canExtractContent" th:text="#{addPassword.selectText.7}"></label>
|
||||||
<div class="form-check">
|
</div>
|
||||||
<input class="form-check-input" type="checkbox"
|
<div class="form-check">
|
||||||
id="canAssembleDocument" name="canAssembleDocument"> <label
|
<input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
|
||||||
class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label>
|
<label class="form-check-label" for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox"
|
<input class="form-check-input" type="checkbox" id="canFillInForm" name="canFillInForm">
|
||||||
id="canExtractContent" name="canExtractContent"> <label
|
<label class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></label>
|
||||||
class="form-check-label" for="canExtractContent" th:text="#{addPassword.selectText.7}"></label>
|
</div>
|
||||||
</div>
|
<div class="form-check">
|
||||||
<div class="form-check">
|
<input class="form-check-input" type="checkbox" id="canModify" name="canModify">
|
||||||
<input class="form-check-input" type="checkbox"
|
<label class="form-check-label" for="canModify" th:text="#{addPassword.selectText.10}"></label>
|
||||||
id="canExtractForAccessibility"
|
</div>
|
||||||
name="canExtractForAccessibility"> <label
|
<div class="form-check">
|
||||||
class="form-check-label" for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label>
|
<input class="form-check-input" type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations">
|
||||||
</div>
|
<label class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
|
||||||
<div class="form-check">
|
</div>
|
||||||
<input class="form-check-input" type="checkbox"
|
<div class="form-check">
|
||||||
id="canFillInForm" name="canFillInForm"> <label
|
<input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
|
||||||
class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></label>
|
<label class="form-check-label" for="canPrint" th:text="#{addPassword.selectText.12}"></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="canModify"
|
<input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
|
||||||
name="canModify"> <label class="form-check-label"
|
<label class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
|
||||||
for="canModify" th:text="#{addPassword.selectText.10}"></label>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox"
|
|
||||||
id="canModifyAnnotations" name="canModifyAnnotations"> <label
|
|
||||||
class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="canPrint"
|
|
||||||
name="canPrint"> <label class="form-check-label"
|
|
||||||
for="canPrint" th:text="#{addPassword.selectText.12}"></label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox"
|
|
||||||
id="canPrintFaithful" name="canPrintFaithful"> <label
|
|
||||||
class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div class="form-group text-center">
|
<div class="form-group text-center">
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{addPassword.submit}"></button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{addPassword.submit}"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,56 +1,91 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{watermark.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{watermark.title})}"></th:block>
|
||||||
|
|
||||||
<body> <div id="page-container">
|
<body>
|
||||||
<div id="content-wrap">
|
<div id="page-container">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div id="content-wrap">
|
||||||
<br>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{watermark.header}"></h2>
|
<h2 th:text="#{watermark.header}"></h2>
|
||||||
|
|
||||||
<form method="post" enctype="multipart/form-data" action="add-watermark">
|
<form method="post" enctype="multipart/form-data" action="add-watermark">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label th:text="#{watermark.selectText.1}"></label>
|
<label th:text="#{watermark.selectText.1}"></label>
|
||||||
<div class="custom-file">
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
</div>
|
||||||
name="fileInput" required> <label
|
<div class="form-group">
|
||||||
class="custom-file-label" th:text="#{pdfPrompt}"></label>
|
<label for="watermarkText" th:text="#{watermark.selectText.2}"></label>
|
||||||
</div>
|
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="watermarkText" th:text="#{watermark.selectText.2}"></label>
|
<label for="fontSize" th:text="#{watermark.selectText.3}"></label>
|
||||||
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required/>
|
<input type="text" id="fontSize" name="fontSize" class="form-control" value="30" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="fontSize" th:text="#{watermark.selectText.3}"></label>
|
<label for="opacity" th:text="#{watermark.selectText.7}"></label>
|
||||||
<input type="text" id="fontSize" name="fontSize" class="form-control" value="30"/>
|
<input type="text" id="opacity" name="opacityText" class="form-control" value="50" onblur="updateopacityValue()" />
|
||||||
</div>
|
<input type="hidden" id="opacityReal" name="opacity" value="0.5">
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label for="rotation" th:text="#{watermark.selectText.4}"></label>
|
|
||||||
<input type="text" id="rotation" name="rotation" class="form-control" value="45"/>
|
|
||||||
</div>
|
<script>
|
||||||
<div class="form-group">
|
const opacityInput = document.getElementById('opacity');
|
||||||
<label for="widthSpacer" th:text="#{watermark.selectText.5}"></label>
|
const opacityRealInput = document.getElementById('opacityReal');
|
||||||
<input type="text" id="widthSpacer" name="widthSpacer" class="form-control" value="50"/>
|
|
||||||
</div>
|
const updateopacityValue = () => {
|
||||||
<div class="form-group">
|
let percentageValue = parseFloat(opacityInput.value.replace('%', ''));
|
||||||
<label for="heightSpacer" th:text="#{watermark.selectText.6}"></label>
|
if (isNaN(percentageValue)) {
|
||||||
<input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50"/>
|
percentageValue = 0;
|
||||||
</div>
|
}
|
||||||
<div class="form-group text-center">
|
percentageValue = Math.min(Math.max(percentageValue, 0), 100);
|
||||||
<input type="submit" th:value="#{watermark.submit}" class="btn btn-primary"/>
|
opacityInput.value = `${percentageValue}`;
|
||||||
</div>
|
opacityRealInput.value = (percentageValue / 100).toFixed(2);
|
||||||
</form>
|
};
|
||||||
</div>
|
|
||||||
</div>
|
const appendPercentageSymbol = () => {
|
||||||
</div>
|
if (!opacityInput.value.endsWith('%')) {
|
||||||
</div>
|
opacityInput.value += '%';
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
}
|
||||||
</div>
|
};
|
||||||
|
|
||||||
|
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" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="widthSpacer" th:text="#{watermark.selectText.5}"></label>
|
||||||
|
<input type="text" id="widthSpacer" name="widthSpacer" class="form-control" value="50" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="heightSpacer" th:text="#{watermark.selectText.6}"></label>
|
||||||
|
<input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group text-center">
|
||||||
|
<input type="submit" id="submitBtn" th:value="#{watermark.submit}" class="btn btn-primary" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
263
src/main/resources/templates/security/change-metadata.html
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
<th:block th:insert="~{fragments/common :: head(title=#{changeMetadata.title})}"></th:block>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="page-container">
|
||||||
|
<div id="content-wrap">
|
||||||
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
<br> <br>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2 th:text="#{changeMetadata.header}"></h2>
|
||||||
|
|
||||||
|
<form method="post" id="form1" enctype="multipart/form-data" th:action="@{/update-metadata}">
|
||||||
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
|
<p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
|
||||||
|
|
||||||
|
<div class="form-group-inline form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="deleteAll" name="deleteAll">
|
||||||
|
<label class="ml-3" for="deleteAll" th:text="#{changeMetadata.selectText.2}" ></label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group-inline form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="customModeCheckbox">
|
||||||
|
<label class="ml-3" for="customModeCheckbox" th:text="#{changeMetadata.selectText.3}"></label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-check-label" for="author" th:text="#{changeMetadata.author}"></label>
|
||||||
|
<input type="text" class="form-control" id="author" name="author">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-check-label" for="creationDate" th:text="#{changeMetadata.creationDate}"></label>
|
||||||
|
<input type="text" class="form-control" id="creationDate" name="creationDate" placeholder="2020/12/25 18:30:59">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-check-label" for="creator" th:text="#{changeMetadata.creator}"></label>
|
||||||
|
<input type="text" class="form-control" id="creator" name="creator">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-check-label" for="keywords" th:text="#{changeMetadata.keywords}"></label>
|
||||||
|
<input type="text" class="form-control" id="keywords" name="keywords">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-check-label" for="modificationDate" th:text="#{changeMetadata.modDate}"></label>
|
||||||
|
<input type="text" class="form-control" id="modificationDate" name="modificationDate" placeholder="2020/12/25 18:30:59">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-check-label" for="producer" th:text="#{changeMetadata.producer}"></label>
|
||||||
|
<input type="text" class="form-control" id="producer" name="producer">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-check-label" for="subject" th:text="#{changeMetadata.subject}"></label>
|
||||||
|
<input type="text" class="form-control" id="subject" name="subject">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-check-label" for="title" th:text="#{changeMetadata.title}"></label>
|
||||||
|
<input type="text" class="form-control" id="title" name="title">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-check-label" for="trapped" th:text="#{changeMetadata.trapped}"></label>
|
||||||
|
<select class="form-control" id="trapped" name="trapped">
|
||||||
|
<option value="True" th:text="#{true}"></option>
|
||||||
|
<option value="False" th:text="#{false}" selected></option>
|
||||||
|
<option value="Unknown" th:text="#{unknown}"></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="customMetadata" style="display: none;">
|
||||||
|
<h3 th:text="#{changeMetadata.selectText.4}"></h3>
|
||||||
|
<div class="form-group" id="otherMetadataEntries"></div>
|
||||||
|
</div>
|
||||||
|
<div id="customMetadataEntries"></div>
|
||||||
|
<button type="button" class="btn btn-secondary" id="addMetadataBtn" th:text="#{changeMetadata.selectText.5}"></button>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<button class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const deleteAllCheckbox = document.querySelector("#deleteAll");
|
||||||
|
const inputs = document.querySelectorAll(".form-control");
|
||||||
|
const customMetadataDiv = document.getElementById('customMetadata');
|
||||||
|
const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries');
|
||||||
|
|
||||||
|
deleteAllCheckbox.addEventListener("change", function(event) {
|
||||||
|
if (event.target !== deleteAllCheckbox) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.forEach(input => {
|
||||||
|
if (input === deleteAllCheckbox) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
input.disabled = deleteAllCheckbox.checked;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const customModeCheckbox = document.getElementById('customModeCheckbox');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const addMetadataBtn = document.getElementById("addMetadataBtn");
|
||||||
|
const customMetadataFormContainer = document.getElementById("customMetadataEntries");
|
||||||
|
var count = 1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const fileInput = document.querySelector("#fileInput-input");
|
||||||
|
const authorInput = document.querySelector("#author");
|
||||||
|
const creationDateInput = document.querySelector("#creationDate");
|
||||||
|
const creatorInput = document.querySelector("#creator");
|
||||||
|
const keywordsInput = document.querySelector("#keywords");
|
||||||
|
const modificationDateInput = document.querySelector("#modificationDate");
|
||||||
|
const producerInput = document.querySelector("#producer");
|
||||||
|
const subjectInput = document.querySelector("#subject");
|
||||||
|
const titleInput = document.querySelector("#title");
|
||||||
|
const trappedInput = document.querySelector("#trapped");
|
||||||
|
|
||||||
|
var lastPDFFileMeta = null;
|
||||||
|
fileInput.addEventListener("change", async function() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
while (otherMetadataEntriesDiv.firstChild) {
|
||||||
|
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
|
||||||
|
}
|
||||||
|
while (customMetadataFormContainer.firstChild) {
|
||||||
|
customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const file = this.files[0];
|
||||||
|
var url = URL.createObjectURL(file)
|
||||||
|
|
||||||
|
const pdf = await pdfjsLib.getDocument(url).promise;
|
||||||
|
const pdfMetadata = await pdf.getMetadata();
|
||||||
|
lastPDFFile = pdfMetadata?.info
|
||||||
|
console.log(pdfMetadata);
|
||||||
|
if(!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) {
|
||||||
|
customModeCheckbox.disabled = true;
|
||||||
|
customModeCheckbox.checked = false;
|
||||||
|
} else {
|
||||||
|
customModeCheckbox.disabled = false;
|
||||||
|
}
|
||||||
|
authorInput.value = pdfMetadata?.info?.Author;
|
||||||
|
creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate);
|
||||||
|
creatorInput.value = pdfMetadata?.info?.Creator;
|
||||||
|
keywordsInput.value = pdfMetadata?.info?.Keywords;
|
||||||
|
modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate);
|
||||||
|
producerInput.value = pdfMetadata?.info?.Producer;
|
||||||
|
subjectInput.value = pdfMetadata?.info?.Subject;
|
||||||
|
titleInput.value = pdfMetadata?.info?.Title;
|
||||||
|
console.log(pdfMetadata?.info);
|
||||||
|
const trappedValue = pdfMetadata?.info?.Trapped;
|
||||||
|
// Get all options in the select element
|
||||||
|
const options = trappedInput.options;
|
||||||
|
// Loop through all options to find the one with a matching value
|
||||||
|
for (let i = 0; i < options.length; i++) {
|
||||||
|
if (options[i].value === trappedValue) {
|
||||||
|
options[i].selected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addExtra();
|
||||||
|
});
|
||||||
|
|
||||||
|
addMetadataBtn.addEventListener("click", () => {
|
||||||
|
|
||||||
|
const keyInput = document.createElement("input");
|
||||||
|
keyInput.type = "text";
|
||||||
|
keyInput.placeholder = 'Key';
|
||||||
|
keyInput.className = "form-control";
|
||||||
|
keyInput.name = "customKey" + count;
|
||||||
|
|
||||||
|
const valueInput = document.createElement("input");
|
||||||
|
valueInput.type = "text";
|
||||||
|
valueInput.placeholder = 'Value';
|
||||||
|
valueInput.className = "form-control";
|
||||||
|
valueInput.name = "customValue" + count;
|
||||||
|
count = count + 1;
|
||||||
|
|
||||||
|
const formGroup = document.createElement("div");
|
||||||
|
formGroup.className = "form-group";
|
||||||
|
formGroup.appendChild(keyInput);
|
||||||
|
formGroup.appendChild(valueInput);
|
||||||
|
|
||||||
|
|
||||||
|
customMetadataFormContainer.appendChild(formGroup);
|
||||||
|
});
|
||||||
|
function convertDateFormat(dateTimeString) {
|
||||||
|
if (!dateTimeString || dateTimeString.length < 17) {
|
||||||
|
return dateTimeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const year = dateTimeString.substring(2, 6);
|
||||||
|
const month = dateTimeString.substring(6, 8);
|
||||||
|
const day = dateTimeString.substring(8, 10);
|
||||||
|
const hour = dateTimeString.substring(10, 12);
|
||||||
|
const minute = dateTimeString.substring(12, 14);
|
||||||
|
const second = dateTimeString.substring(14, 16);
|
||||||
|
|
||||||
|
return year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addExtra() {
|
||||||
|
const event = document.getElementById("customModeCheckbox");
|
||||||
|
|
||||||
|
|
||||||
|
if (event.checked && lastPDFFile.Custom != null) {
|
||||||
|
customMetadataDiv.style.display = 'block';
|
||||||
|
for (const [key, value] of Object.entries(lastPDFFile.Custom)) {
|
||||||
|
if (key === 'Author' || key === 'CreationDate' || key === 'Creator' || key === 'Keywords' || key === 'ModDate' || key === 'Producer' || key === 'Subject' || key === 'Title' || key === 'Trapped') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const entryDiv = document.createElement('div');
|
||||||
|
entryDiv.className = 'form-group';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
entryDiv.innerHTML = `<div class="form-group"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
|
||||||
|
otherMetadataEntriesDiv.appendChild(entryDiv);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
customMetadataDiv.style.display = 'none';
|
||||||
|
while (otherMetadataEntriesDiv.firstChild) {
|
||||||
|
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customModeCheckbox.addEventListener('change', (event) => {
|
||||||
|
|
||||||
|
addExtra();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,84 +1,71 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{permissions.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{permissions.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
<body> <div id="page-container">
|
<body>
|
||||||
<div id="content-wrap">
|
<div id="page-container">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div id="content-wrap">
|
||||||
<br>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{permissions.header}"></h2>
|
<h2 th:text="#{permissions.header}"></h2>
|
||||||
<p th:text="#{permissions.warning}"></p>
|
<p th:text="#{permissions.warning}"></p>
|
||||||
<form action="add-password" method="post"
|
<form action="add-password" method="post" enctype="multipart/form-data">
|
||||||
enctype="multipart/form-data">
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label th:text="#{permissions.selectText.1}"></label>
|
||||||
<label th:text="#{permissions.selectText.1}"></label>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
<div class="custom-file">
|
</div>
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
<div class="form-group">
|
||||||
name="fileInput"> <label class="custom-file-label" th:text="#{pdfPrompt}"></label>
|
<label th:text="#{permissions.selectText.2}"></label>
|
||||||
</div>
|
<div class="form-check">
|
||||||
</div>
|
<input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
|
||||||
<div class="form-group">
|
<label class="form-check-label" for="canAssembleDocument" th:text="#{permissions.selectText.3}"></label>
|
||||||
<label th:text="#{permissions.selectText.2}"></label>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox"
|
<input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
|
||||||
id="canAssembleDocument" name="canAssembleDocument"> <label
|
<label class="form-check-label" for="canExtractContent" th:text="#{permissions.selectText.4}"></label>
|
||||||
class="form-check-label" for="canAssembleDocument" th:text="#{permissions.selectText.3}"></label>
|
</div>
|
||||||
</div>
|
<div class="form-check">
|
||||||
<div class="form-check">
|
<input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
|
||||||
<input class="form-check-input" type="checkbox"
|
<label class="form-check-label" for="canExtractForAccessibility" th:text="#{permissions.selectText.5}"></label>
|
||||||
id="canExtractContent" name="canExtractContent"> <label
|
</div>
|
||||||
class="form-check-label" for="canExtractContent" th:text="#{permissions.selectText.4}"></label>
|
<div class="form-check">
|
||||||
</div>
|
<input class="form-check-input" type="checkbox" id="canFillInForm" name="canFillInForm">
|
||||||
<div class="form-check">
|
<label class="form-check-label" for="canFillInForm" th:text="#{permissions.selectText.6}"></label>
|
||||||
<input class="form-check-input" type="checkbox"
|
</div>
|
||||||
id="canExtractForAccessibility"
|
<div class="form-check">
|
||||||
name="canExtractForAccessibility"> <label
|
<input class="form-check-input" type="checkbox" id="canModify" name="canModify">
|
||||||
class="form-check-label" for="canExtractForAccessibility" th:text="#{permissions.selectText.5}"></label>
|
<label class="form-check-label" for="canModify" th:text="#{permissions.selectText.7}"></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox"
|
<input class="form-check-input" type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations">
|
||||||
id="canFillInForm" name="canFillInForm"> <label
|
<label class="form-check-label" for="canModifyAnnotations" th:text="#{permissions.selectText.8}"></label>
|
||||||
class="form-check-label" for="canFillInForm" th:text="#{permissions.selectText.6}"></label>
|
</div>
|
||||||
</div>
|
<div class="form-check">
|
||||||
<div class="form-check">
|
<input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
|
||||||
<input class="form-check-input" type="checkbox" id="canModify"
|
<label class="form-check-label" for="canPrint" th:text="#{permissions.selectText.9}"></label>
|
||||||
name="canModify"> <label class="form-check-label"
|
</div>
|
||||||
for="canModify" th:text="#{permissions.selectText.7}"></label>
|
<div class="form-check">
|
||||||
</div>
|
<input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
|
||||||
<div class="form-check">
|
<label class="form-check-label" for="canPrintFaithful" th:text="#{permissions.selectText.10}"></label>
|
||||||
<input class="form-check-input" type="checkbox"
|
</div>
|
||||||
id="canModifyAnnotations" name="canModifyAnnotations"> <label
|
|
||||||
class="form-check-label" for="canModifyAnnotations" th:text="#{permissions.selectText.8}"></label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="canPrint"
|
|
||||||
name="canPrint"> <label class="form-check-label"
|
|
||||||
for="canPrint" th:text="#{permissions.selectText.9}"></label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox"
|
|
||||||
id="canPrintFaithful" name="canPrintFaithful"> <label
|
|
||||||
class="form-check-label" for="canPrintFaithful" th:text="#{permissions.selectText.10}"></label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div class="form-group text-center">
|
<div class="form-group text-center">
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{permissions.submit}"></button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{permissions.submit}"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,42 +1,37 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{removePassword.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{removePassword.title})}"></th:block>
|
||||||
|
|
||||||
<body> <div id="page-container">
|
<body>
|
||||||
<div id="content-wrap">
|
<div id="page-container">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div id="content-wrap">
|
||||||
<br>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{removePassword.header}"></h2>
|
<h2 th:text="#{removePassword.header}"></h2>
|
||||||
|
|
||||||
<form action="add-password" method="post"
|
<form action="remove-password" method="post" enctype="multipart/form-data">
|
||||||
enctype="multipart/form-data">
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label th:text="#{removePassword.selectText.1}"></label>
|
||||||
<label th:text="#{removePassword.selectText.1}"></label>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
<div class="custom-file">
|
</div>
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
<div class="form-group">
|
||||||
name="fileInput" required> <label
|
<label th:text="#{removePassword.selectText.2}"></label>
|
||||||
class="custom-file-label" th:text="#{pdfPrompt}"></label>
|
<input type="password" class="form-control" id="password" name="password" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<br />
|
||||||
<div class="form-group">
|
<div class="form-group text-center">
|
||||||
<label th:text="#{removePassword.selectText.2}"></label> <input type="password"
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{removePassword.submit}"></button>
|
||||||
class="form-control" id="password" name="password" required>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
<br />
|
</div>
|
||||||
<div class="form-group text-center">
|
</div>
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{removePassword.submit}"></button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
36
src/main/resources/templates/security/remove-watermark.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!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=#{remove-watermark.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="#{remove-watermark.header}"></h2>
|
||||||
|
|
||||||
|
<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, 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" id="submitBtn" th:value="#{remove-watermark.submit}" class="btn btn-primary" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,51 +1,43 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{split.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{split.title})}"></th:block>
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br>
|
<br> <br>
|
||||||
<br>
|
<div class="container">
|
||||||
<div class="container">
|
<div class="row justify-content-center">
|
||||||
<div class="row justify-content-center">
|
<div class="col-md-6">
|
||||||
<div class="col-md-6">
|
<h1 th:text="#{split.header}"></h1>
|
||||||
<h1 th:text="#{split.header}"></h1>
|
<p th:text="#{split.desc.1}"></p>
|
||||||
<p th:text="#{split.desc.1}"></p>
|
<p th:text="#{split.desc.2}"></p>
|
||||||
<p th:text="#{split.desc.2}"></p>
|
<p th:text="#{split.desc.3}"></p>
|
||||||
<p th:text="#{split.desc.3}"></p>
|
<p th:text="#{split.desc.4}"></p>
|
||||||
<p th:text="#{split.desc.4}"></p>
|
<p th:text="#{split.desc.5}"></p>
|
||||||
<p th:text="#{split.desc.5}"></p>
|
<p th:text="#{split.desc.6}"></p>
|
||||||
<p th:text="#{split.desc.6}"></p>
|
<p th:text="#{split.desc.7}"></p>
|
||||||
<p th:text="#{split.desc.7}"></p>
|
<p th:text="#{split.desc.8}"></p>
|
||||||
<p th:text="#{split.desc.8}"></p>
|
|
||||||
|
|
||||||
<form th:action="@{split-pages}" method="post"
|
<form th:action="@{split-pages}" method="post" enctype="multipart/form-data">
|
||||||
enctype="multipart/form-data">
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
<div class="custom-file">
|
|
||||||
<input type="file" class="custom-file-input" id="fileInput"
|
|
||||||
name="fileInput" required> <label
|
|
||||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="pages" th:text="#{split.splitPages}"></label> <input
|
<label for="pages" th:text="#{split.splitPages}"></label>
|
||||||
type="text" class="form-control" id="pages" name="pages"
|
<input type="text" class="form-control" id="pages" name="pages" placeholder="1,3,5-10" required>
|
||||||
placeholder="1,3,5-10" required>
|
</div>
|
||||||
</div>
|
<br>
|
||||||
<br>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split.submit}"></button>
|
||||||
<button type="submit" class="btn btn-primary" th:text="#{split.submit}"></button>
|
</form>
|
||||||
</form>
|
</div>
|
||||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||