Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
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
17
.github/workflows/codeql.yml
vendored
@@ -7,9 +7,8 @@
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
name: "Build repo"
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -42,15 +41,15 @@ jobs:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: java
|
||||
# - name: Initialize CodeQL
|
||||
# uses: github/codeql-action/init@v2
|
||||
# with:
|
||||
# languages: java
|
||||
|
||||
- uses: gradle/gradle-build-action@v2.3.3
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
arguments: assemble --no-build-cache
|
||||
|
||||
- name: Perform CodeQL analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
#- name: Perform CodeQL analysis
|
||||
# uses: github/codeql-action/analyze@v2
|
||||
|
||||
32
.github/workflows/push-docker.yml
vendored
32
.github/workflows/push-docker.yml
vendored
@@ -39,18 +39,18 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
|
||||
- name: Check if tag exists
|
||||
id: checkIdExists
|
||||
continue-on-error: true
|
||||
run: |
|
||||
response=$(curl -s https://hub.docker.com/v2/repositories/frooodle/s-pdf/tags/?name=${{ steps.versionNumber.outputs.versionNumber }})
|
||||
result=$(echo $response | jq ".results")
|
||||
if [ "$result" == "[]" ]; then
|
||||
echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} doesnt exist. Continuing with build and push."
|
||||
else
|
||||
echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} already exists. Skipping build and push."
|
||||
exit 1;
|
||||
fi
|
||||
# - name: Check if tag exists
|
||||
# id: checkIdExists
|
||||
# continue-on-error: true
|
||||
# run: |
|
||||
# response=$(curl -s https://hub.docker.com/v2/repositories/frooodle/s-pdf/tags/?name=${{ steps.versionNumber.outputs.versionNumber }})
|
||||
# result=$(echo $response | jq ".results")
|
||||
# if [ "$result" == "[]" ]; then
|
||||
# echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} doesnt exist. Continuing with build and push."
|
||||
# else
|
||||
# echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} already exists. Skipping build and push."
|
||||
# exit 1;
|
||||
# fi
|
||||
|
||||
|
||||
|
||||
@@ -60,7 +60,13 @@ jobs:
|
||||
docker buildx use mybuilder
|
||||
|
||||
- name: Build and push versioned amd64 and v8
|
||||
if: github.ref == 'refs/heads/main' && steps.checkIdExists.outcome != 'failure'
|
||||
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
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:${{ steps.versionNumber.outputs.versionNumber }}" .
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -108,3 +108,5 @@ local.properties
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
/build
|
||||
53
Dockerfile
53
Dockerfile
@@ -1,5 +1,58 @@
|
||||
# Build jbig2enc in a separate stage
|
||||
FROM debian:bullseye-slim as jbig2enc_builder
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
automake \
|
||||
autoconf \
|
||||
libtool \
|
||||
libleptonica-dev \
|
||||
pkg-config \
|
||||
ca-certificates \
|
||||
zlib1g-dev \
|
||||
make \
|
||||
g++
|
||||
|
||||
RUN git clone https://github.com/agl/jbig2enc && \
|
||||
cd jbig2enc && \
|
||||
./autogen.sh && \
|
||||
./configure && \
|
||||
make && \
|
||||
make install
|
||||
|
||||
# Main stage
|
||||
FROM openjdk:17-jdk-slim
|
||||
|
||||
# Install necessary dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libreoffice-core \
|
||||
libreoffice-common \
|
||||
libreoffice-writer \
|
||||
libreoffice-calc \
|
||||
libreoffice-impress \
|
||||
python3-uno \
|
||||
python3-pip \
|
||||
unoconv \
|
||||
pngquant \
|
||||
ocrmypdf && \
|
||||
pip install --user --upgrade ocrmypdf
|
||||
|
||||
# Copy the jbig2enc binary from the builder stage
|
||||
COPY --from=jbig2enc_builder /usr/local/bin/jbig2 /usr/local/bin/jbig2
|
||||
|
||||
# Copy the application JAR file
|
||||
COPY build/libs/*.jar app.jar
|
||||
|
||||
# Expose the application port
|
||||
EXPOSE 8080
|
||||
|
||||
# Set environment variables
|
||||
ENV LOG_LEVEL=INFO
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT ["java","-jar","/app.jar","-Dlogging.level=${LOG_LEVEL}"]
|
||||
|
||||
|
||||
|
||||
|
||||
49
HowToUseOCR.md
Normal file
49
HowToUseOCR.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# OCR Language Packs and Setup
|
||||
|
||||
This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker.
|
||||
|
||||
## How does the OCR Work
|
||||
Stirling-PDF uses OCRmyPDF which in turn uses tesseract for its text recognition.
|
||||
All credit goes to them for this awesome work!
|
||||
|
||||
## Language Packs
|
||||
|
||||
Tesseract OCR supports a variety of languages. You can find additional language packs in the Tesseract GitHub repositories:
|
||||
|
||||
- [tessdata_fast](https://github.com/tesseract-ocr/tessdata_fast): These language packs are smaller and faster to load, but may provide lower recognition accuracy.
|
||||
- [tessdata](https://github.com/tesseract-ocr/tessdata): These language packs are larger and provide better recognition accuracy, but may take longer to load.
|
||||
|
||||
Depending on your requirements, you can choose the appropriate language pack for your use case. By default Stirling-PDF uses the tessdata_fast eng but this can be replaced.
|
||||
|
||||
### Installing Language Packs
|
||||
|
||||
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
|
||||
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tesseract-ocr/4.00/tessdata`
|
||||
|
||||
#### Docker
|
||||
|
||||
If you are using Docker, you need to expose the Tesseract tessdata directory as a volume in order to use the additional language packs.
|
||||
#### Docker Compose
|
||||
Modify your `docker-compose.yml` file to include the following volume configuration:
|
||||
|
||||
|
||||
```yaml
|
||||
services:
|
||||
your_service_name:
|
||||
image: your_docker_image_name
|
||||
volumes:
|
||||
- /usr/share/tesseract-ocr/4.00/tessdata:/location/of/trainingData
|
||||
```
|
||||
|
||||
|
||||
#### Docker run
|
||||
Add the following to your existing docker run command
|
||||
```bash
|
||||
-v /usr/share/tesseract-ocr/4.00/tessdata:/location/of/trainingData
|
||||
```
|
||||
|
||||
#### Non-Docker
|
||||
If you are not using Docker, you need to install the OCR components, including the ocrmypdf app.
|
||||
You can see [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
[](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.
|
||||
|
||||
@@ -18,12 +23,13 @@ I will support and fix/add things to this if there is a demand [Discord](https:/
|
||||
- Merge multiple PDFs together into a single resultant file
|
||||
- Convert PDFs to and from images
|
||||
- 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.
|
||||
- Compressing PDFs to decrease their filesize.
|
||||
- Add and remove passwords
|
||||
- Set PDF Permissions
|
||||
- Add watermark(s)
|
||||
- Edit metadata
|
||||
- Dark mode support.
|
||||
|
||||
## Technologies used
|
||||
|
||||
23
build.gradle
23
build.gradle
@@ -1,32 +1,38 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.0.2'
|
||||
id 'org.springframework.boot' version '3.0.3'
|
||||
id 'io.spring.dependency-management' version '1.1.0'
|
||||
}
|
||||
|
||||
group = 'stirling.software'
|
||||
version = '0.3.2'
|
||||
version = '0.4.0'
|
||||
sourceCompatibility = '17'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://repo.e-iceblue.com/nexus/content/groups/public/"
|
||||
name "com.e-iceblue"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
|
||||
implementation 'org.apache.logging.log4j:log4j-core:2.20.0'
|
||||
|
||||
//general PDF
|
||||
implementation 'org.apache.pdfbox:pdfbox:2.0.27'
|
||||
implementation 'org.apache.logging.log4j:log4j-core:2.19.0'
|
||||
implementation 'e-iceblue:spire.pdf.free:5.1.0'
|
||||
implementation 'com.itextpdf:itextpdf:5.5.13.3'
|
||||
|
||||
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
manifest {
|
||||
attributes 'Implementation-Title': 'Stirling-PDF',
|
||||
'Implementation-Version': project.version
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
@@ -36,4 +42,3 @@ tasks.named('test') {
|
||||
task printVersion {
|
||||
println project.version
|
||||
}
|
||||
|
||||
|
||||
BIN
docs/stirling-pdf.png
Normal file
BIN
docs/stirling-pdf.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 92 KiB |
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
14
src/main/java/stirling/software/SPDF/config/AppConfig.java
Normal file
14
src/main/java/stirling/software/SPDF/config/AppConfig.java
Normal file
@@ -0,0 +1,14 @@
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
@@ -8,8 +10,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@Configuration
|
||||
public class Beans implements WebMvcConfigurer {
|
||||
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
package stirling.software.SPDF.controller;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDResources;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.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;
|
||||
@@ -18,15 +26,31 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.spire.pdf.PdfCompressionLevel;
|
||||
import com.spire.pdf.PdfDocument;
|
||||
import com.spire.pdf.PdfPageBase;
|
||||
import com.spire.pdf.exporting.PdfImageInfo;
|
||||
import com.spire.pdf.graphics.PdfBitmap;
|
||||
import com.itextpdf.text.DocumentException;
|
||||
import com.itextpdf.text.pdf.PdfReader;
|
||||
import com.itextpdf.text.pdf.PdfStamper;
|
||||
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
//import com.spire.pdf.*;
|
||||
@Controller
|
||||
public class CompressController {
|
||||
|
||||
@@ -38,37 +62,56 @@ public class CompressController {
|
||||
return "compress-pdf";
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/compress-pdf")
|
||||
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile pdfFile,
|
||||
@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
|
||||
PdfDocument document = new PdfDocument();
|
||||
document.loadFromBytes(pdfFile.getBytes());
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
inputFile.transferTo(tempInputFile.toFile());
|
||||
|
||||
// Compress PDF
|
||||
document.getFileInfo().setIncrementalUpdate(false);
|
||||
document.setCompressionLevel(PdfCompressionLevel.Best);
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
// compress PDF Images
|
||||
for (int i = 0; i < document.getPages().getCount(); i++) {
|
||||
// Prepare the OCRmyPDF command
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("ocrmypdf");
|
||||
command.add("--optimize");
|
||||
command.add(String.valueOf(optimizeLevel));
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_compressed.pdf");
|
||||
|
||||
if (jbig2Lossy != null && jbig2Lossy) {
|
||||
command.add("--jbig2-lossy");
|
||||
}
|
||||
|
||||
command.add(tempInputFile.toString());
|
||||
command.add(tempOutputFile.toString());
|
||||
|
||||
int returnCode = ProcessExecutor.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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -33,8 +33,7 @@ public class MergeController {
|
||||
}
|
||||
|
||||
@PostMapping("/merge-pdfs")
|
||||
public ResponseEntity<InputStreamResource> mergePdfs(@RequestParam("fileInput") MultipartFile[] files)
|
||||
throws IOException {
|
||||
public ResponseEntity<InputStreamResource> mergePdfs(@RequestParam("fileInput") MultipartFile[] files) throws IOException {
|
||||
// Read the input PDF files into PDDocument objects
|
||||
List<PDDocument> documents = new ArrayList<>();
|
||||
|
||||
@@ -49,8 +48,7 @@ public class MergeController {
|
||||
mergedDoc.close();
|
||||
|
||||
// Create an InputStreamResource from the merged PDF
|
||||
InputStreamResource resource = new InputStreamResource(
|
||||
new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
|
||||
InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
|
||||
|
||||
// Return the merged PDF as a response
|
||||
return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).body(resource);
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package stirling.software.SPDF.controller;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.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 java.io.FileOutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
//import com.spire.pdf.*;
|
||||
@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;
|
||||
}
|
||||
|
||||
@PostMapping("/ocr-pdf")
|
||||
public ResponseEntity<byte[]> processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile,
|
||||
@RequestParam("languages") List<String> selectedLanguages,
|
||||
@RequestParam(name = "sidecar", required = false) Boolean sidecar) throws IOException, InterruptedException {
|
||||
|
||||
//--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");
|
||||
|
||||
// Run OCR Command
|
||||
String languageOption = String.join("+", selectedLanguages);
|
||||
List<String> command = new ArrayList<>(Arrays.asList("ocrmypdf","--verbose", "2", "--language", languageOption,
|
||||
tempInputFile.toString(), tempOutputFile.toString()));
|
||||
String sidecarFile = tempOutputFile.toString().replace(".pdf", ".txt");
|
||||
if (sidecar != null && sidecar) {
|
||||
command.add("--sidecar");
|
||||
command.add(sidecarFile);
|
||||
}
|
||||
int returnCode = ProcessExecutor.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(sidecarFile);
|
||||
zipOut.putNextEntry(txtEntry);
|
||||
Files.copy(Paths.get(sidecarFile), zipOut);
|
||||
zipOut.closeEntry();
|
||||
}
|
||||
|
||||
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
||||
|
||||
// Clean up the temporary zip file
|
||||
Files.delete(tempZipFile);
|
||||
Files.delete(tempOutputFile);
|
||||
Files.delete(Paths.get(sidecarFile));
|
||||
|
||||
// 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.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@@ -29,15 +27,14 @@ public class OverlayImageController {
|
||||
}
|
||||
|
||||
@PostMapping("/add-image")
|
||||
public ResponseEntity<byte[]> overlayImage(@RequestParam("fileInput") MultipartFile pdfFile,
|
||||
@RequestParam("fileInput2") MultipartFile imageFile, @RequestParam("x") float x,
|
||||
public ResponseEntity<byte[]> overlayImage(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("fileInput2") MultipartFile imageFile, @RequestParam("x") float x,
|
||||
@RequestParam("y") float y) {
|
||||
try {
|
||||
byte[] pdfBytes = pdfFile.getBytes();
|
||||
byte[] imageBytes = imageFile.getBytes();
|
||||
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) {
|
||||
logger.error("Failed to add image to PDF", e);
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
|
||||
@@ -1,25 +1,10 @@
|
||||
package stirling.software.SPDF.controller;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Controller
|
||||
public class PdfController {
|
||||
@@ -31,7 +16,6 @@ public class PdfController {
|
||||
return "redirect:/";
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/")
|
||||
public String home(Model model) {
|
||||
model.addAttribute("currentPage", "home");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.controller;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -9,9 +8,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@@ -40,8 +36,7 @@ public class RearrangePagesPDFController {
|
||||
}
|
||||
|
||||
@PostMapping("/remove-pages")
|
||||
public ResponseEntity<byte[]> deletePages(@RequestParam("fileInput") MultipartFile pdfFile,
|
||||
@RequestParam("pagesToDelete") String pagesToDelete) throws IOException {
|
||||
public ResponseEntity<byte[]> deletePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pagesToDelete") String pagesToDelete) throws IOException {
|
||||
|
||||
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
||||
|
||||
@@ -54,13 +49,12 @@ public class RearrangePagesPDFController {
|
||||
int pageIndex = pagesToRemove.get(i);
|
||||
document.removePage(pageIndex);
|
||||
}
|
||||
|
||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_removed_pages.pdf");
|
||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
|
||||
|
||||
}
|
||||
|
||||
private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) {
|
||||
List<Integer> newPageOrder = new ArrayList<Integer>();
|
||||
List<Integer> newPageOrder = new ArrayList<>();
|
||||
// loop through the page order array
|
||||
for (String element : pageOrderArr) {
|
||||
// check if the element contains a range of pages
|
||||
@@ -88,8 +82,7 @@ public class RearrangePagesPDFController {
|
||||
}
|
||||
|
||||
@PostMapping("/rearrange-pages")
|
||||
public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile,
|
||||
@RequestParam("pageOrder") String pageOrder) {
|
||||
public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) {
|
||||
try {
|
||||
// Load the input PDF
|
||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||
@@ -117,7 +110,7 @@ public class RearrangePagesPDFController {
|
||||
document.addPage(page);
|
||||
}
|
||||
|
||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_rearranged.pdf");
|
||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf");
|
||||
} catch (IOException e) {
|
||||
|
||||
logger.error("Failed rearranging documents", e);
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
package stirling.software.SPDF.controller;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@@ -36,8 +29,7 @@ public class RotationController {
|
||||
}
|
||||
|
||||
@PostMapping("/rotate-pdf")
|
||||
public ResponseEntity<byte[]> rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile,
|
||||
@RequestParam("angle") Integer angle) throws IOException {
|
||||
public ResponseEntity<byte[]> rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("angle") Integer angle) throws IOException {
|
||||
|
||||
// Load the PDF document
|
||||
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
||||
@@ -45,15 +37,11 @@ public class RotationController {
|
||||
// Get the list of pages in the document
|
||||
PDPageTree pages = document.getPages();
|
||||
|
||||
// Rotate all pages by the specified angle
|
||||
Iterator<PDPage> iterPage = pages.iterator();
|
||||
|
||||
while (iterPage.hasNext()) {
|
||||
PDPage page = iterPage.next();
|
||||
for (PDPage page : pages) {
|
||||
page.setRotation(page.getRotation() + angle);
|
||||
}
|
||||
|
||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_rotated.pdf");
|
||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -46,8 +46,7 @@ public class SplitPDFController {
|
||||
}
|
||||
|
||||
@PostMapping("/split-pages")
|
||||
public ResponseEntity<Resource> splitPdf(@RequestParam("fileInput") MultipartFile file,
|
||||
@RequestParam("pages") String pages) throws IOException {
|
||||
public ResponseEntity<Resource> splitPdf(@RequestParam("fileInput") MultipartFile file, @RequestParam("pages") String pages) throws IOException {
|
||||
// parse user input
|
||||
|
||||
// open the pdf document
|
||||
@@ -80,8 +79,7 @@ public class SplitPDFController {
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Splitting PDF into pages: {}",
|
||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
|
||||
// split the document
|
||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||
@@ -136,7 +134,7 @@ public class SplitPDFController {
|
||||
ByteArrayResource resource = new ByteArrayResource(data);
|
||||
new File("split_documents.zip").delete();
|
||||
// return the Resource in the response
|
||||
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=split_documents.zip")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource);
|
||||
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + 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 org.apache.pdfbox.rendering.ImageType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -40,20 +43,44 @@ public class ConvertImgPDFController {
|
||||
byte[] bytes = PdfUtils.convertToPdf(file.getInputStream());
|
||||
logger.info("File {} successfully converted to pdf", file.getOriginalFilename());
|
||||
|
||||
return PdfUtils.bytesToWebResponse(bytes, file.getName() + "_coverted.pdf");
|
||||
return PdfUtils.bytesToWebResponse(bytes, file.getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_coverted.pdf");
|
||||
}
|
||||
|
||||
@PostMapping("/pdf-to-img")
|
||||
public ResponseEntity<byte[]> convertToImage(@RequestParam("fileInput") MultipartFile file,
|
||||
@RequestParam("imageFormat") String imageFormat) throws IOException {
|
||||
public ResponseEntity<Resource> convertToImage(@RequestParam("fileInput") MultipartFile file, @RequestParam("imageFormat") String imageFormat,
|
||||
@RequestParam("singleOrMultiple") String singleOrMultiple, @RequestParam("colorType") String colorType, @RequestParam("dpi") String dpi) throws IOException {
|
||||
|
||||
byte[] pdfBytes = file.getBytes();
|
||||
ImageType colorTypeResult = ImageType.RGB;
|
||||
if ("greyscale".equals(colorType)) {
|
||||
colorTypeResult = ImageType.GRAY;
|
||||
} else if ("blackwhite".equals(colorType)) {
|
||||
colorTypeResult = ImageType.BINARY;
|
||||
}
|
||||
// returns bytes for image
|
||||
byte[] result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toLowerCase());
|
||||
boolean singleImage = singleOrMultiple.equals("single");
|
||||
byte[] result = null;
|
||||
try {
|
||||
result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.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)));
|
||||
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
|
||||
ResponseEntity<byte[]> response = new ResponseEntity<>(result, headers, HttpStatus.OK);
|
||||
ResponseEntity<Resource> response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
|
||||
return response;
|
||||
} else {
|
||||
ByteArrayResource resource = new ByteArrayResource(result);
|
||||
// return the Resource in the response
|
||||
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="+ file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToImages.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.contentLength(resource.contentLength()).body(resource);
|
||||
}
|
||||
}
|
||||
|
||||
private String getMediaType(String imageFormat) {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package stirling.software.SPDF.controller.converters;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
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 org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import stirling.software.SPDF.LibreOfficeListener;
|
||||
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.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,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;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
||||
import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
|
||||
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@@ -47,25 +41,20 @@ public class PasswordController {
|
||||
}
|
||||
|
||||
@PostMapping("/remove-password")
|
||||
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput,
|
||||
@RequestParam(name = "password") String password) throws IOException {
|
||||
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(name = "password") String password) throws IOException {
|
||||
PDDocument document = PDDocument.load(fileInput.getBytes(), password);
|
||||
document.setAllSecurityToBeRemoved(true);
|
||||
return PdfUtils.pdfDocToWebResponse(document, fileInput.getName() + "_password_removed.pdf");
|
||||
return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_password_removed.pdf");
|
||||
}
|
||||
|
||||
@PostMapping("/add-password")
|
||||
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput,
|
||||
@RequestParam(defaultValue = "", name = "password") String password,
|
||||
@RequestParam(defaultValue = "128", name = "keyLength") int keyLength,
|
||||
@RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument,
|
||||
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(defaultValue = "", name = "password") String password,
|
||||
@RequestParam(defaultValue = "128", name = "keyLength") int keyLength, @RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument,
|
||||
@RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent,
|
||||
@RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility,
|
||||
@RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm,
|
||||
@RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
|
||||
@RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
|
||||
@RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations,
|
||||
@RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint,
|
||||
@RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
|
||||
@RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, @RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
|
||||
throws IOException {
|
||||
|
||||
PDDocument document = PDDocument.load(fileInput.getBytes());
|
||||
@@ -86,7 +75,7 @@ public class PasswordController {
|
||||
|
||||
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;
|
||||
|
||||
import java.awt.Color;
|
||||
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.PDDocumentCatalog;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
import org.apache.pdfbox.pdmodel.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.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;
|
||||
@@ -23,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.WatermarkRemover;
|
||||
|
||||
@Controller
|
||||
public class WatermarkController {
|
||||
@@ -33,13 +36,17 @@ public class WatermarkController {
|
||||
return "security/add-watermark";
|
||||
}
|
||||
|
||||
@GetMapping("/remove-watermark")
|
||||
public String removeWatermarkForm(Model model) {
|
||||
model.addAttribute("currentPage", "remove-watermark");
|
||||
return "security/remove-watermark";
|
||||
}
|
||||
|
||||
@PostMapping("/add-watermark")
|
||||
public ResponseEntity<byte[]> addWatermark(@RequestParam("fileInput") MultipartFile pdfFile,
|
||||
@RequestParam("watermarkText") String watermarkText,
|
||||
@RequestParam(defaultValue = "30", name = "fontSize") float fontSize,
|
||||
@RequestParam(defaultValue = "0", name = "rotation") float rotation,
|
||||
@RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer,
|
||||
@RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer) throws IOException {
|
||||
public ResponseEntity<byte[]> addWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText,
|
||||
@RequestParam(defaultValue = "30", name = "fontSize") float fontSize, @RequestParam(defaultValue = "0", name = "rotation") float rotation,
|
||||
@RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer, @RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer)
|
||||
throws IOException {
|
||||
|
||||
// Load the input PDF
|
||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||
@@ -47,8 +54,7 @@ public class WatermarkController {
|
||||
// Create a page in the document
|
||||
for (PDPage page : document.getPages()) {
|
||||
// Get the page's content stream
|
||||
PDPageContentStream contentStream = new PDPageContentStream(document, page,
|
||||
PDPageContentStream.AppendMode.APPEND, true);
|
||||
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
|
||||
|
||||
// Set font of watermark
|
||||
PDFont font = PDType1Font.HELVETICA_BOLD;
|
||||
@@ -67,8 +73,7 @@ public class WatermarkController {
|
||||
// Add the watermark text
|
||||
for (int i = 0; i < watermarkRows; i++) {
|
||||
for (int j = 0; j < watermarkCols; j++) {
|
||||
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation),
|
||||
j * watermarkWidth, i * watermarkHeight));
|
||||
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), j * watermarkWidth, i * watermarkHeight));
|
||||
contentStream.showTextWithPositioning(new Object[] { watermarkText });
|
||||
}
|
||||
}
|
||||
@@ -78,6 +83,64 @@ public class WatermarkController {
|
||||
// Close the content stream
|
||||
contentStream.close();
|
||||
}
|
||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_watermarked.pdf");
|
||||
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
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;
|
||||
|
||||
import java.awt.Graphics;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -7,12 +8,13 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
@@ -27,7 +29,9 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import com.spire.pdf.PdfDocument;
|
||||
import com.itextpdf.text.Document;
|
||||
import com.itextpdf.text.DocumentException;
|
||||
import com.itextpdf.text.pdf.PdfWriter;
|
||||
|
||||
public class PdfUtils {
|
||||
|
||||
@@ -76,24 +80,52 @@ public class PdfUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] convertFromPdf(byte[] inputStream, String imageType) throws IOException {
|
||||
public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI)
|
||||
throws IOException, Exception {
|
||||
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
|
||||
// Create a PDFRenderer to convert the PDF to an image
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(0, 300, ImageType.RGB);
|
||||
int pageCount = document.getNumberOfPages();
|
||||
List<BufferedImage> images = new ArrayList<>();
|
||||
|
||||
// Get an ImageWriter for the specified image type
|
||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(imageType);
|
||||
ImageWriter writer = writers.next();
|
||||
// Create images of all pages
|
||||
for (int i = 0; i < pageCount; i++) {
|
||||
images.add(pdfRenderer.renderImageWithDPI(i, 300, colorType));
|
||||
}
|
||||
|
||||
// Create a ByteArrayOutputStream to save the image to
|
||||
if (singleImage) {
|
||||
// Combine all images into a single big image
|
||||
BufferedImage combined = new BufferedImage(images.get(0).getWidth(), images.get(0).getHeight() * pageCount, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics g = combined.getGraphics();
|
||||
for (int i = 0; i < images.size(); i++) {
|
||||
g.drawImage(images.get(i), 0, i * images.get(0).getHeight(), null);
|
||||
}
|
||||
images = Arrays.asList(combined);
|
||||
}
|
||||
|
||||
// Create a ByteArrayOutputStream to save the image(s) to
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) {
|
||||
writer.setOutput(ios);
|
||||
if (singleImage) {
|
||||
// Write the image to the output stream
|
||||
writer.write(new IIOImage(bim, null, null));
|
||||
ImageIO.write(images.get(0), imageType, baos);
|
||||
|
||||
// Log that the image was successfully written to the byte array
|
||||
logger.info("Image successfully written to byte array");
|
||||
} else {
|
||||
// Zip the images and return as byte array
|
||||
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
|
||||
for (int i = 0; i < images.size(); i++) {
|
||||
BufferedImage image = images.get(i);
|
||||
try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) {
|
||||
ImageIO.write(image, 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) {
|
||||
@@ -108,8 +140,7 @@ public class PdfUtils {
|
||||
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) {
|
||||
// Get the first page of the PDF
|
||||
PDPage page = document.getPage(0);
|
||||
try (PDPageContentStream contentStream = new PDPageContentStream(document, page,
|
||||
PDPageContentStream.AppendMode.APPEND, true)) {
|
||||
try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true)) {
|
||||
// Create an image object from the image bytes
|
||||
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "");
|
||||
// Draw the image onto the page at the specified x and y coordinates
|
||||
@@ -128,17 +159,20 @@ public class PdfUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static ResponseEntity<byte[]> pdfDocToWebResponse(PdfDocument document, String docName) throws IOException {
|
||||
|
||||
// Open Byte Array and save document to it
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.saveToStream(baos);
|
||||
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
|
||||
@@ -150,8 +184,7 @@ public class PdfUtils {
|
||||
return PdfUtils.boasToWebResponse(baos, docName);
|
||||
}
|
||||
|
||||
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName)
|
||||
throws IOException {
|
||||
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException {
|
||||
return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.io.BufferedReader;
|
||||
import java.util.ArrayList;
|
||||
public class ProcessExecutor {
|
||||
public static int runCommandWithOutputHandling(List<String> command) throws IOException, InterruptedException {
|
||||
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
|
||||
int exitCode = process.waitFor();
|
||||
|
||||
// Wait for the reader threads to finish
|
||||
errorReaderThread.join();
|
||||
outputReaderThread.join();
|
||||
|
||||
if (outputLines.size() > 0) {
|
||||
String outputMessage = String.join("\n", outputLines);
|
||||
System.out.println("Command output:\n" + outputMessage);
|
||||
}
|
||||
|
||||
if (errorLines.size() > 0) {
|
||||
String errorMessage = String.join("\n", errorLines);
|
||||
System.out.println("Command error output:\n" + errorMessage);
|
||||
if (exitCode != 0) {
|
||||
throw new IOException("Command process failed with exit code " + exitCode + ". Error message: " + errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -9,3 +9,11 @@ spring.servlet.multipart.max-file-size=1000MB
|
||||
spring.servlet.multipart.max-request-size=1000MB
|
||||
|
||||
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
|
||||
@@ -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)
|
||||
|
||||
276
src/main/resources/messages_ar_AR.properties
Normal file
276
src/main/resources/messages_ar_AR.properties
Normal file
@@ -0,0 +1,276 @@
|
||||
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
|
||||
# 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
|
||||
home.ocr.desc=\u0645\u0633\u062D \u0648\u0627\u0643\u062A\u0634\u0627\u0641 \u0627\u0644\u0646\u0635 \u0645\u0646 \u0627\u0644\u0635\u0648\u0631 \u062F\u0627\u062E\u0644 PDF \u0648\u0625\u0639\u0627\u062F\u0629 \u0625\u0636\u0627\u0641\u062A\u0647 \u0643\u0646\u0635.
|
||||
|
||||
home.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.zip=\u0645\u0644\u0641\u0627\u062A \u0627\u0644\u062A\u0646\u0632\u064A\u0644 \u0627\u0644\u0645\u062A\u0639\u062F\u062F \u0627\u0644\u0645\u0636\u063A\u0648\u0637\u0629
|
||||
|
||||
#OCR
|
||||
OCR.title = OCR
|
||||
ocr.header=OCR (\u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u0623\u062D\u0631\u0641)
|
||||
ocr.selectText.1=\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0644\u063A\u0627\u062A \u0627\u0644\u062A\u064A \u0633\u064A\u062A\u0645 \u0627\u0643\u062A\u0634\u0627\u0641\u0647\u0627 \u062F\u0627\u062E\u0644 PDF (\u0627\u0644\u0644\u063A\u0627\u062A \u0627\u0644\u0645\u062F\u0631\u062C\u0629 \u0647\u064A \u062A\u0644\u0643 \u0627\u0644\u0645\u0643\u062A\u0634\u0641\u0629 \u062D\u0627\u0644\u064A\u0627):
|
||||
ocr.selectText.2=\u0625\u0646\u062A\u0627\u062C \u0645\u0644\u0641 \u0646\u0635\u064A \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0646\u0635 OCR \u062C\u0646\u0628\u0627 \u0625\u0644\u0649 \u062C\u0646\u0628 \u0645\u0639 \u0645\u0644\u0641 PDF OCR'ed
|
||||
ocr.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=ضغط ملف PDF
|
||||
compress.compressLevel=القيمة بين 1 و 100 (يتم تقليل 1 إلى أقصى حد)
|
||||
compress.submit=ضغط
|
||||
|
||||
|
||||
#merge
|
||||
merge.title=دمج
|
||||
merge.header=دمج ملفات PDF متعددة (2+)
|
||||
merge.submit=دمج
|
||||
|
||||
#pdfOrganiser
|
||||
pdfOrganiser.title=منظم الصفحة
|
||||
pdfOrganiser.header=منظم صفحات PDF
|
||||
pdfOrganiser.submit=إعادة ترتيب الصفحات
|
||||
|
||||
|
||||
#pageRemover
|
||||
pageRemover.title=مزيل الصفحة
|
||||
pageRemover.header=مزيل صفحة PDF
|
||||
pageRemover.pagesToDelete=الصفحات المراد حذفها (أدخل قائمة بأرقام الصفحات مفصولة بفواصل):
|
||||
pageRemover.submit=حذف الصفحات
|
||||
|
||||
#rotate
|
||||
rotate.title=تدوير PDF
|
||||
rotate.header=تدوير PDF
|
||||
rotate.selectAngle=حدد زاوية الدوران (بمضاعفات 90 درجة):
|
||||
rotate.submit=استدارة
|
||||
|
||||
|
||||
|
||||
|
||||
#merge
|
||||
split.title=انقسام PDF
|
||||
split.header=تقسيم PDF
|
||||
split.desc.1=الأرقام التي تحددها هي رقم الصفحة التي تريد تقسيمها
|
||||
split.desc.2=على هذا النحو ، سيؤدي تحديد 1،3،7-8 إلى تقسيم مستند من 10 صفحات إلى 6 PDFS منفصلة مع:
|
||||
split.desc.3=المستند رقم 1: الصفحة 1
|
||||
split.desc.4=المستند رقم 2: الصفحتان 2 و 3
|
||||
split.desc.5=المستند رقم 3: الصفحة 4 و 5 و 6
|
||||
split.desc.6=المستند رقم 4: الصفحة 7
|
||||
split.desc.7=المستند رقم 5: الصفحة 8
|
||||
split.desc.8=المستند رقم 6: الصفحتان 9 و 10
|
||||
split.splitPages=أدخل الصفحات المراد تقسيمها:
|
||||
split.submit=Split
|
||||
|
||||
|
||||
#merge
|
||||
imageToPDF.title=صورة إلى PDF
|
||||
imageToPDF.header=صورة إلى PDF
|
||||
imageToPDF.submit=تحول
|
||||
|
||||
#pdfToImage
|
||||
pdfToImage.title=تحويل PDF إلى صورة
|
||||
pdfToImage.header=تحويل PDF إلى صورة
|
||||
pdfToImage.selectText=تنسيق الصورة
|
||||
pdfToImage.singleOrMultiple = \u0646\u0648\u0639 \u0646\u062A\u064A\u062C\u0629 \u0627\u0644\u0635\u0648\u0631\u0629
|
||||
pdfToImage.single = \u0635\u0648\u0631\u0629 \u0648\u0627\u062D\u062F\u0629 \u0643\u0628\u064A\u0631\u0629
|
||||
pdfToImage.multi = \u0635\u0648\u0631 \u0645\u062A\u0639\u062F\u062F\u0629
|
||||
pdfToImage.colorType = \u0646\u0648\u0639 \u0627\u0644\u0644\u0648\u0646
|
||||
pdfToImage.color = \u0627\u0644\u0644\u0648\u0646
|
||||
pdfToImage.grey = \u062A\u062F\u0631\u062C \u0627\u0644\u0631\u0645\u0627\u062F\u064A
|
||||
pdfToImage.blackwhite = \u0623\u0628\u064A\u0636 \u0648\u0623\u0633\u0648\u062F (\u0642\u062F \u064A\u0641\u0642\u062F \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A!)
|
||||
pdfToImage.submit=تحول
|
||||
|
||||
#addPassword
|
||||
addPassword.title=إضافة كلمة مرور
|
||||
addPassword.header=إضافة كلمة مرور (تشفير)
|
||||
addPassword.selectText.1=حدد ملف PDF للتشفير
|
||||
addPassword.selectText.2=كلمة المرور
|
||||
addPassword.selectText.3=طول مفتاح التشفير
|
||||
addPassword.selectText.4=القيم الأعلى تكون أقوى ، لكن القيم الأقل لها توافق أفضل.
|
||||
addPassword.selectText.5=أذونات للتعيين
|
||||
addPassword.selectText.6=منع تجميع المستند
|
||||
addPassword.selectText.7=منع استخراج المحتوى
|
||||
addPassword.selectText.8=منع الاستخراج للوصول
|
||||
addPassword.selectText.9=منع ملء النموذج
|
||||
addPassword.selectText.10=منع التعديل
|
||||
addPassword.selectText.11=منع تعديل التعليقات التوضيحية
|
||||
addPassword.selectText.12=منع الطباعة
|
||||
addPassword.selectText.13=منع طباعة تنسيقات مختلفة
|
||||
addPassword.submit=تشفير
|
||||
|
||||
#watermark
|
||||
watermark.title=إضافة علامة مائية
|
||||
watermark.header=إضافة علامة مائية
|
||||
watermark.selectText.1=حدد PDF لإضافة العلامة المائية إلى:
|
||||
watermark.selectText.2=نص العلامة المائية:
|
||||
watermark.selectText.3=حجم الخط:
|
||||
watermark.selectText.4=دوران (0-360):
|
||||
watermark.selectText.5=widthSpacer (مسافة بين كل علامة مائية أفقيًا):
|
||||
watermark.selectText.6=heightSpacer (مسافة بين كل علامة مائية عموديًا):
|
||||
watermark.submit=إضافة علامة مائية
|
||||
|
||||
#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
|
||||
@@ -1,6 +1,9 @@
|
||||
###########
|
||||
# Generic #
|
||||
###########
|
||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||
language.direction=ltr
|
||||
|
||||
pdfPrompt=PDF auswählen
|
||||
multiPdfPrompt=PDFs auswählen(2+)
|
||||
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
|
||||
pageOrderPrompt=Seitenreihenfolge (Geben Sie eine durch Komma getrennte Liste von Seitenzahlen ein):
|
||||
goToPage=Los
|
||||
true=Wahr
|
||||
false=Falsch
|
||||
unknown=Unbekannt
|
||||
save=Speichern
|
||||
close=Schließen
|
||||
|
||||
#############
|
||||
# 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.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.desc=Die Berechtigungen für Ihr PDF-Dokument verändern.
|
||||
|
||||
@@ -58,6 +70,53 @@ home.removePassword.desc=Den Passwortschutz eines PDFs entfernen.
|
||||
home.compressPdfs.title=PDF komprimieren
|
||||
home.compressPdfs.desc=PDF komprimieren um die Dateigröße zu reduzieren.
|
||||
|
||||
home.changeMetadata.title=Metadaten ändern
|
||||
home.changeMetadata.desc=Ändern/Entfernen/Hinzufügen von Metadaten aus einem PDF-Dokument
|
||||
|
||||
home.fileToPDF.title=Datei in PDF konvertieren
|
||||
home.fileToPDF.desc=Konvertieren Sie nahezu jede Datei in PDF (DOCX, PNG, XLS, PPT, TXT und mehr)
|
||||
|
||||
home.ocr.title=OCR auf PDF ausführen
|
||||
home.ocr.desc=Scannt und erkennt Text aus Bildern in einer PDF-Datei und fügt ihn erneut als Text hinzu.
|
||||
|
||||
home.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.zip=Dateien mit mehrfachem Download zippen
|
||||
|
||||
#OCR
|
||||
ocr.title=OCR
|
||||
ocr.header=OCR (Optische Zeichenerkennung)
|
||||
ocr.selectText.1=Wählen Sie die Sprachen aus, die in der PDF-Datei erkannt werden sollen (die aufgelisteten sind die aktuell erkannten):
|
||||
ocr.selectText.2=Textdatei mit OCR-Text neben der OCR-PDF-Datei erstellen
|
||||
ocr.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
|
||||
@@ -80,7 +139,6 @@ merge.submit=Zusammenführen
|
||||
#pdfOrganiser
|
||||
pdfOrganiser.title=Seiten anordnen
|
||||
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
|
||||
|
||||
|
||||
@@ -123,6 +181,13 @@ imageToPDF.submit=Umwandeln
|
||||
pdfToImage.title=PDF zu Bild
|
||||
pdfToImage.header=PDF zu Bild
|
||||
pdfToImage.selectText=Bildformat
|
||||
pdfToImage.singleOrMultiple=Bildergebnistyp
|
||||
pdfToImage.single=Einzelnes großes Bild
|
||||
pdfToImage.multi=Mehrere Bilder
|
||||
pdfToImage.colorType=Farbtyp
|
||||
pdfToImage.color=Farbe
|
||||
pdfToImage.grey=Graustufen
|
||||
pdfToImage.blackwhite=Schwarzweiß (Datenverlust möglich!)
|
||||
pdfToImage.submit=Umwandeln
|
||||
|
||||
#addPassword
|
||||
@@ -154,6 +219,12 @@ watermark.selectText.5=breiteSpacer (horizontaler Abstand zwischen den einzelnen
|
||||
watermark.selectText.6=höheSpacer (vertikaler Abstand zwischen den einzelnen Wasserzeichen):
|
||||
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
|
||||
permissions.title=Berechtigungen ändern
|
||||
@@ -179,11 +250,30 @@ removePassword.selectText.2=Passwort
|
||||
removePassword.submit=Entfernen
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
changeMetadata.title=Metadaten ändern
|
||||
changeMetadata.header=Metadaten ändern
|
||||
changeMetadata.selectText.1=Bitte bearbeiten Sie die Variablen, die Sie ändern möchten
|
||||
changeMetadata.selectText.2=Alle Metadaten löschen
|
||||
changeMetadata.selectText.3=Benutzerdefinierte Metadaten anzeigen:
|
||||
changeMetadata.author=Autor:
|
||||
changeMetadata.creationDate=Erstellungsdatum (jjjj/MM/tt HH:mm:ss):
|
||||
changeMetadata.creator=Ersteller:
|
||||
changeMetadata.keywords=Schlüsselwörter:
|
||||
changeMetadata.modDate=Änderungsdatum (JJJJ/MM/TT HH:mm:ss):
|
||||
changeMetadata.producer=Produzent:
|
||||
changeMetadata.subject=Betreff:
|
||||
changeMetadata.title=Titel:
|
||||
changeMetadata.trapped=Gefangen:
|
||||
changeMetadata.selectText.4=Andere Metadaten:
|
||||
changeMetadata.selectText.5=Benutzerdefinierten Metadateneintrag hinzufügen
|
||||
changeMetadata.submit=Ändern
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
###########
|
||||
# Generic #
|
||||
###########
|
||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||
language.direction=ltr
|
||||
|
||||
pdfPrompt=Choose PDF
|
||||
multiPdfPrompt=Choose PDFs (2+)
|
||||
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
|
||||
@@ -8,7 +11,13 @@ imgPrompt=Choose Image
|
||||
genericSubmit=Submit
|
||||
processTimeWarning=Warning: This process can take up to a minute depending on file-size
|
||||
pageOrderPrompt=Page Order (Enter a comma-separated list of page numbers) :
|
||||
goToPage=go
|
||||
goToPage=Go
|
||||
true=True
|
||||
false=False
|
||||
unknown=Unknown
|
||||
save=Save
|
||||
close=Close
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -29,7 +38,7 @@ 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.imageToPdf.desc=Convert a image (PNG, JPEG, GIF) to PDF.
|
||||
|
||||
home.pdfToImage.title=PDF to Image
|
||||
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.desc=Add a custom watermark to your PDF document.
|
||||
|
||||
home.remove-watermark.title=Remove Watermark
|
||||
home.remove-watermark.desc=Remove watermarks from your PDF document.
|
||||
|
||||
home.permissions.title=Change Permissions
|
||||
home.permissions.desc=Change the permissions of your PDF document
|
||||
|
||||
@@ -58,6 +70,64 @@ home.removePassword.desc=Remove password protection from your PDF document.
|
||||
home.compressPdfs.title=Compress PDFs
|
||||
home.compressPdfs.desc=Compress PDFs to reduce their file size.
|
||||
|
||||
home.changeMetadata.title=Change Metadata
|
||||
home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document
|
||||
|
||||
home.fileToPDF.title=Convert file to PDF
|
||||
home.fileToPDF.desc=Convert nearly any file to PDF (DOCX, PNG, XLS, PPT, TXT and more)
|
||||
|
||||
home.ocr.title=Run OCR on PDF
|
||||
home.ocr.desc=Scans and detects text from images within a PDF and re-adds it as text.
|
||||
|
||||
home.extractImages.title=Extract Images
|
||||
home.extractImages.desc=Extracts all images from a PDF and saves them to zip
|
||||
|
||||
|
||||
navbar.settings=Settings
|
||||
settings.title=Settings
|
||||
settings.update=Update available
|
||||
settings.appVersion=App Version:
|
||||
settings.downloadOption.title=Choose download option (For single file non zip downloads):
|
||||
settings.downloadOption.1=Open in same window
|
||||
settings.downloadOption.2=Open in new window
|
||||
settings.downloadOption.3=Download file
|
||||
settings.zip=Zip multi-download files
|
||||
|
||||
#OCR
|
||||
ocr.title=OCR
|
||||
ocr.header=OCR (Optical Character Recognition)
|
||||
ocr.selectText.1=Select languages that are to be detected within the PDF (Ones listed are the ones currently detected):
|
||||
ocr.selectText.2=Produce text file containing OCR text alongside the OCR'ed PDF
|
||||
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
|
||||
ocr.credit=This service uses OCRmyPDF and Tesseract for OCR.
|
||||
ocr.submit=Process PDF with OCR
|
||||
|
||||
extractImages.title=Extract Images
|
||||
extractImages.header=Extract Images
|
||||
extractImages.selectText=Select image format to convert extracted images to
|
||||
extractImages.submit=Extract
|
||||
|
||||
|
||||
#File to PDF
|
||||
fileToPDF.title=File to PDF
|
||||
fileToPDF.header=Convert any file to PDF
|
||||
fileToPDF.credit=This service uses LibreOffice and Unoconv for file conversion.
|
||||
fileToPDF.supportedFileTypes=Supported file types should include the below however for a full updated list of supported formats, please refer to the LibreOffice documentation
|
||||
fileToPDF.submit=Convert to PDF
|
||||
|
||||
|
||||
#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
|
||||
@@ -65,12 +135,6 @@ 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
|
||||
@@ -122,6 +186,13 @@ imageToPDF.submit=Convert
|
||||
pdfToImage.title=PDF to Image
|
||||
pdfToImage.header=PDF to Image
|
||||
pdfToImage.selectText=Image Format
|
||||
pdfToImage.singleOrMultiple=Image result type
|
||||
pdfToImage.single=Single Big Image
|
||||
pdfToImage.multi=Multiple Images
|
||||
pdfToImage.colorType=Colour type
|
||||
pdfToImage.color=Colour
|
||||
pdfToImage.grey=Greyscale
|
||||
pdfToImage.blackwhite=Black and White (May lose data!)
|
||||
pdfToImage.submit=Convert
|
||||
|
||||
#addPassword
|
||||
@@ -153,6 +224,12 @@ watermark.selectText.5=widthSpacer (Space between each watermark horizontally):
|
||||
watermark.selectText.6=heightSpacer (Space between each watermark vertically):
|
||||
watermark.submit=Add Watermark
|
||||
|
||||
#remove-watermark
|
||||
remove-watermark.title=Remove Watermark
|
||||
remove-watermark.header=Remove Watermark
|
||||
remove-watermark.selectText.1=Select PDF to remove watermark from:
|
||||
remove-watermark.selectText.2=Watermark Text:
|
||||
remove-watermark.submit=Remove Watermark
|
||||
|
||||
#Change permissions
|
||||
permissions.title=Change Permissions
|
||||
@@ -177,11 +254,28 @@ removePassword.selectText.1=Select PDF to Decrypt
|
||||
removePassword.selectText.2=Password
|
||||
removePassword.submit=Remove
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
changeMetadata.title=Change Metadata
|
||||
changeMetadata.header=Change Metadata
|
||||
changeMetadata.selectText.1=Please edit the variables you wish to change
|
||||
changeMetadata.selectText.2=Delete all metadata
|
||||
changeMetadata.selectText.3=Show Custom Metadata:
|
||||
changeMetadata.author=Author:
|
||||
changeMetadata.creationDate=Creation Date (yyyy/MM/dd HH:mm:ss):
|
||||
changeMetadata.creator=Creator:
|
||||
changeMetadata.keywords=Keywords:
|
||||
changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss):
|
||||
changeMetadata.producer=Producer:
|
||||
changeMetadata.subject=Subject:
|
||||
changeMetadata.title=Title:
|
||||
changeMetadata.trapped=Trapped:
|
||||
changeMetadata.selectText.4=Other Metadata:
|
||||
changeMetadata.selectText.5=Add Custom Metadata Entry
|
||||
changeMetadata.submit=Change
|
||||
|
||||
xlsToPdf.title=Excel to PDF
|
||||
xlsToPdf.header=Excel to PDF
|
||||
xlsToPdf.selectText.1=Select XLS or XLSX Excel sheet to convert
|
||||
xlsToPdf.convert=convert
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
###########
|
||||
# Generic #
|
||||
###########
|
||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||
language.direction=ltr
|
||||
|
||||
pdfPrompt=Choose PDF
|
||||
multiPdfPrompt=Choose PDFs (2+)
|
||||
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
|
||||
@@ -8,7 +11,13 @@ imgPrompt=Choose Image
|
||||
genericSubmit=Submit
|
||||
processTimeWarning=Warning: This process can take up to a minute depending on file-size
|
||||
pageOrderPrompt=Page Order (Enter a comma-separated list of page numbers) :
|
||||
goToPage=go
|
||||
goToPage=Go
|
||||
true=True
|
||||
false=False
|
||||
unknown=Unknown
|
||||
save=Save
|
||||
close=Close
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -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.desc=Add a custom watermark to your PDF document.
|
||||
|
||||
home.remove-watermark.title=Remove Watermark
|
||||
home.remove-watermark.desc=Remove watermarks from your PDF document.
|
||||
|
||||
home.permissions.title=Change Permissions
|
||||
home.permissions.desc=Change the permissions of your PDF document
|
||||
|
||||
@@ -58,6 +70,50 @@ home.removePassword.desc=Remove password protection from your PDF document.
|
||||
home.compressPdfs.title=Compress PDFs
|
||||
home.compressPdfs.desc=Compress PDFs to reduce their file size.
|
||||
|
||||
home.changeMetadata.title=Change Metadata
|
||||
home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document
|
||||
|
||||
home.fileToPDF.title=Convert file to PDF
|
||||
home.fileToPDF.desc=Convert nearly any file to PDF (DOCX, PNG, XLS, PPT, TXT and more)
|
||||
|
||||
home.ocr.title=Run OCR on PDF
|
||||
home.ocr.desc=Scans and detects text from images within a PDF and re-adds it as text.
|
||||
|
||||
home.extractImages.title=Extract Images
|
||||
home.extractImages.desc=Extracts all images from a PDF and saves them to zip
|
||||
|
||||
|
||||
navbar.settings=Settings
|
||||
settings.title=Settings
|
||||
settings.update=Update available
|
||||
settings.appVersion=App Version:
|
||||
settings.downloadOption.title=Choose download option (For single file non zip downloads):
|
||||
settings.downloadOption.1=Open in same window
|
||||
settings.downloadOption.2=Open in new window
|
||||
settings.downloadOption.3=Download file
|
||||
settings.zip=Zip multi-download files
|
||||
|
||||
#OCR
|
||||
ocr.title=OCR
|
||||
ocr.header=OCR (Optical Character Recognition)
|
||||
ocr.selectText.1=Select languages that are to be detected within the PDF (Ones listed are the ones currently detected):
|
||||
ocr.selectText.2=Produce text file containing OCR text alongside the OCR'ed PDF
|
||||
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
|
||||
ocr.credit=This service uses OCRmyPDF and Tesseract for OCR.
|
||||
ocr.submit=Process PDF with OCR
|
||||
|
||||
extractImages.title=Extract Images
|
||||
extractImages.header=Extract Images
|
||||
extractImages.selectText=Select image format to convert extracted images to
|
||||
extractImages.submit=Extract
|
||||
|
||||
|
||||
#File to PDF
|
||||
fileToPDF.title=File to PDF
|
||||
fileToPDF.header=Convert any file to PDF
|
||||
fileToPDF.credit=This service uses LibreOffice and Unoconv for file conversion.
|
||||
fileToPDF.supportedFileTypes=Supported file types should include the below however for a full updated list of supported formats, please refer to the LibreOffice documentation
|
||||
fileToPDF.submit=Convert to PDF
|
||||
|
||||
|
||||
#Add image
|
||||
@@ -122,8 +178,16 @@ imageToPDF.submit=Convert
|
||||
pdfToImage.title=PDF to Image
|
||||
pdfToImage.header=PDF to Image
|
||||
pdfToImage.selectText=Image Format
|
||||
pdfToImage.singleOrMultiple=Image result type
|
||||
pdfToImage.single=Single Big Image
|
||||
pdfToImage.multi=Multiple Images
|
||||
pdfToImage.colorType=Color type
|
||||
pdfToImage.color=Color
|
||||
pdfToImage.grey=Grayscale
|
||||
pdfToImage.blackwhite=Black and White (May lose data!)
|
||||
pdfToImage.submit=Convert
|
||||
|
||||
|
||||
#addPassword
|
||||
addPassword.title=Add Password
|
||||
addPassword.header=Add password (Encrypt)
|
||||
@@ -153,6 +217,12 @@ watermark.selectText.5=widthSpacer (Space between each watermark horizontally):
|
||||
watermark.selectText.6=heightSpacer (Space between each watermark vertically):
|
||||
watermark.submit=Add Watermark
|
||||
|
||||
#remove-watermark
|
||||
remove-watermark.title=Remove Watermark
|
||||
remove-watermark.header=Remove Watermark
|
||||
remove-watermark.selectText.1=Select PDF to remove watermark from:
|
||||
remove-watermark.selectText.2=Watermark Text:
|
||||
remove-watermark.submit=Remove Watermark
|
||||
|
||||
#Change permissions
|
||||
permissions.title=Change Permissions
|
||||
@@ -177,21 +247,27 @@ removePassword.selectText.1=Select PDF to Decrypt
|
||||
removePassword.selectText.2=Password
|
||||
removePassword.submit=Remove
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
changeMetadata.title=Change Metadata
|
||||
changeMetadata.header=Change Metadata
|
||||
changeMetadata.selectText.1=Please edit the variables you wish to change
|
||||
changeMetadata.selectText.2=Delete all metadata
|
||||
changeMetadata.selectText.3=Show Custom Metadata:
|
||||
changeMetadata.author=Author:
|
||||
changeMetadata.creationDate=Creation Date (yyyy/MM/dd HH:mm:ss):
|
||||
changeMetadata.creator=Creator:
|
||||
changeMetadata.keywords=Keywords:
|
||||
changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss):
|
||||
changeMetadata.producer=Producer:
|
||||
changeMetadata.subject=Subject:
|
||||
changeMetadata.title=Title:
|
||||
changeMetadata.trapped=Trapped:
|
||||
changeMetadata.selectText.4=Other Metadata:
|
||||
changeMetadata.selectText.5=Add Custom Metadata Entry
|
||||
changeMetadata.submit=Change
|
||||
|
||||
|
||||
fileToPDF.credit=This service uses LibreOffice and Unoconv for file conversion.
|
||||
fileToPDF.supportedFileTypes=Supported file types should include the below however for a full updated list of supported formats, please refer to the LibreOffice documentation
|
||||
|
||||
|
||||
|
||||
|
||||
281
src/main/resources/messages_fr_FR.properties
Normal file
281
src/main/resources/messages_fr_FR.properties
Normal file
@@ -0,0 +1,281 @@
|
||||
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#
|
||||
# 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 OCR sur PDF
|
||||
home.ocr.desc=Analyse et détecte le texte des images d\u2019un fichier PDF et le rajoute en tant que texte.
|
||||
|
||||
home.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.zip=Fichiers multi-téléchargements Zip
|
||||
|
||||
|
||||
#OCR
|
||||
ocr.title=OCR
|
||||
ocr.header=OCR (reconnaissance optique de caractères)
|
||||
ocr.selectText.1=Sélectionnez les langues à détecter dans le fichier PDF (celles répertoriées sont celles actuellement détectées) :
|
||||
ocr.selectText.2=Produire un fichier texte contenant du texte OCR à côté du PDF OCR
|
||||
ocr.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.compressLevel=Valeur entre 1 et 100 (1 étant le plus réduit)
|
||||
compress.submit=Compresser
|
||||
|
||||
|
||||
#merge
|
||||
merge.title=Fusionner
|
||||
merge.header=Fusionner plusieurs PDF (2+)
|
||||
merge.submit=Fusionner
|
||||
|
||||
#pdfOrganiser
|
||||
pdfOrganiser.title=Organisateur de pages
|
||||
pdfOrganiser.header=Organisateur de pages PDF
|
||||
pdfOrganiser.submit=Réorganiser les pages
|
||||
|
||||
|
||||
#pageRemover
|
||||
pageRemover.title=Suppresseur de pages
|
||||
pageRemover.header=Outil de suppression de pages PDF
|
||||
pageRemover.pagesToDelete=Pages à supprimer (Entrez une liste de numéros de page séparés par des virgules)Â:
|
||||
pageRemover.submit=Supprimer des pages
|
||||
|
||||
#rotate
|
||||
rotate.title=Faire pivoter le PDF
|
||||
rotate.header=Faire pivoter le PDF
|
||||
rotate.selectAngle=S\u00e9lectionner l'angle de rotation (en multiples de 90 degr\u00e9s):
|
||||
rotate.submit=Rotation
|
||||
|
||||
|
||||
|
||||
|
||||
#merge
|
||||
split.title=Fractionner le PDF
|
||||
split.header=Diviser le PDF
|
||||
split.desc.1=Les numéros que vous sélectionnez sont le numéro de page sur lequel vous souhaitez faire un fractionnement
|
||||
split.desc.2=Ainsi, la sélection de 1,3,7-8 diviserait un document de 10 pages en 6 PDF distincts avecÂ:
|
||||
split.desc.3=Document #1Â: Page 1
|
||||
split.desc.4=Document #2Â: Pages 2 et 3
|
||||
split.desc.5=Document #3Â: Pages 4, 5 et 6
|
||||
split.desc.6=Document #4Â: Page 7
|
||||
split.desc.7=Document #5Â: Page 8
|
||||
split.desc.8=Document #6Â: Pages 9 et 10
|
||||
split.splitPages=Entrez les pages sur lesquelles fractionnerÂ:
|
||||
split.submit=Diviser
|
||||
|
||||
|
||||
#merge
|
||||
imageToPDF.title=Image au format PDF
|
||||
imageToPDF.header=Image au format PDF
|
||||
imageToPDF.submit=Convertir
|
||||
|
||||
#pdfToImage
|
||||
pdfToImage.title=PDF vers image
|
||||
pdfToImage.header=PDF vers image
|
||||
pdfToImage.selectText=Format d'image
|
||||
pdfToImage.singleOrMultiple=Type de résultat d'image
|
||||
pdfToImage.single=Une seule grande image
|
||||
pdfToImage.multi=Plusieurs images
|
||||
pdfToImage.colorType=Type de couleur
|
||||
pdfToImage.color=Couleur
|
||||
pdfToImage.grey=Niveaux de gris
|
||||
pdfToImage.blackwhite=Noir et Blanc (Peut perdre des données !)
|
||||
pdfToImage.submit=Convertir
|
||||
|
||||
#addPassword
|
||||
addPassword.title=Ajouter un mot de passe
|
||||
addPassword.header=Ajouter un mot de passe (chiffrer)
|
||||
addPassword.selectText.1=Sélectionnez le PDF à chiffrer
|
||||
addPassword.selectText.2=Mot de passe
|
||||
addPassword.selectText.3=Longueur de la clé de chiffrement
|
||||
addPassword.selectText.4=Les valeurs supérieures sont plus fortes, mais les valeurs inférieures ont une meilleure compatibilité.
|
||||
addPassword.selectText.5=Autorisations à définir
|
||||
addPassword.selectText.6=Empêcher l'assemblage du document
|
||||
addPassword.selectText.7=Empêcher l'extraction de contenu
|
||||
addPassword.selectText.8=Empêcher l'extraction pour l'accessibilité
|
||||
addPassword.selectText.9=Empêcher de remplir le formulaire
|
||||
addPassword.selectText.10=Empêcher la modification
|
||||
addPassword.selectText.11=Empêcher la modification des annotations
|
||||
addPassword.selectText.12=Empêcher l'impression
|
||||
addPassword.selectText.13=Empêcher l'impression de différents formats
|
||||
addPassword.submit=Crypter
|
||||
|
||||
#watermark
|
||||
watermark.title=Ajouter un filigrane
|
||||
watermark.header=Ajouter un filigrane
|
||||
watermark.selectText.1=Sélectionnez le PDF auquel ajouter un filigraneÂ:
|
||||
watermark.selectText.2=Texte du filigraneÂ:
|
||||
watermark.selectText.3=Taille de la policeÂ:
|
||||
watermark.selectText.4=Rotation (0-360)Â:
|
||||
watermark.selectText.5=widthSpacer (Espace entre chaque filigrane horizontalement)Â:
|
||||
watermark.selectText.6=heightSpacer (Espace entre chaque filigrane verticalement)Â:
|
||||
watermark.submit=Ajouter un filigrane
|
||||
|
||||
#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
|
||||
|
||||
2018
src/main/resources/static/css/bootstrap-icons.css
vendored
Normal file
2018
src/main/resources/static/css/bootstrap-icons.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6
src/main/resources/static/css/bootstrap.min.css
vendored
Normal file
6
src/main/resources/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,16 +1,16 @@
|
||||
/* Dark Mode Styles */
|
||||
body {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
background-color: #333 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.dark-card {
|
||||
background-color: #333 !important;
|
||||
color: white;
|
||||
color: white !important;
|
||||
}
|
||||
.jumbotron {
|
||||
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 {
|
||||
@@ -21,3 +21,6 @@ body {
|
||||
background-color: #222 !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.woff
vendored
Normal file
Binary file not shown.
BIN
src/main/resources/static/css/fonts/bootstrap-icons.woff2
vendored
Normal file
BIN
src/main/resources/static/css/fonts/bootstrap-icons.woff2
vendored
Normal file
Binary file not shown.
23
src/main/resources/static/css/general.css
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
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
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
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
6
src/main/resources/static/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
src/main/resources/static/js/jquery.min.js
vendored
Normal file
2
src/main/resources/static/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
src/main/resources/static/js/jszip.min.js
vendored
Normal file
13
src/main/resources/static/js/jszip.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
src/main/resources/static/js/popper.min.js
vendored
Normal file
6
src/main/resources/static/js/popper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
15821
src/main/resources/static/pdfjs/pdf.js
vendored
Normal file
15821
src/main/resources/static/pdfjs/pdf.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
src/main/resources/static/pdfjs/pdf.js.map
vendored
Normal file
1
src/main/resources/static/pdfjs/pdf.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
64477
src/main/resources/static/pdfjs/pdf.worker.js
vendored
Normal file
64477
src/main/resources/static/pdfjs/pdf.worker.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/main/resources/static/pdfjs/pdf.worker.js.map
vendored
Normal file
1
src/main/resources/static/pdfjs/pdf.worker.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
<!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>
|
||||
@@ -9,40 +9,25 @@
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{addImage.header}"></h2>
|
||||
|
||||
|
||||
|
||||
<form method="post" th:action="@{add-image}"
|
||||
enctype="multipart/form-data">
|
||||
|
||||
<form method="post" th:action="@{add-image}" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput" required> <label
|
||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput2"
|
||||
name="fileInput2" required> <label
|
||||
class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label>
|
||||
<input type="file" class="custom-file-input" id="fileInput2" name="fileInput2" required>
|
||||
<label class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="x">X</label> <input type="number" class="form-control"
|
||||
id="x" name="x" step="0.01" required>
|
||||
<label for="x">X</label> <input type="number" class="form-control" id="x" name="x" step="0.01" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="y">Y</label> <input type="number" class="form-control"
|
||||
id="y" name="y" step="0.01" required>
|
||||
<label for="y">Y</label> <input type="number" class="form-control" id="y" name="y" step="0.01" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{addImage.submit}"></button>
|
||||
</form>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
<!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>
|
||||
|
||||
|
||||
<body> <div id="page-container">
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{compress.header}"></h2>
|
||||
<form action="#" th:action="@{compress-pdf}"
|
||||
th:object="${rotateForm}" method="post"
|
||||
enctype="multipart/form-data">
|
||||
<p th:text="#{processTimeWarning}"></p>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput" required> <label
|
||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
||||
<form action="#" th:action="@{/compress-pdf}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<div>
|
||||
<label for="optimizeLevel" th:text="#{compress.selectText.1}"></label>
|
||||
<select name="optimizeLevel" id="optimizeLevel">
|
||||
<option value="0" th:text="#{compress.selectText.2}"></option>
|
||||
<option value="1" selected th:text="#{compress.selectText.3}"></option>
|
||||
<option value="2" th:text="#{compress.selectText.4}"></option>
|
||||
<option value="3" th:text="#{compress.selectText.5}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="imageCompressionLevel" th:text="#{compress.compressLevel}"></label> <input type="number" class="form-control"
|
||||
id="imageCompressionLevel" name="imageCompressionLevel" step="1"
|
||||
value="1" min="1" max="100" required>
|
||||
<div>
|
||||
<input type="checkbox" name="fastWebView" id="fastWebView">
|
||||
<label for="fastWebView" th:text="#{compress.selectText.6}"></label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" th:text="#{compress.submit}"></button>
|
||||
<div>
|
||||
<input type="checkbox" name="jbig2Lossy" id="jbig2Lossy">
|
||||
<label for="jbig2Lossy" th:text="#{compress.selectText.7}"></label>
|
||||
</div>
|
||||
<button type="submit" th:text="#{compress.submit}"></button>
|
||||
</form>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
<p class="mt-3" th:text="#{compress.credit}"></p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
36
src/main/resources/templates/convert/file-to-pdf.html
Normal file
36
src/main/resources/templates/convert/file-to-pdf.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.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" 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,32 +1,28 @@
|
||||
<!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>
|
||||
|
||||
|
||||
<body> <div id="page-container">
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{imageToPDF.header}"></h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
th:action="@{img-to-pdf}">
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{img-to-pdf}">
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput" required> <label
|
||||
class="custom-file-label" for="fileInput" th:text="#{imgPrompt}"></label>
|
||||
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" required>
|
||||
<label class="custom-file-label" for="fileInput" th:text="#{imgPrompt}"></label>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
|
||||
|
||||
</form>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +1,56 @@
|
||||
<!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="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{pdfToImage.header}"></h2>
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
th:action="@{pdf-to-img}">
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput" required> <label
|
||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
<p th:text="#{processTimeWarning}"></p>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-img}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{pdfToImage.selectText}"></label> <select class="form-control"
|
||||
name="imageFormat">
|
||||
<option value="jpg">JPEG</option>
|
||||
<label th:text="#{pdfToImage.selectText}"></label>
|
||||
<select class="form-control" name="imageFormat">
|
||||
<option value="png">PNG</option>
|
||||
<option value="jpg">JPG</option>
|
||||
<option value="gif">GIF</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{pdfToImage.singleOrMultiple}"></label>
|
||||
<select class="form-control" name="singleOrMultiple">
|
||||
<option value="single" th:text="#{pdfToImage.single}"></option>
|
||||
<option value="multiple" th:text="#{pdfToImage.multi}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{pdfToImage.colorType}"></label>
|
||||
<select class="form-control" name="colorType">
|
||||
<option value="color" th:text="#{pdfToImage.color}"></option>
|
||||
<option value="greyscale" th:text="#{pdfToImage.grey}"></option>
|
||||
<option value="blackwhite" th:text="#{pdfToImage.blackwhite}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="dpi">DPI:</label>
|
||||
<input type="number" name="dpi" class="form-control" id="dpi" min="1" max="100" step="1" value="30" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{pdfToImage.submit}"></button>
|
||||
</form>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
|
||||
131
src/main/resources/templates/error.html
Normal file
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
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)}"></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" 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,17 +1,32 @@
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<!-- Metadata -->
|
||||
<meta charset="UTF-8">
|
||||
<title th:text="'S-PDF ' + ${title}"></title>
|
||||
<link rel="shortcut icon" href="favicon.svg">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.3.122/build/pdf.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.3.122/web/pdf_viewer.min.css" rel="stylesheet">
|
||||
<!-- jQuery -->
|
||||
<script src="js/jquery.min.js"></script>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="js/jszip.min.js"></script>
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<script src="js/popper.min.js"></script>
|
||||
<script src="js/bootstrap.min.js"></script>
|
||||
<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>
|
||||
function toggleDarkMode() {
|
||||
function toggleDarkMode() {
|
||||
var checkbox = document.getElementById("toggle-dark-mode");
|
||||
var darkModeStyles = document.getElementById("dark-mode-styles");
|
||||
if (checkbox.checked) {
|
||||
@@ -22,99 +37,349 @@
|
||||
darkModeStyles.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
var darkModeStyles = document.getElementById("dark-mode-styles");
|
||||
var checkbox = document.getElementById("toggle-dark-mode");
|
||||
|
||||
// Check if the user has already set a preference
|
||||
if (localStorage.getItem("dark-mode") == "on") {
|
||||
darkModeStyles.disabled = false;
|
||||
checkbox.checked = true;
|
||||
}
|
||||
if (localStorage.getItem("dark-mode") == "off") {
|
||||
} 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>
|
||||
<link rel="stylesheet" href="general.css">
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<th:block th:fragment="filelist">
|
||||
<div id="fileList"></div>
|
||||
<div id="fileList2"></div>
|
||||
<script>
|
||||
var input = document.getElementById("fileInput");
|
||||
var output = document.getElementById("fileList");
|
||||
|
||||
input.addEventListener("change", function() {
|
||||
var files = input.files;
|
||||
var fileNames = "";
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
fileNames += (i + 1) + ". " + files[i].name + "<br>";
|
||||
}
|
||||
|
||||
output.innerHTML = fileNames;
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
var input2 = document.getElementById("fileInput2");
|
||||
var output2 = document.getElementById("fileList2");
|
||||
if (input2 != null && !input2) {
|
||||
input2.addEventListener("change", function() {
|
||||
var files = input2.files;
|
||||
var fileNames = "";
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
fileNames += (i + 1) + ". " + files[i].name + "<br>";
|
||||
}
|
||||
|
||||
output2.innerHTML = fileNames;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
if (dropContainer) {
|
||||
dropContainer.ondragover = dropContainer.ondragenter = function(evt) {
|
||||
evt.preventDefault();
|
||||
};
|
||||
|
||||
dropContainer.ondrop = function(evt) {
|
||||
if (fileInput) {
|
||||
fileInput.files = evt.dataTransfer.files;
|
||||
|
||||
const dT = new DataTransfer();
|
||||
dT.items.add(evt.dataTransfer.files[0]);
|
||||
dT.items.add(evt.dataTransfer.files[3]);
|
||||
fileInput.files = dT.files;
|
||||
|
||||
evt.preventDefault();
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</th:block>
|
||||
|
||||
<th:block th:fragment="fileSelector(name, multiple)">
|
||||
<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}">
|
||||
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" multiple>
|
||||
<label class="custom-file-label" th:for="${name}+'-input'" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
<div class="selected-files"></div>
|
||||
</div>
|
||||
<br>
|
||||
<div id="progressBarContainer" style="display: none; position: relative;">
|
||||
<div class="progress" style="height: 1rem;">
|
||||
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script 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);
|
||||
<script>
|
||||
|
||||
$('form').submit(function(event) {
|
||||
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;
|
||||
if (!multiple && files.length > 1) {
|
||||
console.log("multi parallel download")
|
||||
submitMultiPdfForm(event,url);
|
||||
} else {
|
||||
$(this).siblings(".custom-file-label").addClass("selected").html([[#{pdfPrompt}]]);
|
||||
console.log("start single download")
|
||||
|
||||
// Get the selected download option from localStorage
|
||||
const downloadOption = localStorage.getItem('downloadOption');
|
||||
|
||||
var formData = new FormData($('form')[0]);
|
||||
|
||||
// Send the request to the server using the fetch() API
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => {
|
||||
if (!response) {
|
||||
throw new Error('Received null response for file ' + i);
|
||||
}
|
||||
console.log("load single download")
|
||||
|
||||
|
||||
// Extract the filename from the Content-Disposition header, if present
|
||||
let filename = null;
|
||||
const contentDispositionHeader = response.headers.get('Content-Disposition');
|
||||
console.log(contentDispositionHeader)
|
||||
if (contentDispositionHeader && contentDispositionHeader.indexOf('attachment') !== -1) {
|
||||
filename = contentDispositionHeader.split('filename=')[1].replace(/"/g, '');
|
||||
} else {
|
||||
// If the Content-Disposition header is not present or does not contain the filename, use a default filename
|
||||
filename = 'download';
|
||||
}
|
||||
console.log("filename=" + filename)
|
||||
|
||||
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
console.log("contentType=" + contentType)
|
||||
// Check if the response is a PDF or an image
|
||||
if (contentType.includes('pdf') || contentType.includes('image')) {
|
||||
response.blob().then(blob => {
|
||||
console.log("pdf/image")
|
||||
|
||||
// Perform the appropriate action based on the download option
|
||||
if (downloadOption === 'sameWindow') {
|
||||
console.log("same window")
|
||||
|
||||
// Open the file in the same window
|
||||
window.location.href = URL.createObjectURL(blob);
|
||||
} else if (downloadOption === 'newWindow') {
|
||||
console.log("new window")
|
||||
|
||||
// Open the file in a new window
|
||||
window.open(URL.createObjectURL(blob), '_blank');
|
||||
} else {
|
||||
console.log("else save")
|
||||
|
||||
// Download the file
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
link.click();
|
||||
}
|
||||
});
|
||||
} else if (contentType.includes('json')) {
|
||||
// Handle the JSON response
|
||||
response.json().then(data => {
|
||||
// Format the error message
|
||||
const errorMessage = JSON.stringify(data, null, 2);
|
||||
|
||||
// Display the error message in an alert
|
||||
alert(`An error occurred: ${errorMessage}`);
|
||||
});
|
||||
|
||||
} else {
|
||||
response.blob().then(blob => {
|
||||
console.log("else save 2 zip")
|
||||
|
||||
// For ZIP files or other file types, just download the file
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
link.click();
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("error listener")
|
||||
|
||||
// Extract the error message and stack trace from the response
|
||||
const errorMessage = error.message;
|
||||
const stackTrace = error.stack;
|
||||
|
||||
// Create an error message to display to the user
|
||||
const message = `${errorMessage}\n\n${stackTrace}`;
|
||||
|
||||
// Display the error message to the user
|
||||
alert(message);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function submitMultiPdfForm(event,url) {
|
||||
// Get the selected PDF files
|
||||
var files = $('#fileInput-input')[0].files;
|
||||
|
||||
// Get the existing form data
|
||||
var formData = new FormData($('form')[0]);
|
||||
formData.delete('fileInput');
|
||||
|
||||
// Show the progress bar
|
||||
$('#progressBarContainer').show();
|
||||
|
||||
// Initialize the progress bar
|
||||
var progressBar = $('#progressBar');
|
||||
progressBar.css('width', '0%');
|
||||
progressBar.attr('aria-valuenow', 0);
|
||||
progressBar.attr('aria-valuemax', files.length);
|
||||
|
||||
// Check the flag in localStorage
|
||||
const zipFiles = localStorage.getItem('zipParallelFiles') === 'true';
|
||||
|
||||
// Initialize JSZip instance if needed
|
||||
let jszip = null;
|
||||
if (zipFiles) {
|
||||
jszip = new JSZip();
|
||||
}
|
||||
|
||||
|
||||
// Submit each PDF file in parallel
|
||||
var promises = [];
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var fileFormData = new FormData();
|
||||
fileFormData.append('fileInput', files[i]);
|
||||
for (var pair of formData.entries()) {
|
||||
fileFormData.append(pair[0], pair[1]);
|
||||
}
|
||||
console.log(fileFormData);
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
body: fileFormData
|
||||
}).then(function(response) {
|
||||
if (!response) {
|
||||
throw new Error('Received null response for file ' + i);
|
||||
}
|
||||
console.log('Received response for file ' + i + ': ' + response);
|
||||
|
||||
var contentDisposition = response.headers.get('content-disposition');
|
||||
var fileName = contentDisposition.split('filename=')[1].replace(/"/g, '');
|
||||
|
||||
response.blob().then(function (blob) {
|
||||
if (zipFiles) {
|
||||
// Add the file to the ZIP archive
|
||||
jszip.file(fileName, blob);
|
||||
resolve();
|
||||
} else {
|
||||
// Download the file directly
|
||||
var url = window.URL.createObjectURL(blob);
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
}).catch(function(error) {
|
||||
console.error('Error submitting request for file ' + i + ': ' + error);
|
||||
|
||||
// Set default values or fallbacks for error properties
|
||||
var status = error && error.status || 500;
|
||||
var statusText = error && error.statusText || 'Internal Server Error';
|
||||
var message = error && error.message || 'An error occurred while processing your request.';
|
||||
|
||||
// Reject the Promise to signal that the request has failed
|
||||
reject();
|
||||
// Redirect to error page with Spring Boot error parameters
|
||||
var url = '/error?status=' + status + '&error=' + encodeURIComponent(statusText) + '&message=' + encodeURIComponent(message);
|
||||
window.location.href = url;
|
||||
});
|
||||
});
|
||||
|
||||
// Update the progress bar as each request finishes
|
||||
promise.then(function() {
|
||||
var progress = ((progressBar.attr('aria-valuenow') / files.length) * 100) + (100 / files.length);
|
||||
progressBar.css('width', progress + '%');
|
||||
progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1);
|
||||
});
|
||||
|
||||
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) {
|
||||
jszip.generateAsync({ type: "blob" }).then(function (content) {
|
||||
var url = window.URL.createObjectURL(content);
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = "files.zip";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
</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>");
|
||||
});
|
||||
if (fileNames.length === 1) {
|
||||
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]);
|
||||
} else if (fileNames.length > 1) {
|
||||
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames.length + " files selected");
|
||||
} else {
|
||||
$(inputElement).siblings(".custom-file-label").addClass("selected").html([[#{pdfPrompt}]]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.custom-file-label {
|
||||
padding-right: 90px;
|
||||
}
|
||||
|
||||
.selected-files {
|
||||
margin-top: 10px;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
</th:block>
|
||||
89
src/main/resources/templates/fragments/errorBanner.html
Normal file
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,7 @@
|
||||
<div th:fragment="footer">
|
||||
<link rel="stylesheet"
|
||||
href="https://use.fontawesome.com/releases/v5.6.1/css/all.css">
|
||||
<footer id="footer" class="text-center py-3">
|
||||
<a href="https://github.com/Frooodle/Stirling-PDF" target="_blank"
|
||||
class="mx-1"> <i class="fab fa-github fa-2x"></i>
|
||||
</a> <a href="https://hub.docker.com/r/frooodle/s-pdf" target="_blank"
|
||||
class="mx-1"> <i class="fab fa-docker fa-2x"></i>
|
||||
</a>
|
||||
<a href="https://github.com/Frooodle/Stirling-PDF" target="_blank" class="mx-1"><img src="images/github.svg"></img></a>
|
||||
<a href="https://hub.docker.com/r/frooodle/s-pdf" target="_blank" class="mx-1"><img src="images/docker.svg"></img></a>
|
||||
<a href="https://discord.gg/Cn8pWhQRxZ" target="_blank" class="mx-1"><img src="images/discord.svg"></img></a>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -1,132 +1,271 @@
|
||||
<div th:fragment="navbar">
|
||||
<div th:fragment="navbar" class="mx-auto">
|
||||
<script>
|
||||
document
|
||||
.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
function() {
|
||||
// Get the dropdown items
|
||||
var dropdownItems = document
|
||||
.querySelectorAll('.lang_dropdown-item');
|
||||
|
||||
// Loop through the dropdown items
|
||||
for (var i = 0; i < dropdownItems.length; i++) {
|
||||
dropdownItems[i].classList
|
||||
.remove('active');
|
||||
if (dropdownItems[i].dataset.languageCode === localStorage
|
||||
.getItem('languageCode')) {
|
||||
dropdownItems[i].classList
|
||||
.add('active');
|
||||
}
|
||||
|
||||
// Add a click event listener to each dropdown item
|
||||
dropdownItems[i]
|
||||
.addEventListener(
|
||||
'click',
|
||||
function(event) {
|
||||
|
||||
// Prevent the default link behavior
|
||||
event
|
||||
.preventDefault();
|
||||
|
||||
// Get the language code
|
||||
var languageCode = this.dataset.languageCode;
|
||||
|
||||
// Save the language code to local storage
|
||||
localStorage
|
||||
.setItem(
|
||||
'languageCode',
|
||||
languageCode);
|
||||
|
||||
// Get the current URL
|
||||
var currentUrl = window.location.href;
|
||||
|
||||
// Check if the URL already contains a "?lang" query parameter
|
||||
if (currentUrl
|
||||
.indexOf('?lang=') === -1) {
|
||||
// Update the URL with the "?lang" query parameter
|
||||
window.location.href = currentUrl
|
||||
+ '?lang='
|
||||
+ languageCode;
|
||||
} else {
|
||||
// Replace the "?lang" query parameter with the new value
|
||||
window.location.href = currentUrl
|
||||
.replace(
|
||||
/\?lang=\w{2,}/,
|
||||
'?lang='
|
||||
+ languageCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script th:inline="javascript">
|
||||
function compareVersions(version1, version2) {
|
||||
const v1 = version1.split('.');
|
||||
const v2 = version2.split('.');
|
||||
|
||||
for (let i = 0; i < v1.length || i < v2.length; i++) {
|
||||
const n1 = parseInt(v1[i]) || 0;
|
||||
const n2 = parseInt(v2[i]) || 0;
|
||||
|
||||
if (n1 > n2) {
|
||||
return 1;
|
||||
} else if (n1 < n2) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function getLatestReleaseVersion() {
|
||||
const url = "https://api.github.com/repos/Frooodle/Stirling-PDF/releases/latest";
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
return data.tag_name.substring(1);
|
||||
}
|
||||
|
||||
const currentVersion = /*[[${@appVersion}]]*/ ''; // Replace with your current version number
|
||||
|
||||
async function checkForUpdate() {
|
||||
const latestVersion = await getLatestReleaseVersion();
|
||||
console.log("latestVersion=" + latestVersion)
|
||||
console.log("currentVersion=" + currentVersion)
|
||||
console.log("compareVersions(latestVersion, currentVersion) > 0)=" + compareVersions(latestVersion, currentVersion))
|
||||
if (latestVersion != null && latestVersion != "" && compareVersions(latestVersion, currentVersion) > 0) {
|
||||
document.getElementById("update-btn").style.visibility = "visible";
|
||||
console.log("visible")
|
||||
} else {
|
||||
document.getElementById("update-btn").style.visibility = "hidden";
|
||||
console.log("hidden")
|
||||
}
|
||||
}
|
||||
|
||||
checkForUpdate();
|
||||
</script>
|
||||
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container">
|
||||
<div class="container ">
|
||||
|
||||
<a class="navbar-brand" href="#" th:href="@{/}">Stirling PDF</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse"
|
||||
data-target="#navbarNav" aria-controls="navbarNav"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
|
||||
<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>
|
||||
<ul class="navbar-nav mr-auto flex-nowrap">
|
||||
|
||||
<li class="nav-item"><a class="nav-link" href="#"
|
||||
th:href="@{rotate-pdf}"
|
||||
th:classappend="${currentPage}=='rotate-pdf' ? 'active' : ''" th:text="#{home.rotate.title}"></a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="#" th:href="@{merge-pdfs}" th:classappend="${currentPage}=='merge-pdfs' ? 'active' : ''" th:text="#{home.merge.title}"></a></li>
|
||||
|
||||
<li class="nav-item"><a class="nav-link" href="#" th:href="@{split-pdfs}" th:classappend="${currentPage}=='split-pdfs' ? 'active' : ''" th:text="#{home.split.title}"></a></li>
|
||||
|
||||
<li class="nav-item"><a class="nav-link" href="#" th:href="@{pdf-organizer}" th:classappend="${currentPage}=='pdf-organizer' ? 'active' : ''" th:text="#{home.pdfOrganiser.title}"></a></li>
|
||||
|
||||
<li class="nav-item"><a class="nav-link" href="#" th:href="@{rotate-pdf}" th:classappend="${currentPage}=='rotate-pdf' ? 'active' : ''" th:text="#{home.rotate.title}"></a></li>
|
||||
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-to-pdf' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.convert}"></a>
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-to-pdf' OR ${currentPage}=='file-to-pdf' OR ${currentPage}=='xlsx-to-pdf' ? 'active' : ''"><a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false" th:text="#{navbar.convert}"></a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="#" th:href="@{pdf-to-img}" th:classappend="${currentPage}=='pdf-to-img' ? 'active' : ''" th:text="#{home.pdfToImage.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{img-to-pdf}" th:classappend="${currentPage}=='img-to-pdf' ? 'active' : ''" th:text="#{home.imageToPdf.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{file-to-pdf}" th:classappend="${currentPage}=='file-to-pdf' ? 'active' : ''" th:text="#{home.fileToPDF.title}"></a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='change-permissions' OR ${currentPage}=='add-watermark' ? 'active' : ''">
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='change-permissions' OR ${currentPage}=='add-watermark' OR ${currentPage}=='remove-watermark' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.security}"></a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="#" th:href="@{add-password}" th:classappend="${currentPage}=='add-password' ? 'active' : ''" th:text="#{home.addPassword.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{remove-password}" th:classappend="${currentPage}=='remove-password' ? 'active' : ''" th:text="#{home.removePassword.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{change-permissions}" th:classappend="${currentPage}=='change-permissions' ? 'active' : ''" th:text="#{home.permissions.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{add-watermark}" th:classappend="${currentPage}=='add-watermark' ? 'active' : ''" th:text="#{home.watermark.title}"></a>
|
||||
<a class="dropdown-item" href="#" th:href="@{change-metadata}" th:classappend="${currentPage}=='change-metadata' ? 'active' : ''" th:text="#{home.changeMetadata.title}"></a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='add-image' OR ${currentPage}=='compress-pdf' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="#{navbar.other}"></a>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</li>
|
||||
<a class="dropdown-item" href="#" th:href="@{extract-images}" th:classappend="${currentPage}=='extract-images' ? 'active' : ''" th:text="#{home.extractImages.title}"></a>
|
||||
</div></li>
|
||||
|
||||
|
||||
|
||||
<li class="nav-item"><a class="nav-link" href="#"
|
||||
th:href="@{add-image}"
|
||||
th:classappend="${currentPage}=='add-image' ? 'active' : ''"></a></li>
|
||||
|
||||
<li class="nav-item"><a class="nav-link" href="#"
|
||||
th:href="@{compress-pdf}"
|
||||
th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''"></a></li>
|
||||
|
||||
<input type="checkbox" id="toggle-dark-mode" checked="true"
|
||||
th:onclick="javascript:toggleDarkMode()">
|
||||
<a class="nav-link" href="#" for="toggle-dark-mode" th:text="#{navbar.darkmode}"></a>
|
||||
<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">
|
||||
Language
|
||||
<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_US">English (US)</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="en_GB">English (UK)</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="de_DE">German</a>
|
||||
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="ar_AR">العربية</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="de_DE">Deutsch</a>
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Get the dropdown items
|
||||
var dropdownItems = document.querySelectorAll('.lang_dropdown-item');
|
||||
|
||||
// Loop through the dropdown items
|
||||
for (var i = 0; i < dropdownItems.length; i++) {
|
||||
dropdownItems[i].classList.remove('active');
|
||||
if(dropdownItems[i].dataset.languageCode === localStorage.getItem('languageCode')){
|
||||
dropdownItems[i].classList.add('active');
|
||||
}
|
||||
// Add a click event listener to each dropdown item
|
||||
dropdownItems[i].addEventListener('click', function(event) {
|
||||
|
||||
|
||||
// Prevent the default link behavior
|
||||
event.preventDefault();
|
||||
|
||||
// Get the language code
|
||||
var languageCode = this.dataset.languageCode;
|
||||
|
||||
// Save the language code to local storage
|
||||
localStorage.setItem('languageCode', languageCode);
|
||||
|
||||
// Get the current URL
|
||||
var currentUrl = window.location.href;
|
||||
|
||||
// Check if the URL already contains a "?lang" query parameter
|
||||
if (currentUrl.indexOf('?lang=') === -1) {
|
||||
// Update the URL with the "?lang" query parameter
|
||||
window.location.href = currentUrl + '?lang=' + languageCode;
|
||||
} else {
|
||||
// Replace the "?lang" query parameter with the new value
|
||||
window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</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:text="#{settings.appVersion} + ' ' + ${@appVersion}"></p>
|
||||
<a href="https://github.com/Frooodle/Stirling-PDF/releases" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="update-btn" th:text="#{settings.update}"></button>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="downloadOption" th:text="#{settings.downloadOption.title}"></label>
|
||||
<select class="form-control" id="downloadOption">
|
||||
<option value="sameWindow" th:text="#{settings.downloadOption.1}"></option>
|
||||
<option value="newWindow" th:text="#{settings.downloadOption.2}"></option>
|
||||
<option value="downloadFile" th:text="#{settings.downloadOption.3}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="zipParallelFiles">
|
||||
<label class="custom-control-label" for="zipParallelFiles" th:text="#{settings.zip}"></label>
|
||||
</div>
|
||||
</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 zipParallelFiles flag from local storage, or set it to false if it doesn't exist
|
||||
var zipParallelFiles = localStorage.getItem('zipParallelFiles') === 'true';
|
||||
|
||||
// Set the checked state of the checkbox
|
||||
document.getElementById('zipParallelFiles').checked = zipParallelFiles;
|
||||
|
||||
// Save the checked state of the checkbox to local storage when it changes
|
||||
document.getElementById('zipParallelFiles').addEventListener('change', function () {
|
||||
zipParallelFiles = this.checked;
|
||||
localStorage.setItem('zipParallelFiles', zipParallelFiles);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
<!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>
|
||||
|
||||
<style>
|
||||
.features-container {
|
||||
.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);
|
||||
}
|
||||
|
||||
.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 {
|
||||
}
|
||||
|
||||
.feature-card .card-text {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
@@ -49,13 +50,20 @@
|
||||
|
||||
<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.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.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.changeMetadata.title}, cardText=#{home.changeMetadata.desc}, cardLink='change-metadata')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions')}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.ocr.title}, cardText=#{home.ocr.desc}, cardLink='ocr-pdf')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(cardTitle=#{home.extractImages.title}, cardText=#{home.extractImages.desc}, cardLink='extract-images')}"></div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
<!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>
|
||||
|
||||
|
||||
<body> <div id="page-container">
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container" id="dropContainer">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{merge.header}"></h2>
|
||||
<form action="merge-pdfs" method="post"
|
||||
enctype="multipart/form-data">
|
||||
<form action="merge-pdfs" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label th:text="#{multiPdfDropPrompt}"></label>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput" multiple required> <label
|
||||
class="custom-file-label" th:text="#{pdfPrompt}">s</label>
|
||||
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" multiple required>
|
||||
<label class="custom-file-label" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -30,65 +28,79 @@
|
||||
<button type="submit" class="btn btn-primary" th:text="#{merge.submit}"></button>
|
||||
</div>
|
||||
</form>
|
||||
<style>
|
||||
|
||||
.list-group-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filename {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.arrows {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.move-up span,
|
||||
.move-down span {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
<script>
|
||||
document
|
||||
.getElementById("fileInput")
|
||||
.addEventListener(
|
||||
"change",
|
||||
function() {
|
||||
document.getElementById("fileInput").addEventListener("change", function() {
|
||||
var files = this.files;
|
||||
var list = document
|
||||
.getElementById("selectedFiles");
|
||||
var list = document.getElementById("selectedFiles");
|
||||
list.innerHTML = "";
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var item = document
|
||||
.createElement("li");
|
||||
item.className = "list-group-item d-flex justify-content-between align-items-center";
|
||||
item.textContent = files[i].name;
|
||||
item.innerHTML += '<div><button class="btn btn-secondary move-up">Move Up</button> <button class="btn btn-secondary move-down">Move Down</button></div>';
|
||||
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);
|
||||
}
|
||||
var moveUpButtons = document
|
||||
.querySelectorAll(".move-up");
|
||||
|
||||
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;
|
||||
moveUpButtons[i].addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
var parent = this.closest(".list-group-item");
|
||||
var grandParent = parent.parentNode;
|
||||
if (parent.previousElementSibling) {
|
||||
grandParent
|
||||
.insertBefore(
|
||||
parent,
|
||||
parent.previousElementSibling);
|
||||
grandParent.insertBefore(parent, parent.previousElementSibling);
|
||||
updateFiles();
|
||||
}
|
||||
});
|
||||
}
|
||||
var moveDownButtons = document
|
||||
.querySelectorAll(".move-down");
|
||||
|
||||
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;
|
||||
moveDownButtons[i].addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
var parent = this.closest(".list-group-item");
|
||||
var grandParent = parent.parentNode;
|
||||
if (parent.nextElementSibling) {
|
||||
grandParent
|
||||
.insertBefore(
|
||||
parent.nextElementSibling,
|
||||
parent);
|
||||
grandParent.insertBefore(parent.nextElementSibling, parent);
|
||||
updateFiles();
|
||||
}
|
||||
});
|
||||
@@ -96,33 +108,24 @@
|
||||
|
||||
function updateFiles() {
|
||||
var dataTransfer = new DataTransfer();
|
||||
var liElements = document
|
||||
.querySelectorAll("#selectedFiles li");
|
||||
var liElements = document.querySelectorAll("#selectedFiles li");
|
||||
|
||||
for (var i = 0; i < liElements.length; i++) {
|
||||
var fileNameFromList = liElements[i].innerText
|
||||
.replace(
|
||||
"\nMove Up Move Down",
|
||||
"");
|
||||
var fileFromFiles
|
||||
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);
|
||||
dataTransfer.items.add(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
document
|
||||
.getElementById("fileInput").files = dataTransfer.files;
|
||||
document.getElementById("fileInput").files = dataTransfer.files;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
46
src/main/resources/templates/ocr-pdf.html
Normal file
46
src/main/resources/templates/ocr-pdf.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<!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)}"></div>
|
||||
<div class="form-group">
|
||||
<label for="languages" class="form-label" th:text="#{ocr.selectText.1}"></label>
|
||||
<div id="languages">
|
||||
<div th:each="language: ${languages}">
|
||||
<input type="checkbox" class="form-check-input" th:name="languages" th:value="${language}" th:id="${'language-' + language}" />
|
||||
<label class="form-check-label" th:for="${'language-' + language}" th:text="${language}"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="form-group">
|
||||
<input type="checkbox" class="form-check-input" name="sidecar" id="sidecar" />
|
||||
<label class="form-check-label" for="sidecar" th:text="#{ocr.selectText.2}"></label>
|
||||
</div> -->
|
||||
<button type="submit" class="btn btn-primary" th:text="#{ocr.submit}"></button>
|
||||
</form>
|
||||
<p th:text="#{ocr.credit}"></p>
|
||||
<p th:text="#{ocr.help}"></p>
|
||||
<a href="https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md">https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,34 +1,27 @@
|
||||
<!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>
|
||||
|
||||
|
||||
<body> <div id="page-container">
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{pdfOrganiser.header}"></h2>
|
||||
|
||||
<form th:action="@{rearrange-pages}" method="post"
|
||||
enctype="multipart/form-data">
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput" required> <label
|
||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
<form th:action="@{rearrange-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<div class="form-group">
|
||||
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label> <input type="text" class="form-control"
|
||||
id="fileInput" name="pageOrder"
|
||||
placeholder="(e.g. 1,3,2 or 4-8,2,10-12)" required>
|
||||
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
|
||||
<input type="text" class="form-control" id="fileInput" name="pageOrder" placeholder="(e.g. 1,3,2 or 4-8,2,10-12)" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
|
||||
</form>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,35 +1,27 @@
|
||||
<!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>
|
||||
|
||||
|
||||
<body> <div id="page-container">
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{pageRemover.header}"></h2>
|
||||
|
||||
<form th:action="@{remove-pages}" method="post"
|
||||
enctype="multipart/form-data">
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput" required> <label
|
||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
<form th:action="@{remove-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<div class="form-group">
|
||||
<label for="pagesToDelete" th:text="#{pageRemover.pagesToDelete}"></label> <input type="text" class="form-control"
|
||||
id="fileInput" name="pagesToDelete"
|
||||
placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
|
||||
<label for="pagesToDelete" th:text="#{pageRemover.pagesToDelete}"></label>
|
||||
<input type="text" class="form-control" id="fileInput" name="pagesToDelete" placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{pageRemover.submit}"></button>
|
||||
</form>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<!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>
|
||||
|
||||
|
||||
<body> <div id="page-container">
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{rotate.header}"></h2>
|
||||
|
||||
<form action="#" th:action="@{rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="fragments/common :: fileSelector(name='fileInput', multiple=false)"></div>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<input type="hidden" id="angleInput" name="angle" value="0">
|
||||
|
||||
<div id="editSection" style="display: none">
|
||||
<div class="previewContainer">
|
||||
<img id="pdf-preview"/>
|
||||
<img id="pdf-preview" />
|
||||
</div>
|
||||
|
||||
<div class="buttonContainer">
|
||||
<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">
|
||||
<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 fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" />
|
||||
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{rotate.submit}"></button>
|
||||
<button type="button" class="btn btn-secondary" onclick="rotate(90)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
|
||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
|
||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -97,35 +97,36 @@
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
#pdf-preview {
|
||||
#pdf-preview {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
max-width: 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;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
translate: -50% -50%;
|
||||
}
|
||||
.previewContainer {
|
||||
}
|
||||
|
||||
.previewContainer {
|
||||
aspect-ratio: 1;
|
||||
width: 100%;
|
||||
border: 1px solid rgba(0,0,0,.125);
|
||||
border: 1px solid rgba(0, 0, 0, .125);
|
||||
border-radius: 0.25rem;
|
||||
margin: 1rem 0;
|
||||
padding: 15px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
.buttonContainer {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,35 +1,28 @@
|
||||
<!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>
|
||||
|
||||
<body> <div id="page-container">
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{addPassword.header}"></h2>
|
||||
|
||||
<form action="add-password" method="post"
|
||||
enctype="multipart/form-data">
|
||||
<form action="add-password" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label th:text="#{addPassword.selectText.1}"></label>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput" required> <label
|
||||
class="custom-file-label" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{addPassword.selectText.2}"></label> <input type="password"
|
||||
class="form-control" id="password" name="password" required>
|
||||
<label th:text="#{addPassword.selectText.2}"></label> <input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{addPassword.selectText.3}"></label> <select class="form-control"
|
||||
id="keyLength" name="keyLength">
|
||||
<label th:text="#{addPassword.selectText.3}"></label> <select class="form-control" id="keyLength" name="keyLength">
|
||||
<option value="40">40</option>
|
||||
<option value="128">128</option>
|
||||
<option value="256">256</option>
|
||||
@@ -38,45 +31,36 @@
|
||||
<div class="form-group">
|
||||
<label th:text="#{addPassword.selectText.5}"></label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canAssembleDocument" name="canAssembleDocument"> <label
|
||||
class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
|
||||
<label class="form-check-label" for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canExtractContent" name="canExtractContent"> <label
|
||||
class="form-check-label" for="canExtractContent" th:text="#{addPassword.selectText.7}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
|
||||
<label class="form-check-label" for="canExtractContent" th:text="#{addPassword.selectText.7}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canExtractForAccessibility"
|
||||
name="canExtractForAccessibility"> <label
|
||||
class="form-check-label" for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
|
||||
<label class="form-check-label" for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canFillInForm" name="canFillInForm"> <label
|
||||
class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canFillInForm" name="canFillInForm">
|
||||
<label class="form-check-label" for="canFillInForm" th:text="#{addPassword.selectText.9}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canModify"
|
||||
name="canModify"> <label class="form-check-label"
|
||||
for="canModify" th:text="#{addPassword.selectText.10}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canModify" name="canModify">
|
||||
<label class="form-check-label" for="canModify" th:text="#{addPassword.selectText.10}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canModifyAnnotations" name="canModifyAnnotations"> <label
|
||||
class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations">
|
||||
<label class="form-check-label" for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canPrint"
|
||||
name="canPrint"> <label class="form-check-label"
|
||||
for="canPrint" th:text="#{addPassword.selectText.12}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
|
||||
<label class="form-check-label" for="canPrint" th:text="#{addPassword.selectText.12}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canPrintFaithful" name="canPrintFaithful"> <label
|
||||
class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
|
||||
<label class="form-check-label" for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<!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>
|
||||
|
||||
<body> <div id="page-container">
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
@@ -16,37 +16,33 @@
|
||||
<form method="post" enctype="multipart/form-data" action="add-watermark">
|
||||
<div class="form-group">
|
||||
<label th:text="#{watermark.selectText.1}"></label>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput" required> <label
|
||||
class="custom-file-label" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="watermarkText" th:text="#{watermark.selectText.2}"></label>
|
||||
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required/>
|
||||
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="fontSize" th:text="#{watermark.selectText.3}"></label>
|
||||
<input type="text" id="fontSize" name="fontSize" class="form-control" value="30"/>
|
||||
<input type="text" id="fontSize" name="fontSize" class="form-control" value="30" />
|
||||
</div>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50" />
|
||||
</div>
|
||||
<div class="form-group text-center">
|
||||
<input type="submit" th:value="#{watermark.submit}" class="btn btn-primary"/>
|
||||
<input type="submit" th:value="#{watermark.submit}" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
263
src/main/resources/templates/security/change-metadata.html
Normal file
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)}"></div>
|
||||
<p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
|
||||
|
||||
<div class="form-group-inline form-check">
|
||||
<input type="checkbox" class="form-check-input" id="deleteAll" name="deleteAll">
|
||||
<label class="ml-3" for="deleteAll" th:text="#{changeMetadata.selectText.2}" ></label>
|
||||
</div>
|
||||
|
||||
<div class="form-group-inline form-check">
|
||||
<input type="checkbox" class="form-check-input" id="customModeCheckbox">
|
||||
<label class="ml-3" for="customModeCheckbox" th:text="#{changeMetadata.selectText.3}"></label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-check-label" for="author" th:text="#{changeMetadata.author}"></label>
|
||||
<input type="text" class="form-control" id="author" name="author">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-check-label" for="creationDate" th:text="#{changeMetadata.creationDate}"></label>
|
||||
<input type="text" class="form-control" id="creationDate" name="creationDate" placeholder="2020/12/25 18:30:59">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-check-label" for="creator" th:text="#{changeMetadata.creator}"></label>
|
||||
<input type="text" class="form-control" id="creator" name="creator">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-check-label" for="keywords" th:text="#{changeMetadata.keywords}"></label>
|
||||
<input type="text" class="form-control" id="keywords" name="keywords">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-check-label" for="modificationDate" th:text="#{changeMetadata.modDate}"></label>
|
||||
<input type="text" class="form-control" id="modificationDate" name="modificationDate" placeholder="2020/12/25 18:30:59">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-check-label" for="producer" th:text="#{changeMetadata.producer}"></label>
|
||||
<input type="text" class="form-control" id="producer" name="producer">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-check-label" for="subject" th:text="#{changeMetadata.subject}"></label>
|
||||
<input type="text" class="form-control" id="subject" name="subject">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-check-label" for="title" th:text="#{changeMetadata.title}"></label>
|
||||
<input type="text" class="form-control" id="title" name="title">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-check-label" for="trapped" th:text="#{changeMetadata.trapped}"></label>
|
||||
<select class="form-control" id="trapped" name="trapped">
|
||||
<option value="True" th:text="#{true}"></option>
|
||||
<option value="False" th:text="#{false}" selected></option>
|
||||
<option value="Unknown" th:text="#{unknown}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="customMetadata" style="display: none;">
|
||||
<h3 th:text="#{changeMetadata.selectText.4}"></h3>
|
||||
<div class="form-group" id="otherMetadataEntries"></div>
|
||||
</div>
|
||||
<div id="customMetadataEntries"></div>
|
||||
<button type="button" class="btn btn-secondary" id="addMetadataBtn" th:text="#{changeMetadata.selectText.5}"></button>
|
||||
<br>
|
||||
<br>
|
||||
<button class="btn btn-primary" type="submit" th:text="#{changeMetadata.submit}"></button>
|
||||
<script>
|
||||
|
||||
const deleteAllCheckbox = document.querySelector("#deleteAll");
|
||||
const inputs = document.querySelectorAll(".form-control");
|
||||
const customMetadataDiv = document.getElementById('customMetadata');
|
||||
const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries');
|
||||
|
||||
deleteAllCheckbox.addEventListener("change", function(event) {
|
||||
if (event.target !== deleteAllCheckbox) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputs.forEach(input => {
|
||||
if (input === deleteAllCheckbox) {
|
||||
return;
|
||||
}
|
||||
input.disabled = deleteAllCheckbox.checked;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const customModeCheckbox = document.getElementById('customModeCheckbox');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const addMetadataBtn = document.getElementById("addMetadataBtn");
|
||||
const customMetadataFormContainer = document.getElementById("customMetadataEntries");
|
||||
var count = 1;
|
||||
|
||||
|
||||
|
||||
|
||||
const fileInput = document.querySelector("#fileInput-input");
|
||||
const authorInput = document.querySelector("#author");
|
||||
const creationDateInput = document.querySelector("#creationDate");
|
||||
const creatorInput = document.querySelector("#creator");
|
||||
const keywordsInput = document.querySelector("#keywords");
|
||||
const modificationDateInput = document.querySelector("#modificationDate");
|
||||
const producerInput = document.querySelector("#producer");
|
||||
const subjectInput = document.querySelector("#subject");
|
||||
const titleInput = document.querySelector("#title");
|
||||
const trappedInput = document.querySelector("#trapped");
|
||||
|
||||
var lastPDFFileMeta = null;
|
||||
fileInput.addEventListener("change", async function() {
|
||||
|
||||
|
||||
|
||||
while (otherMetadataEntriesDiv.firstChild) {
|
||||
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
|
||||
}
|
||||
while (customMetadataFormContainer.firstChild) {
|
||||
customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const file = this.files[0];
|
||||
var url = URL.createObjectURL(file)
|
||||
|
||||
const pdf = await pdfjsLib.getDocument(url).promise;
|
||||
const pdfMetadata = await pdf.getMetadata();
|
||||
lastPDFFile = pdfMetadata?.info
|
||||
console.log(pdfMetadata);
|
||||
if(!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) {
|
||||
customModeCheckbox.disabled = true;
|
||||
customModeCheckbox.checked = false;
|
||||
} else {
|
||||
customModeCheckbox.disabled = false;
|
||||
}
|
||||
authorInput.value = pdfMetadata?.info?.Author;
|
||||
creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate);
|
||||
creatorInput.value = pdfMetadata?.info?.Creator;
|
||||
keywordsInput.value = pdfMetadata?.info?.Keywords;
|
||||
modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate);
|
||||
producerInput.value = pdfMetadata?.info?.Producer;
|
||||
subjectInput.value = pdfMetadata?.info?.Subject;
|
||||
titleInput.value = pdfMetadata?.info?.Title;
|
||||
console.log(pdfMetadata?.info);
|
||||
const trappedValue = pdfMetadata?.info?.Trapped;
|
||||
// Get all options in the select element
|
||||
const options = trappedInput.options;
|
||||
// Loop through all options to find the one with a matching value
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
if (options[i].value === trappedValue) {
|
||||
options[i].selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
addExtra();
|
||||
});
|
||||
|
||||
addMetadataBtn.addEventListener("click", () => {
|
||||
|
||||
const keyInput = document.createElement("input");
|
||||
keyInput.type = "text";
|
||||
keyInput.placeholder = 'Key';
|
||||
keyInput.className = "form-control";
|
||||
keyInput.name = "customKey" + count;
|
||||
|
||||
const valueInput = document.createElement("input");
|
||||
valueInput.type = "text";
|
||||
valueInput.placeholder = 'Value';
|
||||
valueInput.className = "form-control";
|
||||
valueInput.name = "customValue" + count;
|
||||
count = count + 1;
|
||||
|
||||
const formGroup = document.createElement("div");
|
||||
formGroup.className = "form-group";
|
||||
formGroup.appendChild(keyInput);
|
||||
formGroup.appendChild(valueInput);
|
||||
|
||||
|
||||
customMetadataFormContainer.appendChild(formGroup);
|
||||
});
|
||||
function convertDateFormat(dateTimeString) {
|
||||
if (!dateTimeString || dateTimeString.length < 17) {
|
||||
return dateTimeString;
|
||||
}
|
||||
|
||||
const year = dateTimeString.substring(2, 6);
|
||||
const month = dateTimeString.substring(6, 8);
|
||||
const day = dateTimeString.substring(8, 10);
|
||||
const hour = dateTimeString.substring(10, 12);
|
||||
const minute = dateTimeString.substring(12, 14);
|
||||
const second = dateTimeString.substring(14, 16);
|
||||
|
||||
return year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second;
|
||||
}
|
||||
|
||||
function addExtra() {
|
||||
const event = document.getElementById("customModeCheckbox");
|
||||
|
||||
|
||||
if (event.checked && lastPDFFile.Custom != null) {
|
||||
customMetadataDiv.style.display = 'block';
|
||||
for (const [key, value] of Object.entries(lastPDFFile.Custom)) {
|
||||
if (key === 'Author' || key === 'CreationDate' || key === 'Creator' || key === 'Keywords' || key === 'ModDate' || key === 'Producer' || key === 'Subject' || key === 'Title' || key === 'Trapped') {
|
||||
continue;
|
||||
}
|
||||
const entryDiv = document.createElement('div');
|
||||
entryDiv.className = 'form-group';
|
||||
|
||||
|
||||
|
||||
entryDiv.innerHTML = `<div class="form-group"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
|
||||
otherMetadataEntriesDiv.appendChild(entryDiv);
|
||||
}
|
||||
} else {
|
||||
customMetadataDiv.style.display = 'none';
|
||||
while (otherMetadataEntriesDiv.firstChild) {
|
||||
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
customModeCheckbox.addEventListener('change', (event) => {
|
||||
|
||||
addExtra();
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,70 +1,57 @@
|
||||
<!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>
|
||||
|
||||
|
||||
<body> <div id="page-container">
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{permissions.header}"></h2>
|
||||
<p th:text="#{permissions.warning}"></p>
|
||||
<form action="add-password" method="post"
|
||||
enctype="multipart/form-data">
|
||||
<form action="add-password" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label th:text="#{permissions.selectText.1}"></label>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput"> <label class="custom-file-label" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{permissions.selectText.2}"></label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canAssembleDocument" name="canAssembleDocument"> <label
|
||||
class="form-check-label" for="canAssembleDocument" th:text="#{permissions.selectText.3}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
|
||||
<label class="form-check-label" for="canAssembleDocument" th:text="#{permissions.selectText.3}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canExtractContent" name="canExtractContent"> <label
|
||||
class="form-check-label" for="canExtractContent" th:text="#{permissions.selectText.4}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canExtractContent" name="canExtractContent">
|
||||
<label class="form-check-label" for="canExtractContent" th:text="#{permissions.selectText.4}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canExtractForAccessibility"
|
||||
name="canExtractForAccessibility"> <label
|
||||
class="form-check-label" for="canExtractForAccessibility" th:text="#{permissions.selectText.5}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility">
|
||||
<label class="form-check-label" for="canExtractForAccessibility" th:text="#{permissions.selectText.5}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canFillInForm" name="canFillInForm"> <label
|
||||
class="form-check-label" for="canFillInForm" th:text="#{permissions.selectText.6}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canFillInForm" name="canFillInForm">
|
||||
<label class="form-check-label" for="canFillInForm" th:text="#{permissions.selectText.6}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canModify"
|
||||
name="canModify"> <label class="form-check-label"
|
||||
for="canModify" th:text="#{permissions.selectText.7}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canModify" name="canModify">
|
||||
<label class="form-check-label" for="canModify" th:text="#{permissions.selectText.7}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canModifyAnnotations" name="canModifyAnnotations"> <label
|
||||
class="form-check-label" for="canModifyAnnotations" th:text="#{permissions.selectText.8}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations">
|
||||
<label class="form-check-label" for="canModifyAnnotations" th:text="#{permissions.selectText.8}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canPrint"
|
||||
name="canPrint"> <label class="form-check-label"
|
||||
for="canPrint" th:text="#{permissions.selectText.9}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canPrint" name="canPrint">
|
||||
<label class="form-check-label" for="canPrint" th:text="#{permissions.selectText.9}"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="canPrintFaithful" name="canPrintFaithful"> <label
|
||||
class="form-check-label" for="canPrintFaithful" th:text="#{permissions.selectText.10}"></label>
|
||||
<input class="form-check-input" type="checkbox" id="canPrintFaithful" name="canPrintFaithful">
|
||||
<label class="form-check-label" for="canPrintFaithful" th:text="#{permissions.selectText.10}"></label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
<!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>
|
||||
|
||||
<body> <div id="page-container">
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{removePassword.header}"></h2>
|
||||
|
||||
<form action="add-password" method="post"
|
||||
enctype="multipart/form-data">
|
||||
<form action="remove-password" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label th:text="#{removePassword.selectText.1}"></label>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput" required> <label
|
||||
class="custom-file-label" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{removePassword.selectText.2}"></label> <input type="password"
|
||||
class="form-control" id="password" name="password" required>
|
||||
<label th:text="#{removePassword.selectText.2}"></label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<br />
|
||||
<div class="form-group text-center">
|
||||
|
||||
36
src/main/resources/templates/security/remove-watermark.html
Normal file
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)}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="watermarkText" th:text="#{remove-watermark.selectText.2}"></label>
|
||||
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
|
||||
</div>
|
||||
<div class="form-group text-center">
|
||||
<input type="submit" th:value="#{remove-watermark.submit}" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!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>
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br>
|
||||
<br>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
@@ -24,23 +23,16 @@
|
||||
<p th:text="#{split.desc.7}"></p>
|
||||
<p th:text="#{split.desc.8}"></p>
|
||||
|
||||
<form th:action="@{split-pages}" method="post"
|
||||
enctype="multipart/form-data">
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="fileInput"
|
||||
name="fileInput" required> <label
|
||||
class="custom-file-label" for="fileInput" th:text="#{pdfPrompt}"></label>
|
||||
</div>
|
||||
<form th:action="@{split-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="pages" th:text="#{split.splitPages}"></label> <input
|
||||
type="text" class="form-control" id="pages" name="pages"
|
||||
placeholder="1,3,5-10" required>
|
||||
<label for="pages" th:text="#{split.splitPages}"></label>
|
||||
<input type="text" class="form-control" id="pages" name="pages" placeholder="1,3,5-10" required>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{split.submit}"></button>
|
||||
</form>
|
||||
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user