Merge branch 'Stirling-Tools:main' into main

This commit is contained in:
leo-jmateo 2025-03-11 22:22:08 +01:00 committed by GitHub
commit ecec1e6350
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 993 additions and 564 deletions

View File

@ -8,11 +8,29 @@
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
"dockerfile": "../Dockerfile.dev" "dockerfile": "../Dockerfile.dev"
}, },
"runArgs": [
"-e",
"GIT_EDITOR=code --wait",
"--security-opt",
"label=disable"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally. // Use 'forwardPorts' to make a list of ports inside the container available locally.
"appPort": [8080], "forwardPorts": [8080, 2002, 2003],
"portsAttributes": {
"8080": {
"label": "Stirling-PDF Dev Port"
},
"2002": {
"label": "unoserver Port"
},
"2003": {
"label": "UnoConvert Port"
}
},
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated", "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
"mounts": [ "mounts": [
"source=logs-volume,target=/workspace/logs,type=volume" "source=logs-volume,target=/workspace/logs,type=volume",
"source=build-volume,target=/workspace/build,type=volume"
], ],
"workspaceFolder": "/workspace", "workspaceFolder": "/workspace",
// Configure tool-specific properties. // Configure tool-specific properties.
@ -97,18 +115,17 @@
"Oracle.oracle-java", // Oracle Java extension with additional features for Java development "Oracle.oracle-java", // Oracle Java extension with additional features for Java development
"streetsidesoftware.code-spell-checker", // Spell checker for code to avoid typos "streetsidesoftware.code-spell-checker", // Spell checker for code to avoid typos
"vmware.vscode-boot-dev-pack", // Developer tools for Spring Boot by VMware "vmware.vscode-boot-dev-pack", // Developer tools for Spring Boot by VMware
"vmware.vscode-spring-boot", // Spring Boot tools by VMware for enhanced Spring development
"vscjava.vscode-java-pack", // Java Extension Pack with essential Java tools for VS Code "vscjava.vscode-java-pack", // Java Extension Pack with essential Java tools for VS Code
"vscjava.vscode-spring-boot-dashboard", // Spring Boot dashboard for managing and visualizing Spring Boot applications
"vscjava.vscode-spring-initializr", // Support for Spring Initializr to create new Spring projects
"EditorConfig.EditorConfig", // EditorConfig support for maintaining consistent coding styles "EditorConfig.EditorConfig", // EditorConfig support for maintaining consistent coding styles
"ms-azuretools.vscode-docker", // Docker extension for Visual Studio Code "ms-azuretools.vscode-docker", // Docker extension for Visual Studio Code
"charliermarsh.ruff" // Ruff extension for Ruff language support "charliermarsh.ruff", // Ruff extension for Ruff language support
"github.vscode-github-actions" // GitHub Actions extension for Visual Studio Code
] ]
} }
}, },
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
"remoteUser": "devuser", "remoteUser": "devuser",
"shutdownAction": "stopContainer", "shutdownAction": "stopContainer",
"postStartCommand": "./scripts/init-setup.sh" "initializeCommand": "bash ./.devcontainer/git-init.sh",
"postStartCommand": "./.devcontainer/init-setup.sh"
} }

19
.devcontainer/git-init.sh Normal file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
GIT_USER=$(git config --get user.name)
GIT_EMAIL=$(git config --get user.email)
# Exit if GIT_USER or GIT_EMAIL is empty
if [ -z "$GIT_USER" ] || [ -z "$GIT_EMAIL" ]; then
echo "GIT_USER or GIT_EMAIL is not set. Exiting."
exit 1
fi
git config --local user.name "$GIT_USER"
git config --local user.email "$GIT_EMAIL"
# This directory should contain custom Git hooks for the repository
# Set the path for Git hooks to /workspace/hooks
git config --local core.hooksPath '%(prefix)/workspace/hooks'
# Set the safe directory to the workspace path
git config --local --add safe.directory /workspace

View File

@ -0,0 +1,75 @@
#!/usr/bin/env bash
set -e
# =============================================================================
# Dev Container Initialization Script (init-setup.sh)
#
# This script runs when the Dev Container starts and provides guidance on
# how to interact with the project. It prints an ASCII logo, displays the
# current user, changes to the project root, and then shows helpful command
# instructions.
#
# Instructions for future developers:
#
# - To start the application, use:
# ./gradlew bootRun --no-daemon -Dspring-boot.run.fork=true -Dserver.address=0.0.0.0
#
# - To run tests, use:
# ./gradlew test
#
# - To build the project, use:
# ./gradlew build
#
# - For running pre-commit hooks (if configured), use:
# pre-commit run --all-files
#
# Make sure you are in the project root directory after this script executes.
# =============================================================================
echo "Devcontainer started successfully!"
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
GRADLE_VERSION=$(gradle -version | grep "^Gradle " | awk '{print $2}')
GRADLE_PATH=$(which gradle)
JAVA_VERSION=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}')
JAVA_PATH=$(which java)
echo """
____ _____ ___ ____ _ ___ _ _ ____ ____ ____ _____
/ ___|_ _|_ _| _ \| | |_ _| \ | |/ ___| | _ \| _ \| ___|
\___ \ | | | || |_) | | | || \| | | _ _____| |_) | | | | |_
___) || | | || _ <| |___ | || |\ | |_| |_____| __/| |_| | _|
|____/ |_| |___|_| \_\_____|___|_| \_|\____| |_| |____/|_|
"""
echo -e "Stirling-PDF Version: \e[32m$VERSION\e[0m"
echo -e "Gradle Version: \e[32m$GRADLE_VERSION\e[0m"
echo -e "Gradle Path: \e[32m$GRADLE_PATH\e[0m"
echo -e "Java Version: \e[32m$JAVA_VERSION\e[0m"
echo -e "Java Path: \e[32m$JAVA_PATH\e[0m"
# Display current active user (for permission/debugging purposes)
echo -e "Current user: \e[32m$(whoami)\e[0m"
# Change directory to the project root (parent directory of the script)
cd "$(dirname "$0")/.."
echo -e "Changed to project root: \e[32m$(pwd)\e[0m"
# Display available commands for developers
echo "=================================================================="
echo "Available commands:"
echo ""
echo " To start unoserver: "
echo -e "\e[34m nohup /opt/venv/bin/unoserver --port 2003 --interface 0.0.0.0 > /tmp/unoserver.log 2>&1 &\e[0m"
echo
echo " To start the application: "
echo -e "\e[34m gradle bootRun\e[0m"
echo ""
echo " To run tests: "
echo -e "\e[34m gradle test\e[0m"
echo ""
echo " To build the project: "
echo -e "\e[34m gradle build\e[0m"
echo ""
echo " To run pre-commit hooks (if configured):"
echo -e "\e[34m pre-commit run --all-files -c .pre-commit-config.yaml\e[0m"
echo "=================================================================="

View File

@ -54,8 +54,8 @@ Docker:
- any-glob-to-any-file: '.github/workflows/build.yml' - any-glob-to-any-file: '.github/workflows/build.yml'
- any-glob-to-any-file: '.github/workflows/push-docker.yml' - any-glob-to-any-file: '.github/workflows/push-docker.yml'
- any-glob-to-any-file: 'Dockerfile' - any-glob-to-any-file: 'Dockerfile'
- any-glob-to-any-file: 'Dockerfile.*' - any-glob-to-any-file: 'Dockerfile.fat'
- any-glob-to-any-file: '!Dockerfile.dev' - any-glob-to-any-file: 'Dockerfile.ultra-lite'
- any-glob-to-any-file: 'exampleYmlFiles/*.yml' - any-glob-to-any-file: 'exampleYmlFiles/*.yml'
- any-glob-to-any-file: 'scripts/download-security-jar.sh' - any-glob-to-any-file: 'scripts/download-security-jar.sh'
- any-glob-to-any-file: 'scripts/init.sh' - any-glob-to-any-file: 'scripts/init.sh'
@ -67,7 +67,7 @@ Docker:
Devtools: Devtools:
- changed-files: - changed-files:
- any-glob-to-any-file: '.devcontainer/**/*' - any-glob-to-any-file: '.devcontainer/**/*'
- any-glob-to-any-file: '!Dockerfile.dev' - any-glob-to-any-file: 'Dockerfile.dev'
Test: Test:
- changed-files: - changed-files:

2
.gitignore vendored
View File

@ -26,7 +26,7 @@ clientWebUI/
!cucumber/exampleFiles/ !cucumber/exampleFiles/
!cucumber/exampleFiles/example_html.zip !cucumber/exampleFiles/example_html.zip
exampleYmlFiles/stirling/ exampleYmlFiles/stirling/
/testing/file_snapshots
# Gradle # Gradle
.gradle .gradle
.lock .lock

View File

@ -1,53 +1,54 @@
# dockerfile.dev # dockerfile.dev
# Basisimage: Gradle mit JDK 17 (Debian-basiert) # Basisimage: Gradle mit JDK 17 (Debian-basiert)
FROM gradle:8.12-jdk17 FROM gradle:8.13-jdk17
# Als Root-Benutzer arbeiten, um benötigte Pakete zu installieren # Als Root-Benutzer arbeiten, um benötigte Pakete zu installieren
USER root USER root
# Set GRADLE_HOME und füge Gradle zum PATH hinzu
ENV GRADLE_HOME=/opt/gradle
ENV PATH="$GRADLE_HOME/bin:$PATH"
# Update und Installation zusätzlicher Pakete (Debian/Ubuntu-basiert) # Update und Installation zusätzlicher Pakete (Debian/Ubuntu-basiert)
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
wget \ sudo \
ca-certificates \
tzdata \
tini \
bash \
curl \
libreoffice \ libreoffice \
poppler-utils \ poppler-utils \
qpdf \ qpdf \
# settings.yml | tessdataDir: /usr/share/tesseract-ocr/5/tessdata
tesseract-ocr \ tesseract-ocr \
tesseract-ocr-eng \ tesseract-ocr-eng \
fonts-dejavu \ fonts-terminus fonts-dejavu fonts-font-awesome fonts-noto fonts-noto-core fonts-noto-cjk fonts-noto-extra fonts-liberation fonts-linuxlibertine \
fonts-noto \ python3-uno \
python3 \
python3-pip \
python3-venv \ python3-venv \
# ss -tln
iproute2 \
&& apt-get clean && rm -rf /var/lib/apt/lists/* && apt-get clean && rm -rf /var/lib/apt/lists/*
# Setze die Environment Variable für setuptools # Setze die Environment Variable für setuptools
ENV SETUPTOOLS_USE_DISTUTILS=local ENV SETUPTOOLS_USE_DISTUTILS=local
# Installation der benötigten Python-Pakete # Installation der benötigten Python-Pakete
RUN python3 -m venv /opt/venv \ RUN python3 -m venv --system-site-packages /opt/venv \
&& . /opt/venv/bin/activate \ && . /opt/venv/bin/activate \
&& pip install --upgrade setuptools \
&& pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit && pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit
# Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind # Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind
ENV PATH="/opt/venv/bin:$PATH" ENV PATH="/opt/venv/bin:$PATH"
# Erstelle notwendige Verzeichnisse und lege einen NichtRoot Benutzer an COPY . /workspace
RUN mkdir -p /home/devuser/{configs,logs,customFiles,pipeline/watchedFolders,pipeline/finishedFolders} \
&& adduser --disabled-password --gecos '' devuser \
&& chown -R devuser:devuser /home/devuser
RUN mkdir -p /home/devuser/logs /workspace/logs /workspace/scripts /workspace/src/main/resources \ RUN adduser --disabled-password --gecos '' devuser \
&& chown -R devuser:devuser /home/devuser /workspace && chown -R devuser:devuser /home/devuser /workspace
RUN echo "devuser ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/devuser \
&& chmod 0440 /etc/sudoers.d/devuser
# Setze das Arbeitsverzeichnis (wird später per Bind-Mount überschrieben) # Setze das Arbeitsverzeichnis (wird später per Bind-Mount überschrieben)
WORKDIR /workspace WORKDIR /workspace
RUN chmod +x /workspace/.devcontainer/git-init.sh
RUN sudo chmod +x /workspace/.devcontainer/init-setup.sh
# Wechsel zum NichtRoot Benutzer # Wechsel zum NichtRoot Benutzer
USER devuser USER devuser

View File

@ -1,5 +1,11 @@
# Build the application # Build the application
FROM gradle:8.12-jdk17 AS build FROM gradle:8.12-jdk21 AS build
COPY build.gradle .
COPY settings.gradle .
COPY gradlew .
COPY gradle gradle/
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
# Set the working directory # Set the working directory
WORKDIR /app WORKDIR /app
@ -10,7 +16,7 @@ COPY . .
# Build the application with DOCKER_ENABLE_SECURITY=false # Build the application with DOCKER_ENABLE_SECURITY=false
RUN DOCKER_ENABLE_SECURITY=true \ RUN DOCKER_ENABLE_SECURITY=true \
STIRLING_PDF_DESKTOP_UI=false \ STIRLING_PDF_DESKTOP_UI=false \
./gradlew clean build ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
# Main stage # Main stage
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c

View File

@ -120,7 +120,7 @@ Stirling-PDF currently supports 39 languages!
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![87%](https://geps.dev/progress/87) | | Azerbaijani (Azərbaycan Dili) (az_AZ) | ![87%](https://geps.dev/progress/87) |
| Basque (Euskara) (eu_ES) | ![51%](https://geps.dev/progress/51) | | Basque (Euskara) (eu_ES) | ![51%](https://geps.dev/progress/51) |
| Bulgarian (Български) (bg_BG) | ![98%](https://geps.dev/progress/98) | | Bulgarian (Български) (bg_BG) | ![98%](https://geps.dev/progress/98) |
| Catalan (Català) (ca_CA) | ![80%](https://geps.dev/progress/80) | | Catalan (Català) (ca_CA) | ![95%](https://geps.dev/progress/95) |
| Croatian (Hrvatski) (hr_HR) | ![85%](https://geps.dev/progress/85) | | Croatian (Hrvatski) (hr_HR) | ![85%](https://geps.dev/progress/85) |
| Czech (Česky) (cs_CZ) | ![96%](https://geps.dev/progress/96) | | Czech (Česky) (cs_CZ) | ![96%](https://geps.dev/progress/96) |
| Danish (Dansk) (da_DK) | ![84%](https://geps.dev/progress/84) | | Danish (Dansk) (da_DK) | ![84%](https://geps.dev/progress/84) |
@ -128,7 +128,7 @@ Stirling-PDF currently supports 39 languages!
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![96%](https://geps.dev/progress/96) | | French (Français) (fr_FR) | ![96%](https://geps.dev/progress/96) |
| German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) | | German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) |
| Greek (Ελληνικά) (el_GR) | ![96%](https://geps.dev/progress/96) | | Greek (Ελληνικά) (el_GR) | ![96%](https://geps.dev/progress/96) |
| Hindi (हिंदी) (hi_IN) | ![97%](https://geps.dev/progress/97) | | Hindi (हिंदी) (hi_IN) | ![97%](https://geps.dev/progress/97) |
| Hungarian (Magyar) (hu_HU) | ![94%](https://geps.dev/progress/94) | | Hungarian (Magyar) (hu_HU) | ![94%](https://geps.dev/progress/94) |
@ -148,13 +148,13 @@ Stirling-PDF currently supports 39 languages!
| Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) | | Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) |
| Slovakian (Slovensky) (sk_SK) | ![73%](https://geps.dev/progress/73) | | Slovakian (Slovensky) (sk_SK) | ![73%](https://geps.dev/progress/73) |
| Slovenian (Slovenščina) (sl_SI) | ![95%](https://geps.dev/progress/95) | | Slovenian (Slovenščina) (sl_SI) | ![95%](https://geps.dev/progress/95) |
| Spanish (Español) (es_ES) | ![97%](https://geps.dev/progress/97) | | Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) |
| Swedish (Svenska) (sv_SE) | ![92%](https://geps.dev/progress/92) | | Swedish (Svenska) (sv_SE) | ![92%](https://geps.dev/progress/92) |
| Thai (ไทย) (th_TH) | ![84%](https://geps.dev/progress/84) | | Thai (ไทย) (th_TH) | ![84%](https://geps.dev/progress/84) |
| Tibetan (བོད་ཡིག་) (zh_BO) | ![93%](https://geps.dev/progress/93) | | Tibetan (བོད་ཡིག་) (zh_BO) | ![93%](https://geps.dev/progress/93) |
| Traditional Chinese (繁體中文) (zh_TW) | ![98%](https://geps.dev/progress/98) | | Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) |
| Turkish (Türkçe) (tr_TR) | ![81%](https://geps.dev/progress/81) | | Turkish (Türkçe) (tr_TR) | ![81%](https://geps.dev/progress/81) |
| Ukrainian (Українська) (uk_UA) | ![71%](https://geps.dev/progress/71) | | Ukrainian (Українська) (uk_UA) | ![98%](https://geps.dev/progress/98) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![78%](https://geps.dev/progress/78) | | Vietnamese (Tiếng Việt) (vi_VN) | ![78%](https://geps.dev/progress/78) |

View File

@ -25,7 +25,7 @@ ext {
} }
group = "stirling.software" group = "stirling.software"
version = "0.44.0" version = "0.44.1"
java { java {
// 17 is lowest but we support and recommend 21 // 17 is lowest but we support and recommend 21

View File

@ -1,8 +0,0 @@
#!/bin/bash
set -e
whoami
cd "$(dirname "$0")/.."
echo "Devcontainer started..."

View File

@ -0,0 +1,39 @@
package org.apache.pdfbox.examples.util;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
import lombok.extern.slf4j.Slf4j;
/** A custom RandomAccessRead implementation that deletes the file when closed */
@Slf4j
public class DeletingRandomAccessFile extends RandomAccessReadBufferedFile {
private final Path tempFilePath;
public DeletingRandomAccessFile(File file) throws IOException {
super(file);
this.tempFilePath = file.toPath();
}
@Override
public void close() throws IOException {
try {
super.close();
} finally {
try {
boolean deleted = Files.deleteIfExists(tempFilePath);
if (deleted) {
log.info("Successfully deleted temp file: {}", tempFilePath);
} else {
log.warn("Failed to delete temp file (may not exist): {}", tempFilePath);
}
} catch (IOException e) {
log.error("Error deleting temp file: {}", tempFilePath, e);
}
}
}
}

View File

@ -37,7 +37,7 @@ public class AnalysisController {
summary = "Get PDF page count", summary = "Get PDF page count",
description = "Returns total number of pages in PDF. Input:PDF Output:JSON Type:SISO") description = "Returns total number of pages in PDF. Input:PDF Output:JSON Type:SISO")
public Map<String, Integer> getPageCount(@ModelAttribute PDFFile file) throws IOException { public Map<String, Integer> getPageCount(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput().getBytes())) { try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
return Map.of("pageCount", document.getNumberOfPages()); return Map.of("pageCount", document.getNumberOfPages());
} }
} }
@ -47,7 +47,7 @@ public class AnalysisController {
summary = "Get basic PDF information", summary = "Get basic PDF information",
description = "Returns page count, version, file size. Input:PDF Output:JSON Type:SISO") description = "Returns page count, version, file size. Input:PDF Output:JSON Type:SISO")
public Map<String, Object> getBasicInfo(@ModelAttribute PDFFile file) throws IOException { public Map<String, Object> getBasicInfo(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput().getBytes())) { try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
Map<String, Object> info = new HashMap<>(); Map<String, Object> info = new HashMap<>();
info.put("pageCount", document.getNumberOfPages()); info.put("pageCount", document.getNumberOfPages());
info.put("pdfVersion", document.getVersion()); info.put("pdfVersion", document.getVersion());
@ -62,7 +62,7 @@ public class AnalysisController {
description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO") description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO")
public Map<String, String> getDocumentProperties(@ModelAttribute PDFFile file) public Map<String, String> getDocumentProperties(@ModelAttribute PDFFile file)
throws IOException { throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput().getBytes())) { try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
PDDocumentInformation info = document.getDocumentInformation(); PDDocumentInformation info = document.getDocumentInformation();
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>();
properties.put("title", info.getTitle()); properties.put("title", info.getTitle());
@ -83,7 +83,7 @@ public class AnalysisController {
description = "Returns width and height of each page. Input:PDF Output:JSON Type:SISO") description = "Returns width and height of each page. Input:PDF Output:JSON Type:SISO")
public List<Map<String, Float>> getPageDimensions(@ModelAttribute PDFFile file) public List<Map<String, Float>> getPageDimensions(@ModelAttribute PDFFile file)
throws IOException { throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput().getBytes())) { try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
List<Map<String, Float>> dimensions = new ArrayList<>(); List<Map<String, Float>> dimensions = new ArrayList<>();
PDPageTree pages = document.getPages(); PDPageTree pages = document.getPages();
@ -103,7 +103,7 @@ public class AnalysisController {
description = description =
"Returns count and details of form fields. Input:PDF Output:JSON Type:SISO") "Returns count and details of form fields. Input:PDF Output:JSON Type:SISO")
public Map<String, Object> getFormFields(@ModelAttribute PDFFile file) throws IOException { public Map<String, Object> getFormFields(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput().getBytes())) { try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
Map<String, Object> formInfo = new HashMap<>(); Map<String, Object> formInfo = new HashMap<>();
PDAcroForm form = document.getDocumentCatalog().getAcroForm(); PDAcroForm form = document.getDocumentCatalog().getAcroForm();
@ -125,7 +125,7 @@ public class AnalysisController {
summary = "Get annotation information", summary = "Get annotation information",
description = "Returns count and types of annotations. Input:PDF Output:JSON Type:SISO") description = "Returns count and types of annotations. Input:PDF Output:JSON Type:SISO")
public Map<String, Object> getAnnotationInfo(@ModelAttribute PDFFile file) throws IOException { public Map<String, Object> getAnnotationInfo(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput().getBytes())) { try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
Map<String, Object> annotInfo = new HashMap<>(); Map<String, Object> annotInfo = new HashMap<>();
int totalAnnotations = 0; int totalAnnotations = 0;
Map<String, Integer> annotationTypes = new HashMap<>(); Map<String, Integer> annotationTypes = new HashMap<>();
@ -150,7 +150,7 @@ public class AnalysisController {
description = description =
"Returns list of fonts used in the document. Input:PDF Output:JSON Type:SISO") "Returns list of fonts used in the document. Input:PDF Output:JSON Type:SISO")
public Map<String, Object> getFontInfo(@ModelAttribute PDFFile file) throws IOException { public Map<String, Object> getFontInfo(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput().getBytes())) { try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
Map<String, Object> fontInfo = new HashMap<>(); Map<String, Object> fontInfo = new HashMap<>();
Set<String> fontNames = new HashSet<>(); Set<String> fontNames = new HashSet<>();
@ -172,7 +172,7 @@ public class AnalysisController {
description = description =
"Returns encryption and permission details. Input:PDF Output:JSON Type:SISO") "Returns encryption and permission details. Input:PDF Output:JSON Type:SISO")
public Map<String, Object> getSecurityInfo(@ModelAttribute PDFFile file) throws IOException { public Map<String, Object> getSecurityInfo(@ModelAttribute PDFFile file) throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput().getBytes())) { try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
Map<String, Object> securityInfo = new HashMap<>(); Map<String, Object> securityInfo = new HashMap<>();
PDEncryption encryption = document.getEncryption(); PDEncryption encryption = document.getEncryption();

View File

@ -42,7 +42,7 @@ public class CropController {
description = description =
"This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO") "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException { public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
PDDocument sourceDocument = pdfDocumentFactory.load(form.getFileInput().getBytes()); PDDocument sourceDocument = pdfDocumentFactory.load(form);
PDDocument newDocument = PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);

View File

@ -100,8 +100,8 @@ public class MergeController {
}; };
case "byPDFTitle": case "byPDFTitle":
return (file1, file2) -> { return (file1, file2) -> {
try (PDDocument doc1 = pdfDocumentFactory.load(file1.getBytes()); try (PDDocument doc1 = pdfDocumentFactory.load(file1);
PDDocument doc2 = pdfDocumentFactory.load(file2.getBytes())) { PDDocument doc2 = pdfDocumentFactory.load(file2)) {
String title1 = doc1.getDocumentInformation().getTitle(); String title1 = doc1.getDocumentInformation().getTitle();
String title2 = doc2.getDocumentInformation().getTitle(); String title2 = doc2.getDocumentInformation().getTitle();
return title1.compareTo(title2); return title1.compareTo(title2);

View File

@ -63,7 +63,7 @@ public class MultiPageLayoutController {
: (int) Math.sqrt(pagesPerSheet); : (int) Math.sqrt(pagesPerSheet);
int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet); int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
PDDocument sourceDocument = pdfDocumentFactory.load(file.getBytes()); PDDocument sourceDocument = pdfDocumentFactory.load(file);
PDDocument newDocument = PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
PDPage newPage = new PDPage(PDRectangle.A4); PDPage newPage = new PDPage(PDRectangle.A4);

View File

@ -250,7 +250,7 @@ public class RearrangePagesPDFController {
String sortType = request.getCustomMode(); String sortType = request.getCustomMode();
try { try {
// Load the input PDF // Load the input PDF
PDDocument document = pdfDocumentFactory.load(pdfFile.getBytes()); PDDocument document = pdfDocumentFactory.load(pdfFile);
// Split the page order string into an array of page numbers or range of numbers // Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0];

View File

@ -51,7 +51,7 @@ public class ScalePagesController {
String targetPDRectangle = request.getPageSize(); String targetPDRectangle = request.getPageSize();
float scaleFactor = request.getScaleFactor(); float scaleFactor = request.getScaleFactor();
PDDocument sourceDocument = pdfDocumentFactory.load(file.getBytes()); PDDocument sourceDocument = pdfDocumentFactory.load(file);
PDDocument outputDocument = PDDocument outputDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);

View File

@ -62,7 +62,7 @@ public class SplitPDFController {
String pages = request.getPageNumbers(); String pages = request.getPageNumbers();
// open the pdf document // open the pdf document
document = pdfDocumentFactory.load(file.getBytes()); document = pdfDocumentFactory.load(file);
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document); // PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
int totalPages = document.getNumberOfPages(); int totalPages = document.getNumberOfPages();
List<Integer> pageNumbers = request.getPageNumbersList(document, false); List<Integer> pageNumbers = request.getPageNumbersList(document, false);

View File

@ -139,7 +139,7 @@ public class SplitPdfByChaptersController {
if (bookmarkLevel < 0) { if (bookmarkLevel < 0) {
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes()); return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
} }
sourceDocument = pdfDocumentFactory.load(file.getBytes()); sourceDocument = pdfDocumentFactory.load(file);
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline(); PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();

View File

@ -56,7 +56,7 @@ public class SplitPdfBySectionsController {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
PDDocument sourceDocument = pdfDocumentFactory.load(file.getBytes()); PDDocument sourceDocument = pdfDocumentFactory.load(file);
// Process the PDF based on split parameters // Process the PDF based on split parameters
int horiz = request.getHorizontalDivisions() + 1; int horiz = request.getHorizontalDivisions() + 1;

View File

@ -45,7 +45,7 @@ public class ToSinglePageController {
throws IOException { throws IOException {
// Load the source document // Load the source document
PDDocument sourceDocument = pdfDocumentFactory.load(request.getFileInput().getBytes()); PDDocument sourceDocument = pdfDocumentFactory.load(request);
// Calculate total height and max width // Calculate total height and max width
float totalHeight = 0; float totalHeight = 0;

View File

@ -74,7 +74,7 @@ public class ConvertImgPDFController {
; ;
try { try {
// Load the input PDF // Load the input PDF
byte[] newPdfBytes = rearrangePdfPages(file.getBytes(), pageOrderArr); byte[] newPdfBytes = rearrangePdfPages(file, pageOrderArr);
ImageType colorTypeResult = ImageType.RGB; ImageType colorTypeResult = ImageType.RGB;
if ("greyscale".equals(colorType)) { if ("greyscale".equals(colorType)) {
@ -243,9 +243,10 @@ public class ConvertImgPDFController {
* @return A byte array of the rearranged PDF. * @return A byte array of the rearranged PDF.
* @throws IOException If an error occurs while processing the PDF. * @throws IOException If an error occurs while processing the PDF.
*/ */
private byte[] rearrangePdfPages(byte[] pdfBytes, String[] pageOrderArr) throws IOException { private byte[] rearrangePdfPages(MultipartFile pdfFile, String[] pageOrderArr)
throws IOException {
// Load the input PDF // Load the input PDF
PDDocument document = pdfDocumentFactory.load(pdfBytes); PDDocument document = pdfDocumentFactory.load(pdfFile);
int totalPages = document.getNumberOfPages(); int totalPages = document.getNumberOfPages();
List<Integer> newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false); List<Integer> newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);

View File

@ -62,7 +62,7 @@ public class ConvertPDFToOffice {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String outputFormat = request.getOutputFormat(); String outputFormat = request.getOutputFormat();
if ("txt".equals(request.getOutputFormat())) { if ("txt".equals(request.getOutputFormat())) {
try (PDDocument document = pdfDocumentFactory.load(inputFile.getBytes())) { try (PDDocument document = pdfDocumentFactory.load(inputFile)) {
PDFTextStripper stripper = new PDFTextStripper(); PDFTextStripper stripper = new PDFTextStripper();
String text = stripper.getText(document); String text = stripper.getText(document);
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(

View File

@ -59,7 +59,7 @@ public class ExtractCSVController {
String baseName = getBaseName(form.getFileInput().getOriginalFilename()); String baseName = getBaseName(form.getFileInput().getOriginalFilename());
List<CsvEntry> csvEntries = new ArrayList<>(); List<CsvEntry> csvEntries = new ArrayList<>();
try (PDDocument document = pdfDocumentFactory.load(form.getFileInput().getBytes())) { try (PDDocument document = pdfDocumentFactory.load(form)) {
List<Integer> pages = form.getPageNumbersList(document, true); List<Integer> pages = form.getPageNumbersList(document, true);
SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm(); SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
CSVFormat format = CSVFormat format =

View File

@ -49,7 +49,7 @@ public class FilterController {
String text = request.getText(); String text = request.getText();
String pageNumber = request.getPageNumbers(); String pageNumber = request.getPageNumbers();
PDDocument pdfDocument = pdfDocumentFactory.load(inputFile.getBytes()); PDDocument pdfDocument = pdfDocumentFactory.load(inputFile);
if (PdfUtils.hasText(pdfDocument, pageNumber, text)) if (PdfUtils.hasText(pdfDocument, pageNumber, text))
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename())); pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
@ -66,7 +66,7 @@ public class FilterController {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String pageNumber = request.getPageNumbers(); String pageNumber = request.getPageNumbers();
PDDocument pdfDocument = pdfDocumentFactory.load(inputFile.getBytes()); PDDocument pdfDocument = pdfDocumentFactory.load(inputFile);
if (PdfUtils.hasImages(pdfDocument, pageNumber)) if (PdfUtils.hasImages(pdfDocument, pageNumber))
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename())); pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
@ -83,7 +83,7 @@ public class FilterController {
String pageCount = request.getPageCount(); String pageCount = request.getPageCount();
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = pdfDocumentFactory.load(inputFile.getBytes()); PDDocument document = pdfDocumentFactory.load(inputFile);
int actualPageCount = document.getNumberOfPages(); int actualPageCount = document.getNumberOfPages();
boolean valid = false; boolean valid = false;
@ -117,7 +117,7 @@ public class FilterController {
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = pdfDocumentFactory.load(inputFile.getBytes()); PDDocument document = pdfDocumentFactory.load(inputFile);
PDPage firstPage = document.getPage(0); PDPage firstPage = document.getPage(0);
PDRectangle actualPageSize = firstPage.getMediaBox(); PDRectangle actualPageSize = firstPage.getMediaBox();
@ -193,7 +193,7 @@ public class FilterController {
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = pdfDocumentFactory.load(inputFile.getBytes()); PDDocument document = pdfDocumentFactory.load(inputFile);
// Get the rotation of the first page // Get the rotation of the first page
PDPage firstPage = document.getPage(0); PDPage firstPage = document.getPage(0);

View File

@ -52,7 +52,7 @@ public class AutoRenameController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback(); Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback();
PDDocument document = pdfDocumentFactory.load(file.getBytes()); PDDocument document = pdfDocumentFactory.load(file);
PDFTextStripper reader = PDFTextStripper reader =
new PDFTextStripper() { new PDFTextStripper() {
List<LineInfo> lineInfos = new ArrayList<>(); List<LineInfo> lineInfos = new ArrayList<>();

View File

@ -84,7 +84,7 @@ public class BlankPageController {
int threshold = request.getThreshold(); int threshold = request.getThreshold();
float whitePercent = request.getWhitePercent(); float whitePercent = request.getWhitePercent();
try (PDDocument document = pdfDocumentFactory.load(inputFile.getBytes())) { try (PDDocument document = pdfDocumentFactory.load(inputFile)) {
PDPageTree pages = document.getDocumentCatalog().getPages(); PDPageTree pages = document.getDocumentCatalog().getPages();
PDFTextStripper textStripper = new PDFTextStripper(); PDFTextStripper textStripper = new PDFTextStripper();

View File

@ -50,7 +50,7 @@ public class DecompressPdfController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
try (PDDocument document = pdfDocumentFactory.load(file.getBytes())) { try (PDDocument document = pdfDocumentFactory.load(file)) {
// Process all objects in document // Process all objects in document
processAllObjects(document); processAllObjects(document);

View File

@ -95,8 +95,7 @@ public class ExtractImageScansController {
// Check if input file is a PDF // Check if input file is a PDF
if ("pdf".equalsIgnoreCase(extension)) { if ("pdf".equalsIgnoreCase(extension)) {
// Load PDF document // Load PDF document
try (PDDocument document = try (PDDocument document = pdfDocumentFactory.load(form.getFileInput())) {
pdfDocumentFactory.load(form.getFileInput().getBytes())) {
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true); pdfRenderer.setSubsamplingAllowed(true);
int pageCount = document.getNumberOfPages(); int pageCount = document.getNumberOfPages();

View File

@ -67,7 +67,7 @@ public class ExtractImagesController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String format = request.getFormat(); String format = request.getFormat();
boolean allowDuplicates = request.isAllowDuplicates(); boolean allowDuplicates = request.isAllowDuplicates();
PDDocument document = pdfDocumentFactory.load(file.getBytes()); PDDocument document = pdfDocumentFactory.load(file);
// Determine if multithreading should be used based on PDF size or number of pages // Determine if multithreading should be used based on PDF size or number of pages
boolean useMultithreading = shouldUseMultithreading(file, document); boolean useMultithreading = shouldUseMultithreading(file, document);

View File

@ -50,7 +50,7 @@ public class FlattenController {
public ResponseEntity<byte[]> flatten(@ModelAttribute FlattenRequest request) throws Exception { public ResponseEntity<byte[]> flatten(@ModelAttribute FlattenRequest request) throws Exception {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
PDDocument document = pdfDocumentFactory.load(file.getBytes()); PDDocument document = pdfDocumentFactory.load(file);
Boolean flattenOnlyForms = request.getFlattenOnlyForms(); Boolean flattenOnlyForms = request.getFlattenOnlyForms();
if (Boolean.TRUE.equals(flattenOnlyForms)) { if (Boolean.TRUE.equals(flattenOnlyForms)) {

View File

@ -84,7 +84,7 @@ public class MetadataController {
allRequestParams = new java.util.HashMap<String, String>(); allRequestParams = new java.util.HashMap<String, String>();
} }
// Load the PDF file into a PDDocument // Load the PDF file into a PDDocument
PDDocument document = pdfDocumentFactory.load(pdfFile.getBytes()); PDDocument document = pdfDocumentFactory.load(pdfFile);
// Get the document information from the PDF // Get the document information from the PDF
PDDocumentInformation info = document.getDocumentInformation(); PDDocumentInformation info = document.getDocumentInformation();

View File

@ -55,8 +55,7 @@ public class PageNumbersController {
String pagesToNumber = request.getPagesToNumber(); String pagesToNumber = request.getPagesToNumber();
String customText = request.getCustomText(); String customText = request.getCustomText();
int pageNumber = startingNumber; int pageNumber = startingNumber;
byte[] fileBytes = file.getBytes(); PDDocument document = pdfDocumentFactory.load(file);
PDDocument document = pdfDocumentFactory.load(fileBytes);
float font_size = request.getFontSize(); float font_size = request.getFontSize();
String font_type = request.getFontType(); String font_type = request.getFontType();
float marginFactor; float marginFactor;

View File

@ -43,7 +43,7 @@ public class ShowJavascript {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String script = ""; String script = "";
try (PDDocument document = pdfDocumentFactory.load(inputFile.getBytes())) { try (PDDocument document = pdfDocumentFactory.load(inputFile)) {
if (document.getDocumentCatalog() != null if (document.getDocumentCatalog() != null
&& document.getDocumentCatalog().getNames() != null) { && document.getDocumentCatalog().getNames() != null) {

View File

@ -90,7 +90,7 @@ public class CertSignController {
private static void sign( private static void sign(
CustomPDDocumentFactory pdfDocumentFactory, CustomPDDocumentFactory pdfDocumentFactory,
byte[] input, MultipartFile input,
OutputStream output, OutputStream output,
CreateSignature instance, CreateSignature instance,
Boolean showSignature, Boolean showSignature,
@ -179,7 +179,7 @@ public class CertSignController {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
sign( sign(
pdfDocumentFactory, pdfDocumentFactory,
pdf.getBytes(), pdf,
baos, baos,
createSignature, createSignature,
showSignature, showSignature,

View File

@ -126,7 +126,7 @@ public class GetInfoOnPDF {
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO") @Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException { public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
try (PDDocument pdfBoxDoc = pdfDocumentFactory.load(inputFile.getBytes()); ) { try (PDDocument pdfBoxDoc = pdfDocumentFactory.load(inputFile); ) {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
ObjectNode jsonOutput = objectMapper.createObjectNode(); ObjectNode jsonOutput = objectMapper.createObjectNode();

View File

@ -4,10 +4,12 @@ import lombok.Getter;
@Getter @Getter
public enum UsernameAttribute { public enum UsernameAttribute {
MAIL("mail"),
EMAIL("email"), EMAIL("email"),
LOGIN("login"), LOGIN("login"),
PROFILE("profile"), PROFILE("profile"),
NAME("name"), NAME("name"),
UID("uid"),
USERNAME("username"), USERNAME("username"),
NICKNAME("nickname"), NICKNAME("nickname"),
GIVEN_NAME("given_name"), GIVEN_NAME("given_name"),

View File

@ -1,9 +1,7 @@
package stirling.software.SPDF.model.api; package stirling.software.SPDF.model.api;
import java.io.IOException;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Hidden;
@ -32,18 +30,6 @@ public class PDFWithPageNums extends PDFFile {
requiredMode = RequiredMode.NOT_REQUIRED) requiredMode = RequiredMode.NOT_REQUIRED)
private String pageNumbers; private String pageNumbers;
@Hidden
public List<Integer> getPageNumbersList(boolean zeroCount) {
int pageCount = 0;
try {
pageCount = Loader.loadPDF(getFileInput().getBytes()).getNumberOfPages();
} catch (IOException e) {
// TODO Auto-generated catch block
log.error("exception", e);
}
return GeneralUtils.parsePageList(pageNumbers, pageCount, zeroCount);
}
@Hidden @Hidden
public List<Integer> getPageNumbersList(PDDocument doc, boolean oneBased) { public List<Integer> getPageNumbersList(PDDocument doc, boolean oneBased) {
int pageCount = 0; int pageCount = 0;

View File

@ -10,9 +10,9 @@ import java.nio.file.StandardCopyOption;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.examples.util.DeletingRandomAccessFile;
import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
import org.apache.pdfbox.io.RandomAccessStreamCache.StreamCacheCreateFunction; import org.apache.pdfbox.io.RandomAccessStreamCache.StreamCacheCreateFunction;
import org.apache.pdfbox.io.ScratchFile; import org.apache.pdfbox.io.ScratchFile;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@ -102,16 +102,29 @@ public class CustomPDDocumentFactory {
// Since we don't know the size upfront, buffer to a temp file // Since we don't know the size upfront, buffer to a temp file
Path tempFile = createTempFile("pdf-stream-"); Path tempFile = createTempFile("pdf-stream-");
try {
Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING); Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);
return loadAdaptively(tempFile.toFile(), Files.size(tempFile)); return loadAdaptively(tempFile.toFile(), Files.size(tempFile));
} catch (IOException e) {
cleanupFile(tempFile);
throw e;
}
} }
private PDDocument loadAdaptively(Object source, long contentSize) throws IOException { /** Load with password from InputStream */
public PDDocument load(InputStream input, String password) throws IOException {
if (input == null) {
throw new IllegalArgumentException("InputStream cannot be null");
}
// Since we don't know the size upfront, buffer to a temp file
Path tempFile = createTempFile("pdf-stream-");
Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);
return loadAdaptivelyWithPassword(tempFile.toFile(), Files.size(tempFile), password);
}
/**
* Determine the appropriate caching strategy based on file size and available memory. This
* common method is used by both password and non-password loading paths.
*/
private StreamCacheCreateFunction getStreamCacheFunction(long contentSize) {
long maxMemory = Runtime.getRuntime().maxMemory(); long maxMemory = Runtime.getRuntime().maxMemory();
long freeMemory = Runtime.getRuntime().freeMemory(); long freeMemory = Runtime.getRuntime().freeMemory();
long totalMemory = Runtime.getRuntime().totalMemory(); long totalMemory = Runtime.getRuntime().totalMemory();
@ -129,32 +142,38 @@ public class CustomPDDocumentFactory {
usedMemory / (1024 * 1024), usedMemory / (1024 * 1024),
maxMemory / (1024 * 1024)); maxMemory / (1024 * 1024));
// Determine caching strategy based on both file size and available memory
StreamCacheCreateFunction cacheFunction;
// If free memory is critically low, always use file-based caching // If free memory is critically low, always use file-based caching
// In loadAdaptively method, replace current caching strategy decision with:
if (freeMemoryPercent < MIN_FREE_MEMORY_PERCENTAGE if (freeMemoryPercent < MIN_FREE_MEMORY_PERCENTAGE
|| actualFreeMemory < MIN_FREE_MEMORY_BYTES) { || actualFreeMemory < MIN_FREE_MEMORY_BYTES) {
log.info( log.info(
"Low memory detected ({}%), forcing file-based cache", "Low memory detected ({}%), forcing file-based cache",
String.format("%.2f", freeMemoryPercent)); String.format("%.2f", freeMemoryPercent));
cacheFunction = createScratchFileCacheFunction(MemoryUsageSetting.setupTempFileOnly()); return createScratchFileCacheFunction(MemoryUsageSetting.setupTempFileOnly());
} else if (contentSize < SMALL_FILE_THRESHOLD) { } else if (contentSize < SMALL_FILE_THRESHOLD) {
log.info("Using memory-only cache for small document ({}KB)", contentSize / 1024); log.info("Using memory-only cache for small document ({}KB)", contentSize / 1024);
cacheFunction = IOUtils.createMemoryOnlyStreamCache(); return IOUtils.createMemoryOnlyStreamCache();
} else if (contentSize < LARGE_FILE_THRESHOLD) { } else if (contentSize < LARGE_FILE_THRESHOLD) {
// For medium files (10-50MB), use a mixed approach // For medium files (10-50MB), use a mixed approach
log.info( log.info(
"Using mixed memory/file cache for medium document ({}MB)", "Using mixed memory/file cache for medium document ({}MB)",
contentSize / (1024 * 1024)); contentSize / (1024 * 1024));
cacheFunction = return createScratchFileCacheFunction(MemoryUsageSetting.setupMixed(LARGE_FILE_USAGE));
createScratchFileCacheFunction(MemoryUsageSetting.setupMixed(LARGE_FILE_USAGE));
} else { } else {
log.info("Using file-based cache for large document"); log.info("Using file-based cache for large document");
cacheFunction = createScratchFileCacheFunction(MemoryUsageSetting.setupTempFileOnly()); return createScratchFileCacheFunction(MemoryUsageSetting.setupTempFileOnly());
} }
}
/** Update the existing loadAdaptively method to use the common function */
private PDDocument loadAdaptively(Object source, long contentSize) throws IOException {
// Get the appropriate caching strategy
StreamCacheCreateFunction cacheFunction = getStreamCacheFunction(contentSize);
//If small handle as bytes and remove original file
if (contentSize <= SMALL_FILE_THRESHOLD && source instanceof File file) {
source = Files.readAllBytes(file.toPath());
file.delete();
}
PDDocument document; PDDocument document;
if (source instanceof File file) { if (source instanceof File file) {
document = loadFromFile(file, contentSize, cacheFunction); document = loadFromFile(file, contentSize, cacheFunction);
@ -168,6 +187,50 @@ public class CustomPDDocumentFactory {
return document; return document;
} }
/** Load a PDF with password protection using adaptive loading strategies */
private PDDocument loadAdaptivelyWithPassword(Object source, long contentSize, String password)
throws IOException {
// Get the appropriate caching strategy
StreamCacheCreateFunction cacheFunction = getStreamCacheFunction(contentSize);
//If small handle as bytes and remove original file
if (contentSize <= SMALL_FILE_THRESHOLD && source instanceof File file) {
source = Files.readAllBytes(file.toPath());
file.delete();
}
PDDocument document;
if (source instanceof File file) {
document = loadFromFileWithPassword(file, contentSize, cacheFunction, password);
} else if (source instanceof byte[] bytes) {
document = loadFromBytesWithPassword(bytes, contentSize, cacheFunction, password);
} else {
throw new IllegalArgumentException("Unsupported source type: " + source.getClass());
}
postProcessDocument(document);
return document;
}
/** Load a file with password */
private PDDocument loadFromFileWithPassword(
File file, long size, StreamCacheCreateFunction cache, String password)
throws IOException {
return Loader.loadPDF(new DeletingRandomAccessFile(file), password, null, null, cache);
}
/** Load bytes with password */
private PDDocument loadFromBytesWithPassword(
byte[] bytes, long size, StreamCacheCreateFunction cache, String password)
throws IOException {
if (size >= SMALL_FILE_THRESHOLD) {
log.info("Writing large byte array to temp file for password-protected PDF");
Path tempFile = createTempFile("pdf-bytes-");
Files.write(tempFile, bytes);
return Loader.loadPDF(tempFile.toFile(), password, null, null, cache);
}
return Loader.loadPDF(bytes, password, null, null, cache);
}
private StreamCacheCreateFunction createScratchFileCacheFunction(MemoryUsageSetting settings) { private StreamCacheCreateFunction createScratchFileCacheFunction(MemoryUsageSetting settings) {
return () -> { return () -> {
try { try {
@ -185,11 +248,7 @@ public class CustomPDDocumentFactory {
private PDDocument loadFromFile(File file, long size, StreamCacheCreateFunction cache) private PDDocument loadFromFile(File file, long size, StreamCacheCreateFunction cache)
throws IOException { throws IOException {
if (size >= EXTREMELY_LARGE_THRESHOLD) { return Loader.loadPDF(new DeletingRandomAccessFile(file), "", null, null, cache);
log.info("Loading extremely large file via buffered access");
return Loader.loadPDF(new RandomAccessReadBufferedFile(file), "", null, null, cache);
}
return Loader.loadPDF(file, "", null, null, cache);
} }
private PDDocument loadFromBytes(byte[] bytes, long size, StreamCacheCreateFunction cache) private PDDocument loadFromBytes(byte[] bytes, long size, StreamCacheCreateFunction cache)
@ -197,12 +256,9 @@ public class CustomPDDocumentFactory {
if (size >= SMALL_FILE_THRESHOLD) { if (size >= SMALL_FILE_THRESHOLD) {
log.info("Writing large byte array to temp file"); log.info("Writing large byte array to temp file");
Path tempFile = createTempFile("pdf-bytes-"); Path tempFile = createTempFile("pdf-bytes-");
try {
Files.write(tempFile, bytes); Files.write(tempFile, bytes);
return Loader.loadPDF(tempFile.toFile(), "", null, null, cache); return loadFromFile(tempFile.toFile(), size, cache);
} finally {
cleanupFile(tempFile);
}
} }
return Loader.loadPDF(bytes, "", null, null, cache); return Loader.loadPDF(bytes, "", null, null, cache);
} }
@ -225,12 +281,9 @@ public class CustomPDDocumentFactory {
} }
} else { } else {
Path tempFile = createTempFile("pdf-save-"); Path tempFile = createTempFile("pdf-save-");
try {
document.save(tempFile.toFile()); document.save(tempFile.toFile());
return Files.readAllBytes(tempFile); return Files.readAllBytes(tempFile);
} finally {
cleanupFile(tempFile);
}
} }
} }
@ -258,17 +311,6 @@ public class CustomPDDocumentFactory {
return Files.createTempDirectory(prefix + tempCounter.incrementAndGet() + "-"); return Files.createTempDirectory(prefix + tempCounter.incrementAndGet() + "-");
} }
/** Clean up a temporary file */
private void cleanupFile(Path file) {
try {
if (Files.deleteIfExists(file)) {
log.info("Deleted temp file: {}", file);
}
} catch (IOException e) {
log.info("Error deleting temp file {}", file, e);
}
}
/** Create new document bytes based on an existing document */ /** Create new document bytes based on an existing document */
public byte[] createNewBytesBasedOnOldDocument(byte[] oldDocument) throws IOException { public byte[] createNewBytesBasedOnOldDocument(byte[] oldDocument) throws IOException {
try (PDDocument document = load(oldDocument)) { try (PDDocument document = load(oldDocument)) {
@ -339,20 +381,11 @@ public class CustomPDDocumentFactory {
/** Load from a MultipartFile */ /** Load from a MultipartFile */
public PDDocument load(MultipartFile pdfFile) throws IOException { public PDDocument load(MultipartFile pdfFile) throws IOException {
return load(pdfFile.getBytes()); return load(pdfFile.getInputStream());
} }
/** Load with password from MultipartFile */ /** Load with password from MultipartFile */
public PDDocument load(MultipartFile fileInput, String password) throws IOException { public PDDocument load(MultipartFile fileInput, String password) throws IOException {
return load(fileInput.getBytes(), password); return load(fileInput.getInputStream(), password);
}
/** Load with password from byte array */
private PDDocument load(byte[] bytes, String password) throws IOException {
// Since we don't have direct password support in the adaptive loader,
// we'll need to use PDFBox's Loader directly
PDDocument document = Loader.loadPDF(bytes, password);
pdfMetadataService.setDefaultMetadata(document);
return document;
} }
} }

View File

@ -4,6 +4,7 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Locale;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@ -27,11 +28,11 @@ public class FileInfo {
// Formats the file size into a human-readable string. // Formats the file size into a human-readable string.
public String getFormattedFileSize() { public String getFormattedFileSize() {
if (fileSize >= 1024 * 1024 * 1024) { if (fileSize >= 1024 * 1024 * 1024) {
return String.format("%.2f GB", fileSize / (1024.0 * 1024 * 1024)); return String.format(Locale.US, "%.2f GB", fileSize / (1024.0 * 1024 * 1024));
} else if (fileSize >= 1024 * 1024) { } else if (fileSize >= 1024 * 1024) {
return String.format("%.2f MB", fileSize / (1024.0 * 1024)); return String.format(Locale.US, "%.2f MB", fileSize / (1024.0 * 1024));
} else if (fileSize >= 1024) { } else if (fileSize >= 1024) {
return String.format("%.2f KB", fileSize / 1024.0); return String.format(Locale.US, "%.2f KB", fileSize / 1024.0);
} else { } else {
return String.format("%d Bytes", fileSize); return String.format("%d Bytes", fileSize);
} }

View File

@ -32,8 +32,15 @@ public class GeneralUtils {
public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException { public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
File tempFile = Files.createTempFile("temp", null).toFile(); File tempFile = Files.createTempFile("temp", null).toFile();
try (FileOutputStream os = new FileOutputStream(tempFile)) { try (InputStream inputStream = multipartFile.getInputStream();
os.write(multipartFile.getBytes()); FileOutputStream outputStream = new FileOutputStream(tempFile)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
} }
return tempFile; return tempFile;
} }

View File

@ -3,8 +3,8 @@
########### ###########
# the direction that the language is written (ltr = left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size addPageNumbers.fontSize=Розмір шрифту
addPageNumbers.fontName=Font Name addPageNumbers.fontName=Назва шрифту
pdfPrompt=Оберіть PDF(и) pdfPrompt=Оберіть PDF(и)
multiPdfPrompt=Оберіть PDFи (2+) multiPdfPrompt=Оберіть PDFи (2+)
multiPdfDropPrompt=Оберіть (або перетягніть) всі необхідні PDFи multiPdfDropPrompt=Оберіть (або перетягніть) всі необхідні PDFи
@ -56,15 +56,15 @@ userNotFoundMessage=Користувача не знайдено.
incorrectPasswordMessage=Поточний пароль невірний. incorrectPasswordMessage=Поточний пароль невірний.
usernameExistsMessage=Нове ім'я користувача вже існує. usernameExistsMessage=Нове ім'я користувача вже існує.
invalidUsernameMessage=Недійсне ім’я користувача, ім’я користувача може містити лише літери, цифри та наступні спеціальні символи @._+- або має бути дійсною електронною адресою. invalidUsernameMessage=Недійсне ім’я користувача, ім’я користувача може містити лише літери, цифри та наступні спеціальні символи @._+- або має бути дійсною електронною адресою.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end. invalidPasswordMessage=Пароль не повинен бути порожнім і не повинен мати пробілів на початку або в кінці.
confirmPasswordErrorMessage=New Password and Confirm New Password must match. confirmPasswordErrorMessage=Новий пароль і підтвердження нового пароля мають збігатися.
deleteCurrentUserMessage=Неможливо видалити користувача, який увійшов в систему. deleteCurrentUserMessage=Неможливо видалити користувача, який увійшов в систему.
deleteUsernameExistsMessage=Ім'я користувача не існує і не може бути видалено. deleteUsernameExistsMessage=Ім'я користувача не існує і не може бути видалено.
downgradeCurrentUserMessage=Неможливо понизити роль поточного користувача downgradeCurrentUserMessage=Неможливо понизити роль поточного користувача
disabledCurrentUserMessage=The current user cannot be disabled disabledCurrentUserMessage=Поточного користувача неможливо вимкнути
downgradeCurrentUserLongMessage=Неможливо понизити роль поточного користувача. Отже, поточний користувач не відображатиметься. downgradeCurrentUserLongMessage=Неможливо понизити роль поточного користувача. Отже, поточний користувач не відображатиметься.
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user. userAlreadyExistsOAuthMessage=Користувач уже існує як користувач OAuth2.
userAlreadyExistsWebMessage=The user already exists as an web user. userAlreadyExistsWebMessage=Користувач уже існує як веб-користувач.
error=Помилка error=Помилка
oops=Упс! oops=Упс!
help=Допомога help=Допомога
@ -77,18 +77,18 @@ color=Колір
sponsor=Спонсор sponsor=Спонсор
info=Інформація info=Інформація
pro=Pro pro=Pro
page=Page page=Сторінка
pages=Pages pages=Сторінки
loading=Loading... loading=Завантаження...
addToDoc=Add to Document addToDoc=Додати до документу
reset=Reset reset=Скинути
apply=Apply apply=Застосувати
legal.privacy=Privacy Policy legal.privacy=Політика конфіденційності
legal.terms=Terms and Conditions legal.terms=Правила та умови
legal.accessibility=Accessibility legal.accessibility=Доступність
legal.cookie=Cookie Policy legal.cookie=Політика використання файлів cookie
legal.impressum=Impressum legal.impressum=Вихідні дані
############### ###############
# Pipeline # # Pipeline #
@ -100,7 +100,7 @@ pipeline.defaultOption=Користувацький
pipeline.submitButton=Надіслати pipeline.submitButton=Надіслати
pipeline.help=Довідка з конвеєрної обробки pipeline.help=Довідка з конвеєрної обробки
pipeline.scanHelp=Довідка зі сканування папок pipeline.scanHelp=Довідка зі сканування папок
pipeline.deletePrompt=Are you sure you want to delete pipeline pipeline.deletePrompt=Ви впевнені, що хочете видалити конвеєр?
###################### ######################
# Pipeline Options # # Pipeline Options #
@ -118,27 +118,27 @@ pipelineOptions.validateButton=Перевірити
######################## ########################
# ENTERPRISE EDITION # # ENTERPRISE EDITION #
######################## ########################
enterpriseEdition.button=Upgrade to Pro enterpriseEdition.button=Оновлення до Pro
enterpriseEdition.warning=This feature is only available to Pro users. enterpriseEdition.warning=Ця функція доступна лише для користувачів Pro.
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. enterpriseEdition.yamlAdvert=Stirling PDF Pro підтримує конфігураційні файли YAML та інші функції SSO.
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro enterpriseEdition.ssoAdvert=Шукаєте більше функцій керування користувачами? Перегляньте Stirling PDF Pro
################# #################
# Analytics # # Analytics #
################# #################
analytics.title=Do you want make Stirling PDF better? analytics.title=Бажаєте покращити Stirling PDF?
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. analytics.paragraph1=Stirling PDF увімкнув аналітику, щоб допомогти нам покращити продукт. Ми не відстежуємо жодну особисту інформацію чи вміст файлів.
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. analytics.paragraph2=Увімкніть аналітику, щоб допомогти Stirling-PDF розвиватися та дозволити нам краще розуміти наших користувачів.
analytics.enable=Enable analytics analytics.enable=Увімкнути аналітику
analytics.disable=Disable analytics analytics.disable=Вимкнути аналітику
analytics.settings=You can change the settings for analytics in the config/settings.yml file analytics.settings=Ви можете змінити параметри аналітики у файлі config/settings.yml
############# #############
# NAVBAR # # NAVBAR #
############# #############
navbar.favorite=Обране navbar.favorite=Обране
navbar.recent=New and recently updated navbar.recent=Новий і нещодавно оновлений
navbar.darkmode=Темний режим navbar.darkmode=Темний режим
navbar.language=Мови navbar.language=Мови
navbar.settings=Налаштування navbar.settings=Налаштування
@ -151,7 +151,7 @@ navbar.sections.convertFrom=Конвертувати з PDF
navbar.sections.security=Підпис та Безпека navbar.sections.security=Підпис та Безпека
navbar.sections.advance=Додаткове navbar.sections.advance=Додаткове
navbar.sections.edit=Перегляд та Редагування navbar.sections.edit=Перегляд та Редагування
navbar.sections.popular=Popular navbar.sections.popular=Популярне
############# #############
# SETTINGS # # SETTINGS #
@ -167,9 +167,9 @@ settings.downloadOption.3=Завантажити файл
settings.zipThreshold=Zip-файли, коли кількість завантажених файлів перевищує settings.zipThreshold=Zip-файли, коли кількість завантажених файлів перевищує
settings.signOut=Вийти settings.signOut=Вийти
settings.accountSettings=Налаштування акаунта settings.accountSettings=Налаштування акаунта
settings.bored.help=Enables easter egg game settings.bored.help=Вмикає гру «пасхальне яйце».
settings.cacheInputs.name=Save form inputs settings.cacheInputs.name=Зберігати дані форм
settings.cacheInputs.help=Enable to store previously used inputs for future runs settings.cacheInputs.help=Увімкнути для збереження раніше використаних вхідних даних для майбутніх прогонів
changeCreds.title=Змінити облікові дані changeCreds.title=Змінити облікові дані
changeCreds.header=Оновіть дані вашого облікового запису changeCreds.header=Оновіть дані вашого облікового запису
@ -210,7 +210,7 @@ adminUserSettings.user=Користувач
adminUserSettings.addUser=Додати нового користувача adminUserSettings.addUser=Додати нового користувача
adminUserSettings.deleteUser=Видалити користувача adminUserSettings.deleteUser=Видалити користувача
adminUserSettings.confirmDeleteUser=Видалити цього користувача? adminUserSettings.confirmDeleteUser=Видалити цього користувача?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled? adminUserSettings.confirmChangeUserStatus=Чи потрібно вимкнути/ввімкнути користувача?
adminUserSettings.usernameInfo=Ім’я користувача може містити лише літери, цифри та наступні спеціальні символи @._+- або має бути дійсною електронною адресою. adminUserSettings.usernameInfo=Ім’я користувача може містити лише літери, цифри та наступні спеціальні символи @._+- або має бути дійсною електронною адресою.
adminUserSettings.roles=Ролі adminUserSettings.roles=Ролі
adminUserSettings.role=Роль adminUserSettings.role=Роль
@ -224,36 +224,36 @@ adminUserSettings.forceChange=Примусити користувача змін
adminUserSettings.submit=Зберегти користувача adminUserSettings.submit=Зберегти користувача
adminUserSettings.changeUserRole=Змінити роль користувача adminUserSettings.changeUserRole=Змінити роль користувача
adminUserSettings.authenticated=Автентифіковано adminUserSettings.authenticated=Автентифіковано
adminUserSettings.editOwnProfil=Edit own profile adminUserSettings.editOwnProfil=Редагувати власний профіль
adminUserSettings.enabledUser=enabled user adminUserSettings.enabledUser=активний користувач
adminUserSettings.disabledUser=disabled user adminUserSettings.disabledUser=заблокований користувач
adminUserSettings.activeUsers=Active Users: adminUserSettings.activeUsers=Активні користувачі:
adminUserSettings.disabledUsers=Disabled Users: adminUserSettings.disabledUsers=Заблоковані користувачі:
adminUserSettings.totalUsers=Total Users: adminUserSettings.totalUsers=Всього користувачів:
adminUserSettings.lastRequest=Last Request adminUserSettings.lastRequest=Останній запит
database.title=Database Import/Export database.title=Імпорт/експорт бази даних
database.header=Database Import/Export database.header=Імпорт/експорт бази даних
database.fileName=File Name database.fileName=Ім'я файлу
database.creationDate=Creation Date database.creationDate=Дата створення
database.fileSize=File Size database.fileSize=Розмір файлу
database.deleteBackupFile=Delete Backup File database.deleteBackupFile=Видалити файл резервної копії
database.importBackupFile=Import Backup File database.importBackupFile=Імпортувати файл резервної копії
database.createBackupFile=Create Backup File database.createBackupFile=Створити файл резервної копії
database.downloadBackupFile=Download Backup File database.downloadBackupFile=Завантажте файл резервної копії
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application. database.info_1=При імпорті даних важливо забезпечити правильну структуру. Якщо ви не впевнені у своїх діях, зверніться за професійною допомогою. Помилка в структурі може призвести до збоїв у роботі програми та призвести до повної непрацездатності.
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention. database.info_2=Ім'я файлу під час завантаження не має значення. Воно буде перейменовано на формат backup_user_yyyyMMddHHmm.sql для забезпечення одноманітності найменувань.
database.submit=Import Backup database.submit=Імпорт резервної копії
database.importIntoDatabaseSuccessed=Import into database successed database.importIntoDatabaseSuccessed=Імпорт до бази даних виконано вдало
database.backupCreated=Database backup successful database.backupCreated=Резервне копіювання бази даних успішно
database.fileNotFound=File not Found database.fileNotFound=Файл не знайдено
database.fileNullOrEmpty=File must not be null or empty database.fileNullOrEmpty=Файл не має бути пустим
database.failedImportFile=Failed Import File database.failedImportFile=Не вдалося імпортувати файл
database.notSupported=This function is not available for your database connection. database.notSupported=Ця функція недоступна для вашого підключення до бази даних.
session.expired=Your session has expired. Please refresh the page and try again. session.expired=Ваш сеанс закінчився. Будь ласка, оновіть сторінку та повторіть спробу.
session.refreshPage=Refresh Page session.refreshPage=Оновити сторінку
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -262,128 +262,128 @@ home.desc=Ваш локальний універсальний магазин д
home.searchBar=Пошук функцій... home.searchBar=Пошук функцій...
home.viewPdf.title=View/Edit PDF home.viewPdf.title=Перегляд/редагування PDF
home.viewPdf.desc=Перегляд, анотація, додавання тексту або зображень home.viewPdf.desc=Перегляд, анотація, додавання тексту або зображень
viewPdf.tags=view,read,annotate,text,image viewPdf.tags=перегляд,читання,анотації,текст,зображення
home.setFavorites=Set Favourites home.setFavorites=Налаштувати обрані
home.hideFavorites=Hide Favourites home.hideFavorites=Приховати обрані
home.showFavorites=Show Favourites home.showFavorites=Показати обрані
home.legacyHomepage=Old homepage home.legacyHomepage=Стара сторінка
home.newHomePage=Try our new homepage! home.newHomePage=Спробуйте нову сторінку!
home.alphabetical=Alphabetical home.alphabetical=Абеткою
home.globalPopularity=Global Popularity home.globalPopularity=Глобальною поулярністю
home.sortBy=Sort by: home.sortBy=Сортувати за:
home.multiTool.title=Мультіінструмент PDF home.multiTool.title=Мультіінструмент PDF
home.multiTool.desc=Об'єднання, поворот, зміна порядку та видалення сторінок home.multiTool.desc=Об'єднання, поворот, зміна порядку та видалення сторінок
multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side multiTool.tags=мультиінструмент,багатоопераційний,інтерфейс,перетягування,клієнтська частина,інтерактивний
home.merge.title=Об'єднати home.merge.title=Об'єднати
home.merge.desc=Легко об'єднуйте кілька PDF-файлів у один. home.merge.desc=Легко об'єднуйте кілька PDF-файлів у один.
merge.tags=merge,Page operations,Back end,server side merge.tags=об'єднання,операції зі сторінками,серверна частина
home.split.title=Розділити home.split.title=Розділити
home.split.desc=Розділіть PDF-файли на кілька документів home.split.desc=Розділіть PDF-файли на кілька документів
split.tags=Page operations,divide,Multi Page,cut,server side split.tags=операції зі сторінками,розділення,багатосторінковий,вирізання,серверна частина
home.rotate.title=Повернути home.rotate.title=Повернути
home.rotate.desc=Легко повертайте ваші PDF-файли. home.rotate.desc=Легко повертайте ваші PDF-файли.
rotate.tags=server side rotate.tags=серверна частина
home.imageToPdf.title=Зображення в PDF home.imageToPdf.title=Зображення в PDF
home.imageToPdf.desc=Перетворення зображення (PNG, JPEG, GIF) в PDF. home.imageToPdf.desc=Перетворення зображення (PNG, JPEG, GIF) в PDF.
imageToPdf.tags=conversion,img,jpg,picture,photo imageToPdf.tags=конвертація,зображення,jpg,картинка,фото
home.pdfToImage.title=PDF в зображення home.pdfToImage.title=PDF в зображення
home.pdfToImage.desc=Перетворення PDF в зображення. (PNG, JPEG, GIF) home.pdfToImage.desc=Перетворення PDF в зображення. (PNG, JPEG, GIF)
pdfToImage.tags=conversion,img,jpg,picture,photo pdfToImage.tags=конвертація,зображення,jpg,картинка,фото
home.pdfOrganiser.title=Реорганізація home.pdfOrganiser.title=Реорганізація
home.pdfOrganiser.desc=Видалення/перестановка сторінок у будь-якому порядку home.pdfOrganiser.desc=Видалення/перестановка сторінок у будь-якому порядку
pdfOrganiser.tags=duplex,even,odd,sort,move pdfOrganiser.tags=двосторонній друк,парні,непарні,сортування,переміщення
home.addImage.title=Додати зображення home.addImage.title=Додати зображення
home.addImage.desc=Додає зображення у вказане місце в PDF (в розробці) home.addImage.desc=Додає зображення у вказане місце в PDF (в розробці)
addImage.tags=img,jpg,picture,photo addImage.tags=зображення,jpg,картинка,фото
home.watermark.title=Додати водяний знак home.watermark.title=Додати водяний знак
home.watermark.desc=Додайте свій водяний знак до документа PDF. home.watermark.desc=Додайте свій водяний знак до документа PDF.
watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo watermark.tags=текст,повторний,мітка,власний,авторське право,торговельна марка,зображення,jpg,картинка,фото
home.permissions.title=Змінити дозволи home.permissions.title=Змінити дозволи
home.permissions.desc=Змініть дозволи вашого документа PDF home.permissions.desc=Змініть дозволи вашого документа PDF
permissions.tags=read,write,edit,print permissions.tags=читання,запис,редагування,друк
home.removePages.title=Видалення home.removePages.title=Видалення
home.removePages.desc=Видаліть непотрібні сторінки з документа PDF. home.removePages.desc=Видаліть непотрібні сторінки з документа PDF.
removePages.tags=Remove pages,delete pages removePages.tags=видалити сторінки,видалення сторінок
home.addPassword.title=Додати пароль home.addPassword.title=Додати пароль
home.addPassword.desc=Зашифруйте документ PDF паролем. home.addPassword.desc=Зашифруйте документ PDF паролем.
addPassword.tags=secure,security addPassword.tags=безпека,захист
home.removePassword.title=Видалити пароль home.removePassword.title=Видалити пароль
home.removePassword.desc=Зніміть захист паролем з вашого документа PDF. home.removePassword.desc=Зніміть захист паролем з вашого документа PDF.
removePassword.tags=secure,Decrypt,security,unpassword,delete password removePassword.tags=безпека,розшифровка,захист,видалення пароля
home.compressPdfs.title=Стиснути home.compressPdfs.title=Стиснути
home.compressPdfs.desc=Стискайте PDF-файли, щоб зменшити їх розмір. home.compressPdfs.desc=Стискайте PDF-файли, щоб зменшити їх розмір.
compressPdfs.tags=squish,small,tiny compressPdfs.tags=стиск,маленький,крихітний
home.changeMetadata.title=Змінити метадані home.changeMetadata.title=Змінити метадані
home.changeMetadata.desc=Змінити/видалити/додати метадані з документа PDF home.changeMetadata.desc=Змінити/видалити/додати метадані з документа PDF
changeMetadata.tags=Title,author,date,creation,time,publisher,producer,stats changeMetadata.tags=заголовок,автор,дата,створення,час,видавець,виробник,статистика
home.fileToPDF.title=Конвертувати файл в PDF home.fileToPDF.title=Конвертувати файл в PDF
home.fileToPDF.desc=Конвертуйте майже будь-який файл в PDF (DOCX, PNG, XLS, PPT, TXT та інші) home.fileToPDF.desc=Конвертуйте майже будь-який файл в PDF (DOCX, PNG, XLS, PPT, TXT та інші)
fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint fileToPDF.tags=перетворення,формат,документ,картинка,презентація,текст,конвертація,офіс,документи,word,excel,powerpoint
home.ocr.title=OCR/Очищення сканування home.ocr.title=OCR/Очищення сканування
home.ocr.desc=Очищення сканування та виявлення тексту на зображеннях у файлі PDF та повторне додавання його як текст. home.ocr.desc=Очищення сканування та виявлення тексту на зображеннях у файлі PDF та повторне додавання його як текст.
ocr.tags=recognition,text,image,scan,read,identify,detection,editable ocr.tags=розпізнавання,текст,зображення,сканування,читання,ідентифікація,виявлення,редагований
home.extractImages.title=Витягнути зображення home.extractImages.title=Витягнути зображення
home.extractImages.desc=Витягує всі зображення з PDF і зберігає їх у zip home.extractImages.desc=Витягує всі зображення з PDF і зберігає їх у zip
extractImages.tags=picture,photo,save,archive,zip,capture,grab extractImages.tags=зображення,фото,збереження,архів,zip,захоплення,захоплення
home.pdfToPDFA.title=PDF в PDF/A home.pdfToPDFA.title=PDF в PDF/A
home.pdfToPDFA.desc=Перетворення PDF в PDF/A для довготривалого зберігання home.pdfToPDFA.desc=Перетворення PDF в PDF/A для довготривалого зберігання
pdfToPDFA.tags=archive,long-term,standard,conversion,storage,preservation pdfToPDFA.tags=архів,довгостроковий,стандартний,конверсія,зберігання,консервація
home.PDFToWord.title=PDF в Word home.PDFToWord.title=PDF в Word
home.PDFToWord.desc=Перетворення PDF в формати Word (DOC, DOCX та ODT) home.PDFToWord.desc=Перетворення PDF в формати Word (DOC, DOCX та ODT)
PDFToWord.tags=doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile PDFToWord.tags=doc,docx,odt,word,перетворення,формат,перетворення,офіс,microsoft,docfile
home.PDFToPresentation.title=PDF в презентацію home.PDFToPresentation.title=PDF в презентацію
home.PDFToPresentation.desc=Перетворення PDF в формати презентацій (PPT, PPTX та ODP) home.PDFToPresentation.desc=Перетворення PDF в формати презентацій (PPT, PPTX та ODP)
PDFToPresentation.tags=slides,show,office,microsoft PDFToPresentation.tags=слайди,шоу,офіс,майкрософт
home.PDFToText.title=PDF в Text/RTF home.PDFToText.title=PDF в Text/RTF
home.PDFToText.desc=Перетворення PDF в текстовий або RTF формат home.PDFToText.desc=Перетворення PDF в текстовий або RTF формат
PDFToText.tags=richformat,richtextformat,rich text format PDFToText.tags=richformat,richtextformat,формат rich text,rtf
home.PDFToHTML.title=PDF в HTML home.PDFToHTML.title=PDF в HTML
home.PDFToHTML.desc=Перетворення PDF в формат HTML home.PDFToHTML.desc=Перетворення PDF в формат HTML
PDFToHTML.tags=web content,browser friendly PDFToHTML.tags=веб-контент,зручний для перегляду
home.PDFToXML.title=PDF в XML home.PDFToXML.title=PDF в XML
home.PDFToXML.desc=Перетворення PDF в формат XML home.PDFToXML.desc=Перетворення PDF в формат XML
PDFToXML.tags=data-extraction,structured-content,interop,transformation,convert PDFToXML.tags=вилучення даних,структурований вміст,взаємодія,перетворення,перетворення
home.ScannerImageSplit.title=Виявлення/розділення відсканованих фотографій home.ScannerImageSplit.title=Виявлення/розділення відсканованих фотографій
home.ScannerImageSplit.desc=Розділяє кілька фотографій з фото/PDF home.ScannerImageSplit.desc=Розділяє кілька фотографій з фото/PDF
ScannerImageSplit.tags=separate,auto-detect,scans,multi-photo,organize ScannerImageSplit.tags=окремий,автоматичне визначення,сканування,кілька фотографій,упорядкування
home.sign.title=Підпис home.sign.title=Підпис
home.sign.desc=Додає підпис до PDF за допомогою малюнка, тексту або зображення home.sign.desc=Додає підпис до PDF за допомогою малюнка, тексту або зображення
sign.tags=authorize,initials,drawn-signature,text-sign,image-signature sign.tags=авторизувати,ініціали,намальований-підпис,текстовий-підпис,зображення-підпис
home.flatten.title=Згладжування home.flatten.title=Згладжування
home.flatten.desc=Видалення всіх інтерактивних елементів та форм з PDF home.flatten.desc=Видалення всіх інтерактивних елементів та форм з PDF
@ -391,162 +391,162 @@ flatten.tags=static,deactivate,non-interactive,streamline
home.repair.title=Ремонт home.repair.title=Ремонт
home.repair.desc=Намагається відновити пошкоджений/зламаний PDF home.repair.desc=Намагається відновити пошкоджений/зламаний PDF
repair.tags=fix,restore,correction,recover repair.tags=виправити,відновити,виправити,відновити
home.removeBlanks.title=Видалити порожні сторінки home.removeBlanks.title=Видалити порожні сторінки
home.removeBlanks.desc=Виявляє та видаляє порожні сторінки з документа home.removeBlanks.desc=Виявляє та видаляє порожні сторінки з документа
removeBlanks.tags=cleanup,streamline,non-content,organize removeBlanks.tags=очищення,упорядкування,без вмісту,упорядкування
home.removeAnnotations.title=Видалити анотації home.removeAnnotations.title=Видалити анотації
home.removeAnnotations.desc=Видаляє всі коментарі/анотації з PDF home.removeAnnotations.desc=Видаляє всі коментарі/анотації з PDF
removeAnnotations.tags=comments,highlight,notes,markup,remove removeAnnotations.tags=коментарі,виділення,примітки,розмітка,видалення
home.compare.title=Порівняння home.compare.title=Порівняння
home.compare.desc=Порівнює та показує різницю між двома PDF-документами home.compare.desc=Порівнює та показує різницю між двома PDF-документами
compare.tags=differentiate,contrast,changes,analysis compare.tags=диференціація,контраст,зміни,аналіз
home.certSign.title=Підписати сертифікатом home.certSign.title=Підписати сертифікатом
home.certSign.desc=Підписати PDF сертифікатом/ключем (PEM/P12) home.certSign.desc=Підписати PDF сертифікатом/ключем (PEM/P12)
certSign.tags=authenticate,PEM,P12,official,encrypt certSign.tags=автентифікація,pem,p12,офіційний,шифрування
home.removeCertSign.title=Видалити підпис сертифікатом home.removeCertSign.title=Видалити підпис сертифікатом
home.removeCertSign.desc=Видалити підпис сертифікатом з PDF-документу home.removeCertSign.desc=Видалити підпис сертифікатом з PDF-документу
removeCertSign.tags=authenticate,PEM,P12,official,decrypt removeCertSign.tags=автентифікація,pem,p12,офіційний,розшифрувати
home.pageLayout.title=Об'єднати сторінки home.pageLayout.title=Об'єднати сторінки
home.pageLayout.desc=Об'єднання кількох сторінок документа PDF в одну сторінку home.pageLayout.desc=Об'єднання кількох сторінок документа PDF в одну сторінку
pageLayout.tags=merge,composite,single-view,organize pageLayout.tags=об'єднати,скласти,єдиний перегляд,упорядкувати
home.scalePages.title=Змінити розмір/масштаб сторінки home.scalePages.title=Змінити розмір/масштаб сторінки
home.scalePages.desc=Змінити розмір/масштаб сторінки та/або її вмісту. home.scalePages.desc=Змінити розмір/масштаб сторінки та/або її вмісту.
scalePages.tags=resize,modify,dimension,adapt scalePages.tags=змінити розмір,змінити,розмір,адаптувати
home.pipeline.title=Конвеєр (розширений) home.pipeline.title=Конвеєр (розширений)
home.pipeline.desc=Виконуйте кілька дій з PDF-файлами, визначаючи сценарії конвеєрної обробки. home.pipeline.desc=Виконуйте кілька дій з PDF-файлами, визначаючи сценарії конвеєрної обробки.
pipeline.tags=automate,sequence,scripted,batch-process pipeline.tags=автоматизація,послідовність,сценарій,scripted,batch-process
home.add-page-numbers.title=Додати номера сторінок home.add-page-numbers.title=Додати номера сторінок
home.add-page-numbers.desc=Додає номера сторінок по всьому документу в заданому місці home.add-page-numbers.desc=Додає номера сторінок по всьому документу в заданому місці
add-page-numbers.tags=paginate,label,organize,index add-page-numbers.tags=розбити на сторінки,позначити,упорядкувати,індексувати
home.auto-rename.title=Автоматичне перейменування PDF-файлу home.auto-rename.title=Автоматичне перейменування PDF-файлу
home.auto-rename.desc=Автоматичне перейменування файлу PDF на основі його виявленого заголовку home.auto-rename.desc=Автоматичне перейменування файлу PDF на основі його виявленого заголовку
auto-rename.tags=auto-detect,header-based,organize,relabel auto-rename.tags=автоматичне визначення,на основі заголовка,організація,зміна міток
home.adjust-contrast.title=Налаштування кольорів/контрастності home.adjust-contrast.title=Налаштування кольорів/контрастності
home.adjust-contrast.desc=Налаштування контрастності, насиченості та яскравості файлу PDF home.adjust-contrast.desc=Налаштування контрастності, насиченості та яскравості файлу PDF
adjust-contrast.tags=color-correction,tune,modify,enhance adjust-contrast.tags=корекція кольору,налаштування,зміна,покращення
home.crop.title=Обрізати PDF-файл home.crop.title=Обрізати PDF-файл
home.crop.desc=Обрізати PDF-файл, щоб зменшити його розмір (текст залишається!) home.crop.desc=Обрізати PDF-файл, щоб зменшити його розмір (текст залишається!)
crop.tags=trim,shrink,edit,shape crop.tags=обрізати,зменшувати,редагувати,формувати
home.autoSplitPDF.title=Автоматичне розділення сторінок home.autoSplitPDF.title=Автоматичне розділення сторінок
home.autoSplitPDF.desc=Автоматичне розділення відсканованого PDF-файлу за допомогою фізичного роздільника відсканованих сторінок QR-коду home.autoSplitPDF.desc=Автоматичне розділення відсканованого PDF-файлу за допомогою фізичного роздільника відсканованих сторінок QR-коду
autoSplitPDF.tags=QR-based,separate,scan-segment,organize autoSplitPDF.tags=на основі qr,відокремити,сканувати сегмент,упорядкувати
home.sanitizePdf.title=Санітарна обробка home.sanitizePdf.title=Санітарна обробка
home.sanitizePdf.desc=Видалення скриптів та інших елементів з PDF-файлів home.sanitizePdf.desc=Видалення скриптів та інших елементів з PDF-файлів
sanitizePdf.tags=clean,secure,safe,remove-threats sanitizePdf.tags=чистка,безпека,безпечні,віддалення загроз
home.URLToPDF.title=URL/сайт у PDF home.URLToPDF.title=URL/сайт у PDF
home.URLToPDF.desc=Конвертує будь-який http(s)URL у PDF home.URLToPDF.desc=Конвертує будь-який http(s)URL у PDF
URLToPDF.tags=web-capture,save-page,web-to-doc,archive URLToPDF.tags=веб-захоплення,збереження сторінки,веб-документ,архів
home.HTMLToPDF.title=HTML у PDF home.HTMLToPDF.title=HTML у PDF
home.HTMLToPDF.desc=Конвертує будь-який HTML-файл або zip-файл у PDF. home.HTMLToPDF.desc=Конвертує будь-який HTML-файл або zip-файл у PDF.
HTMLToPDF.tags=markup,web-content,transformation,convert HTMLToPDF.tags=розмітка,веб-контент,перетворення,конвертація
home.MarkdownToPDF.title=Markdown у PDF home.MarkdownToPDF.title=Markdown у PDF
home.MarkdownToPDF.desc=Конвертує будь-який файл Markdown у PDF home.MarkdownToPDF.desc=Конвертує будь-який файл Markdown у PDF
MarkdownToPDF.tags=markup,web-content,transformation,convert MarkdownToPDF.tags=розмітка,веб-контент,перетворення,конвертація
home.PDFToMarkdown.title=PDF to Markdown home.PDFToMarkdown.title=PDF у Markdown
home.PDFToMarkdown.desc=Converts any PDF to Markdown home.PDFToMarkdown.desc=Конвертує будь-який файл PDF у Markdown
PDFToMarkdown.tags=markup,web-content,transformation,convert,md PDFToMarkdown.tags=розмітка,веб-вміст,трансформація,перетворення,md
home.getPdfInfo.title=Отримати ВСЮ інформацію у форматі PDF home.getPdfInfo.title=Отримати ВСЮ інформацію у форматі PDF
home.getPdfInfo.desc=Збирає будь-яку можливу інформацію у PDF-файлах. home.getPdfInfo.desc=Збирає будь-яку можливу інформацію у PDF-файлах.
getPdfInfo.tags=infomation,data,stats,statistics getPdfInfo.tags=інформація,дані,статистика,статистика
home.extractPage.title=Видобути сторінку(и) home.extractPage.title=Видобути сторінку(и)
home.extractPage.desc=Видобуває обрані сторінки з PDF home.extractPage.desc=Видобуває обрані сторінки з PDF
extractPage.tags=extract extractPage.tags=екстракт
home.PdfToSinglePage.title=PDF на одну велику сторінку home.PdfToSinglePage.title=PDF на одну велику сторінку
home.PdfToSinglePage.desc=Об'єднує всі сторінки PDF в одну велику сторінку. home.PdfToSinglePage.desc=Об'єднує всі сторінки PDF в одну велику сторінку.
PdfToSinglePage.tags=single page PdfToSinglePage.tags=одну сторінку
home.showJS.title=Показати JavaScript home.showJS.title=Показати JavaScript
home.showJS.desc=Шукає та відображає будь-який JS, вбудований у PDF-файл. home.showJS.desc=Шукає та відображає будь-який JS, вбудований у PDF-файл.
showJS.tags=JS showJS.tags=js
home.autoRedact.title=Автоматичне редагування home.autoRedact.title=Автоматичне редагування
home.autoRedact.desc=Автоматичне затемнення (чорніння) тексту в PDF на основі вхідного тексту home.autoRedact.desc=Автоматичне затемнення (чорніння) тексту в PDF на основі вхідного тексту
autoRedact.tags=Redact,Hide,black out,black,marker,hidden autoRedact.tags=редагувати,приховати,затемнити,чорний,маркер,приховано
home.redact.title=Manual Redaction home.redact.title=Ручне редагування
home.redact.desc=Redacts a PDF based on selected text, drawn shapes and/or selected page(s) home.redact.desc=Редагує PDF-файл на основі виділеного тексту, намальованих форм і/або вибраних сторінок
redact.tags=Redact,Hide,black out,black,marker,hidden,manual redact.tags=редагувати,приховати,затемнити,чорний,маркер,приховано,вручну
home.tableExtraxt.title=PDF у CSV home.tableExtraxt.title=PDF у CSV
home.tableExtraxt.desc=Видобуває таблиці з PDF та перетворює їх у CSV home.tableExtraxt.desc=Видобуває таблиці з PDF та перетворює їх у CSV
tableExtraxt.tags=CSV,Table Extraction,extract,convert tableExtraxt.tags=csv,видобуток таблиці,вилучення,конвертація
home.autoSizeSplitPDF.title=Автоматичне розділення за розміром/кількістю home.autoSizeSplitPDF.title=Автоматичне розділення за розміром/кількістю
home.autoSizeSplitPDF.desc=Розділяє один PDF на кілька документів на основі розміру, кількості сторінок або кількості документів home.autoSizeSplitPDF.desc=Розділяє один PDF на кілька документів на основі розміру, кількості сторінок або кількості документів
autoSizeSplitPDF.tags=pdf,split,document,organization autoSizeSplitPDF.tags=pdf,розділити,документ,організація
home.overlay-pdfs.title=Накладення PDF home.overlay-pdfs.title=Накладення PDF
home.overlay-pdfs.desc=Накладення одного PDF поверх іншого PDF home.overlay-pdfs.desc=Накладення одного PDF поверх іншого PDF
overlay-pdfs.tags=Overlay overlay-pdfs.tags=накладання
home.split-by-sections.title=Розділення PDF за секціями home.split-by-sections.title=Розділення PDF за секціями
home.split-by-sections.desc=Розділення кожної сторінки PDF на менші горизонтальні та вертикальні секції home.split-by-sections.desc=Розділення кожної сторінки PDF на менші горизонтальні та вертикальні секції
split-by-sections.tags=Section Split, Divide, Customize split-by-sections.tags=розділ розділу,розділення,налаштування
home.AddStampRequest.title=Додати печатку на PDF home.AddStampRequest.title=Додати печатку на PDF
home.AddStampRequest.desc=Додавання текстової або зображення печатки у вказані місця home.AddStampRequest.desc=Додавання текстової або зображення печатки у вказані місця
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize AddStampRequest.tags=штамп,додати зображення,центральне зображення,водяний знак,pdf,вставити,налаштувати
home.removeImagePdf.title=Remove image home.removeImagePdf.title=Видалити зображення
home.removeImagePdf.desc=Remove image from PDF to reduce file size home.removeImagePdf.desc=Видаляє зображення з PDF для зменшення розміру файлу
removeImagePdf.tags=Remove Image,Page operations,Back end,server side removeImagePdf.tags=видалення зображення,операції зі сторінками,серверна частина
home.splitPdfByChapters.title=Split PDF by Chapters home.splitPdfByChapters.title=Розділити PDF за розділами
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. home.splitPdfByChapters.desc=Розділяє PDF на кілька файлів на основі структури його розділів
splitPdfByChapters.tags=split,chapters,bookmarks,organize splitPdfByChapters.tags=поділ,глави,закладки,організація
home.validateSignature.title=Validate PDF Signature home.validateSignature.title=Перевірка підпису PDF
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents home.validateSignature.desc=Перевірка цифрових підписів та сертифікатів у PDF-документах
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate validateSignature.tags=підпис,перевірка,валідація,pdf,сертифікат,цифровий підпис,перевірка підпису,перевірка сертифіката
#replace-invert-color #replace-invert-color
replace-color.title=Replace-Invert-Color replace-color.title=Заміна-інверсія кольору
replace-color.header=Replace-Invert Color PDF replace-color.header=Заміна-інверсія кольору PDF
home.replaceColorPdf.title=Replace and Invert Color home.replaceColorPdf.title=Заміна та інверсія кольору
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size home.replaceColorPdf.desc=Замінює колір тексту та фону у PDF та інвертує всі кольори PDF для зменшення розміру файлу
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side replaceColorPdf.tags=Заміна кольору, операції зі сторінками, Серверна частина
replace-color.selectText.1=Replace or Invert color Options replace-color.selectText.1=Параметри заміни або інверсії кольору
replace-color.selectText.2=Default(Default high contrast colors) replace-color.selectText.2=За замовчуванням (кольори високого розмаїття)
replace-color.selectText.3=Custom(Customized colors) replace-color.selectText.3=Користувальницькі (настроювані кольори)
replace-color.selectText.4=Full-Invert(Invert all colors) replace-color.selectText.4=Повна інверсія (інвертувати всі кольори)
replace-color.selectText.5=High contrast color options replace-color.selectText.5=Параметри високого розмаїття
replace-color.selectText.6=white text on black background replace-color.selectText.6=білий текст на чорному тлі
replace-color.selectText.7=Black text on white background replace-color.selectText.7=чорний текст на білому тлі
replace-color.selectText.8=Yellow text on black background replace-color.selectText.8=жовтий текст на чорному тлі
replace-color.selectText.9=Green text on black background replace-color.selectText.9=зелений текст на чорному тлі
replace-color.selectText.10=Choose text Color replace-color.selectText.10=Вибрати колір тексту
replace-color.selectText.11=Choose background Color replace-color.selectText.11=Вибрати колір тла
replace-color.submit=Replace replace-color.submit=Замінити
@ -571,12 +571,12 @@ login.oauth2InvalidUserInfoResponse=Недійсна відповідь з ін
login.oauth2invalidRequest=Недійсний запит login.oauth2invalidRequest=Недійсний запит
login.oauth2AccessDenied=Доступ заблоковано login.oauth2AccessDenied=Доступ заблоковано
login.oauth2InvalidTokenResponse=Недійсна відповідь з токеном login.oauth2InvalidTokenResponse=Недійсна відповідь з токеном
login.oauth2InvalidIdToken=Недійсний Id токен login.oauth2InvalidIdToken=Недійсний ідентифікаційний токен
login.relyingPartyRegistrationNotFound=No relying party registration found login.relyingPartyRegistrationNotFound=Реєстрацію довіряючої сторони не знайдено
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=Користувач деактивовано, вхід з цим ім'ям користувача заблоковано. Зверніться до адміністратора.
login.alreadyLoggedIn=You are already logged in to login.alreadyLoggedIn=Ви вже увійшли до
login.alreadyLoggedIn2=devices. Please log out of the devices and try again. login.alreadyLoggedIn2=пристроїв (а). Будь ласка, вийдіть із цих пристроїв і спробуйте знову.
login.toManySessions=You have too many active sessions login.toManySessions=У вас дуже багато активних сесій
#auto-redact #auto-redact
autoRedact.title=Автоматичне редагування autoRedact.title=Автоматичне редагування
@ -591,31 +591,31 @@ autoRedact.convertPDFToImageLabel=Перетворити PDF в зображен
autoRedact.submitButton=Надіслати autoRedact.submitButton=Надіслати
#redact #redact
redact.title=Manual Redaction redact.title=Ручне редагування
redact.header=Manual Redaction redact.header=Ручне редагування
redact.submit=Redact redact.submit=Редагувати
redact.textBasedRedaction=Text based Redaction redact.textBasedRedaction=Редагування на основі тексту
redact.pageBasedRedaction=Page-based Redaction redact.pageBasedRedaction=Редагування на основі сторінок
redact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) redact.convertPDFToImageLabel=Перетворити PDF на PDF-зображення (використовується для видалення тексту за рамкою)
redact.pageRedactionNumbers.title=Pages redact.pageRedactionNumbers.title=Сторінки
redact.pageRedactionNumbers.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1) redact.pageRedactionNumbers.placeholder=(наприклад, 1,2,8 або 4,7,12-16 або 2n-1)
redact.redactionColor.title=Redaction Color redact.redactionColor.title=Колір редагування
redact.export=Export redact.export=Експорт
redact.upload=Upload redact.upload=Завантажити
redact.boxRedaction=Box draw redaction redact.boxRedaction=Редагування малюванням рамки
redact.zoom=Zoom redact.zoom=Масштаб
redact.zoomIn=Zoom in redact.zoomIn=Збільшити
redact.zoomOut=Zoom out redact.zoomOut=Зменшити
redact.nextPage=Next Page redact.nextPage=Наступна сторінка
redact.previousPage=Previous Page redact.previousPage=Попередня сторінка
redact.toggleSidebar=Toggle Sidebar redact.toggleSidebar=Перемикати бічну панель
redact.showThumbnails=Show Thumbnails redact.showThumbnails=Показати мініатюри
redact.showDocumentOutline=Show Document Outline (double-click to expand/collapse all items) redact.showDocumentOutline=Показати структуру документа (подвійне клацання для розгортання/згортання всіх елементів)
redact.showAttatchments=Show Attachments redact.showAttatchments=Показати вкладення
redact.showLayers=Show Layers (double-click to reset all layers to the default state) redact.showLayers=Показати шари (подвійне клацання для скидання всіх шарів до стану за умовчанням)
redact.colourPicker=Colour Picker redact.colourPicker=Вибір кольору
redact.findCurrentOutlineItem=Find current outline item redact.findCurrentOutlineItem=Знайти поточний елемент структури
redact.applyChanges=Apply Changes redact.applyChanges=Застосувати зміни
#showJS #showJS
showJS.title=Показати JavaScript showJS.title=Показати JavaScript
@ -831,14 +831,14 @@ removeAnnotations.submit=Видалити
#compare #compare
compare.title=Порівняння compare.title=Порівняння
compare.header=Порівняння PDF compare.header=Порівняння PDF
compare.highlightColor.1=Highlight Color 1: compare.highlightColor.1=Колір виділення 1:
compare.highlightColor.2=Highlight Color 2: compare.highlightColor.2=Колір виділення 2:
compare.document.1=Документ 1 compare.document.1=Документ 1
compare.document.2=Документ 2 compare.document.2=Документ 2
compare.submit=Порівняти compare.submit=Порівняти
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced compare.complex.message=Один або обидва надані документи є великими файлами, точність порівняння може бути знижена
compare.large.file.message=One or Both of the provided documents are too large to process compare.large.file.message=Один або обидва надані документи занадто великі для обробки
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. compare.no.text.message=Вибрані PDF-файли не містять текстового вмісту. Будь ласка, виберіть PDF-файли з текстом для порівняння.
#sign #sign
sign.title=Підпис sign.title=Підпис
@ -848,18 +848,18 @@ sign.draw=Намалювати підпис
sign.text=Ввід тексту sign.text=Ввід тексту
sign.clear=Очистити sign.clear=Очистити
sign.add=Додати sign.add=Додати
sign.saved=Saved Signatures sign.saved=Збережені підписи
sign.save=Save Signature sign.save=Зберегти підпис
sign.personalSigs=Personal Signatures sign.personalSigs=Особисті підписи
sign.sharedSigs=Shared Signatures sign.sharedSigs=Загальні підписи
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=Збережені підписи не знайдено
sign.addToAll=Add to all pages sign.addToAll=Додати на всі сторінки
sign.delete=Delete sign.delete=Видалити
sign.first=First page sign.first=Перша сторінка
sign.last=Last page sign.last=Остання сторінка
sign.next=Next page sign.next=Наступна сторінка
sign.previous=Previous page sign.previous=Попередня сторінка
sign.maintainRatio=Toggle maintain aspect ratio sign.maintainRatio=Переключити збереження пропорцій
#repair #repair
@ -886,7 +886,7 @@ ScannerImageSplit.selectText.7=Мінімальна площа контуру:
ScannerImageSplit.selectText.8=Встановлює мінімальний поріг площі контуру для фотографії ScannerImageSplit.selectText.8=Встановлює мінімальний поріг площі контуру для фотографії
ScannerImageSplit.selectText.9=Розмір рамки: ScannerImageSplit.selectText.9=Розмір рамки:
ScannerImageSplit.selectText.10=Встановлює розмір додаваної та видаляної рамки, щоб запобігти появі білих рамок на виході (за замовчуванням: 1). ScannerImageSplit.selectText.10=Встановлює розмір додаваної та видаляної рамки, щоб запобігти появі білих рамок на виході (за замовчуванням: 1).
ScannerImageSplit.info=Python is not installed. It is required to run. ScannerImageSplit.info=Python не встановлено. Він необхідний роботи.
#OCR #OCR
@ -976,45 +976,45 @@ pdfOrganiser.placeholder=(наприклад, 1,3,2 або 4-8,2,10-12 або 2n
#multiTool #multiTool
multiTool.title=Мультіінструмент PDF multiTool.title=Мультиінструмент PDF
multiTool.header=Мультіінструмент PDF multiTool.header=Мультиінструмент PDF
multiTool.uploadPrompts=Ім'я файлу multiTool.uploadPrompts=Ім'я файлу
multiTool.selectAll=Select All multiTool.selectAll=Вибрати все
multiTool.deselectAll=Deselect All multiTool.deselectAll=Скасувати вибір усіх
multiTool.selectPages=Page Select multiTool.selectPages=Вибір сторінки
multiTool.selectedPages=Selected Pages multiTool.selectedPages=Вибрані сторінки
multiTool.page=Page multiTool.page=Сторінка
multiTool.deleteSelected=Delete Selected multiTool.deleteSelected=Видалити вибрані
multiTool.downloadAll=Export multiTool.downloadAll=Експорт
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Експорт вибраних
multiTool.insertPageBreak=Insert Page Break multiTool.insertPageBreak=Вставити розрив сторінки
multiTool.addFile=Add File multiTool.addFile=Додати файл
multiTool.rotateLeft=Rotate Left multiTool.rotateLeft=Повернути вліво
multiTool.rotateRight=Rotate Right multiTool.rotateRight=Повернути праворуч
multiTool.split=Split multiTool.split=Розділити
multiTool.moveLeft=Move Left multiTool.moveLeft=Перемістити вліво
multiTool.moveRight=Move Right multiTool.moveRight=Перемістити праворуч
multiTool.delete=Delete multiTool.delete=Видалити
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Вибрано сторінок
multiTool.undo=Undo multiTool.undo=Скасувати
multiTool.redo=Redo multiTool.redo=Повторити
#decrypt #decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password: decrypt.passwordPrompt=Цей файл захищений паролем. Будь ласка, введіть пароль:
decrypt.cancelled=Operation cancelled for PDF: {0} decrypt.cancelled=Операцію скасовано для PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0} decrypt.noPassword=Не надано пароль для зашифрованого PDF: {0}
decrypt.invalidPassword=Please try again with the correct password. decrypt.invalidPassword=Будь ласка, спробуйте ще раз з правильним паролем.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0} decrypt.invalidPasswordHeader=Неправильний пароль або непідтримуване шифрування для PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again. decrypt.unexpectedError=Виникла помилка при обробці файлу. Будь ласка, спробуйте ще раз.
decrypt.serverError=Server error while decrypting: {0} decrypt.serverError=Помилка сервера під час розшифровки: {0}
decrypt.success=File decrypted successfully. decrypt.success=Файл успішно розшифровано.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=Ця функція також доступна на нашій <a href="{0}">сторінці мультиінструменту</a>. Спробуйте її для покращеного посторінкового інтерфейсу та додаткових можливостей!
#view pdf #view pdf
viewPdf.title=View/Edit PDF viewPdf.title=Перегляд/редагування PDF
viewPdf.header=Переглянути PDF viewPdf.header=Переглянути PDF
#pageRemover #pageRemover
@ -1280,15 +1280,15 @@ survey.please=Будь-ласка, пройдіть опитування!
survey.disabled=(Вікно з опитування буде відключено у наступних оновленнях, але буде доступне внизу сторінки) survey.disabled=(Вікно з опитування буде відключено у наступних оновленнях, але буде доступне внизу сторінки)
survey.button=Пройти опитування survey.button=Пройти опитування
survey.dontShowAgain=Не показувати це вікно survey.dontShowAgain=Не показувати це вікно
survey.meeting.1=If you're using Stirling PDF at work, we'd love to speak to you. We're offering technical support sessions in exchange for a 15 minute user discovery session. survey.meeting.1=Якщо ви використовуєте Stirling PDF на роботі, ми будемо раді поговорити з вами. Ми пропонуємо сеанси технічної підтримки в обмін на 15-хвилинний сеанс пошуку користувачів.
survey.meeting.2=This is a chance to: survey.meeting.2=Це можливість:
survey.meeting.3=Get help with deployment, integrations, or troubleshooting survey.meeting.3=Отримайте допомогу щодо розгортання, інтеграції або усунення несправностей
survey.meeting.4=Provide direct feedback on performance, edge cases, and feature gaps survey.meeting.4=Надайте прямий відгук про продуктивність, крайні випадки та недоліки функцій
survey.meeting.5=Help us refine Stirling PDF for real-world enterprise use survey.meeting.5=Допоможіть нам удосконалити Stirling PDF для реального корпоративного використання
survey.meeting.6=If you're interested, you can book time with our team directly. (English speaking only) survey.meeting.6=Якщо ви зацікавлені, ви можете забронювати час безпосередньо з нашою командою. (тільки англомовний)
survey.meeting.7=Looking forward to digging into your use cases and making Stirling PDF even better! survey.meeting.7=З нетерпінням чекаємо на можливість розібратися у ваших сценаріях використання та зробити Stirling PDF ще кращим!
survey.meeting.notInterested=Not a business and/or interested in a meeting? survey.meeting.notInterested=Не бізнес і/або зацікавлені у зустрічі?
survey.meeting.button=Book meeting survey.meeting.button=Зустріч
#error #error
error.sorry=Вибачте за незручності! error.sorry=Вибачте за незручності!
@ -1305,70 +1305,70 @@ error.discordSubmit=Discord - Надіслати повідомлення під
#remove-image #remove-image
removeImage.title=Remove image removeImage.title=Видалити зображення
removeImage.header=Remove image removeImage.header=Видалити зображення
removeImage.removeImage=Remove image removeImage.removeImage=Видалити зображення
removeImage.submit=Remove image removeImage.submit=Видалити зображення
splitByChapters.title=Split PDF by Chapters splitByChapters.title=Розділити PDF по главам
splitByChapters.header=Split PDF by Chapters splitByChapters.header=Розділити PDF по главам
splitByChapters.bookmarkLevel=Bookmark Level splitByChapters.bookmarkLevel=Уровень закладок
splitByChapters.includeMetadata=Include Metadata splitByChapters.includeMetadata=Включити метаданні
splitByChapters.allowDuplicates=Allow Duplicates splitByChapters.allowDuplicates=Разрешить публикации
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. splitByChapters.desc.1=Цей інструмент розділяє PDF-файл на кілька PDF-файлів на основі своєї структури глави.
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). splitByChapters.desc.2=Уровень закладок: виберіть рівень закладок для розподілу (0 для верхнього рівня, 1 для другого рівня і т.д.).
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Включити метаданні: якщо позначено, метаданні вихідного PDF будуть включені в кожен розділений PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Розрішити публікації: якщо позначено, можна створити окремий PDF із кількох закладок на одній сторінці.
splitByChapters.submit=Split PDF splitByChapters.submit=Розділити PDF
#File Chooser #File Chooser
fileChooser.click=Click fileChooser.click=Натисніть
fileChooser.or=or fileChooser.or=або
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Перетащите
fileChooser.dragAndDropPDF=Drag & Drop PDF file fileChooser.dragAndDropPDF=Перетащите PDF-файл
fileChooser.dragAndDropImage=Drag & Drop Image file fileChooser.dragAndDropImage=Перетащите файл зображення
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Перетащите файл(и) сюда
fileChooser.extractPDF=Extracting... fileChooser.extractPDF=Видобування...
#release notes #release notes
releases.footer=Releases releases.footer=Релізи
releases.title=Release Notes releases.title=Примечания к релизу
releases.header=Release Notes releases.header=Примечания к релизу
releases.current.version=Current Release releases.current.version=Текущий релиз
releases.note=Release notes are only available in English releases.note=Примітка до релізу доступна тільки на англійській мові
#Validate Signature #Validate Signature
validateSignature.title=Validate PDF Signatures validateSignature.title=Перевірка підписів PDF
validateSignature.header=Validate Digital Signatures validateSignature.header=Перевірка цифрових підписів
validateSignature.selectPDF=Select signed PDF file validateSignature.selectPDF=Виберіть підписаний PDF-файл
validateSignature.submit=Validate Signatures validateSignature.submit=Перевірити підписи
validateSignature.results=Validation Results validateSignature.results=Результаты проверки
validateSignature.status=Status validateSignature.status=Статус
validateSignature.signer=Signer validateSignature.signer=Підписант
validateSignature.date=Date validateSignature.date=Дата
validateSignature.reason=Reason validateSignature.reason=Причина
validateSignature.location=Location validateSignature.location=Местоположение
validateSignature.noSignatures=No digital signatures found in this document validateSignature.noSignatures=В цьому документі не знайдено цифрових підписів
validateSignature.status.valid=Valid validateSignature.status.valid=Дійна
validateSignature.status.invalid=Invalid validateSignature.status.invalid=Недійсна
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity validateSignature.chain.invalid=Перевірка цепочки сертифікатів не удалась - неможливо перевірити особистість підписанта
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified validateSignature.trust.invalid=Сертифікат відсутній у довіреному сховищі - джерело не може бути перевірено
validateSignature.cert.expired=Certificate has expired validateSignature.cert.expired=Срок дії сертифіката істеку
validateSignature.cert.revoked=Certificate has been revoked validateSignature.cert.revoked=Сертифікат був отозван
validateSignature.signature.info=Signature Information validateSignature.signature.info=Інформація про підписи
validateSignature.signature=Signature validateSignature.signature=Подпись
validateSignature.signature.mathValid=Signature is mathematically valid BUT: validateSignature.signature.mathValid=Подпись математически корректна, НО:
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional) validateSignature.selectCustomCert=Користувачський файл сертифіката X.509 (Необов'язково)
validateSignature.cert.info=Certificate Details validateSignature.cert.info=Сведения про сертифікати
validateSignature.cert.issuer=Issuer validateSignature.cert.issuer=Издатель
validateSignature.cert.subject=Subject validateSignature.cert.subject=суб'єкт
validateSignature.cert.serialNumber=Serial Number validateSignature.cert.serialNumber=Серийний номер
validateSignature.cert.validFrom=Valid From validateSignature.cert.validFrom=Дійсний з
validateSignature.cert.validUntil=Valid Until validateSignature.cert.validUntil=Дійсний до
validateSignature.cert.algorithm=Algorithm validateSignature.cert.algorithm=Алгоритм
validateSignature.cert.keySize=Key Size validateSignature.cert.keySize=Розмір ключа
validateSignature.cert.version=Version validateSignature.cert.version=Версія
validateSignature.cert.keyUsage=Key Usage validateSignature.cert.keyUsage=Використання ключа
validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.selfSigned=Самоподписанный
validateSignature.cert.bits=bits validateSignature.cert.bits=біт

View File

@ -0,0 +1,32 @@
package stirling.software.SPDF.utils;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class FileInfoTest {
@ParameterizedTest(name = "{index}: fileSize={0}")
@CsvSource({
"0, '0 Bytes'",
"1023, '1023 Bytes'",
"1024, '1.00 KB'",
"1048575, '1024.00 KB'", // Do we really want this as result?
"1048576, '1.00 MB'",
"1073741823, '1024.00 MB'", // Do we really want this as result?
"1073741824, '1.00 GB'"
})
void testGetFormattedFileSize(long fileSize, String expectedFormattedSize) {
FileInfo fileInfo = new FileInfo(
"example.txt",
"/path/to/example.txt",
LocalDateTime.now(),
fileSize,
LocalDateTime.now().minusDays(1));
assertEquals(expectedFormattedSize, fileInfo.getFormattedFileSize());
}
}

View File

@ -39,6 +39,136 @@ check_health() {
return 0 return 0
} }
# Function to capture file list from a Docker container
capture_file_list() {
local container_name=$1
local output_file=$2
echo "Capturing file list from $container_name..."
# Get all files in one command, output directly from Docker to avoid path issues
# Skip proc, sys, dev, and the specified LibreOffice config directory
# Also skip PDFBox and LibreOffice temporary files
docker exec $container_name sh -c "find / -type f \
-not -path '*/proc/*' \
-not -path '*/sys/*' \
-not -path '*/dev/*' \
-not -path '/config/*' \
-not -path '/logs/*' \
-not -path '*/home/stirlingpdfuser/.config/libreoffice/*' \
-not -path '*/tmp/PDFBox*' \
-not -path '*/tmp/hsperfdata_stirlingpdfuser/*' \
-not -path '*/tmp/lu*' \
-not -path '*/tmp/tmp*' \
2>/dev/null | xargs -I{} sh -c 'stat -c \"%n %s %Y\" \"{}\" 2>/dev/null || true' | sort" > "$output_file"
# Check if the output file has content
if [ ! -s "$output_file" ]; then
echo "WARNING: Failed to capture file list or container returned empty list"
echo "Trying alternative approach..."
# Alternative simpler approach - just get paths as a fallback
docker exec $container_name sh -c "find / -type f \
-not -path '*/proc/*' \
-not -path '*/sys/*' \
-not -path '*/dev/*' \
-not -path '/config/*' \
-not -path '/logs/*' \
-not -path '*/home/stirlingpdfuser/.config/libreoffice/*' \
-not -path '*/tmp/PDFBox*' \
-not -path '*/tmp/hsperfdata_stirlingpdfuser/*' \
-not -path '*/tmp/lu*' \
-not -path '*/tmp/tmp*' \
2>/dev/null | sort" > "$output_file"
if [ ! -s "$output_file" ]; then
echo "ERROR: All attempts to capture file list failed"
# Create a dummy entry to prevent diff errors
echo "NO_FILES_FOUND 0 0" > "$output_file"
fi
fi
echo "File list captured to $output_file"
}
# Function to compare before and after file lists
compare_file_lists() {
local before_file=$1
local after_file=$2
local diff_file=$3
local container_name=$4 # Added container_name parameter
echo "Comparing file lists..."
# Check if files exist and have content
if [ ! -s "$before_file" ] || [ ! -s "$after_file" ]; then
echo "WARNING: One or both file lists are empty."
if [ ! -s "$before_file" ]; then
echo "Before file is empty: $before_file"
fi
if [ ! -s "$after_file" ]; then
echo "After file is empty: $after_file"
fi
# Create empty diff file
> "$diff_file"
# Check if we at least have the after file to look for temp files
if [ -s "$after_file" ]; then
echo "Checking for temp files in the after snapshot..."
grep -i "tmp\|temp" "$after_file" > "${diff_file}.tmp"
if [ -s "${diff_file}.tmp" ]; then
echo "WARNING: Temporary files found:"
cat "${diff_file}.tmp"
echo "Printing docker logs due to temporary file detection:"
docker logs "$container_name" # Print logs when temp files are found
return 1
else
echo "No temporary files found in the after snapshot."
fi
fi
return 0
fi
# Both files exist and have content, proceed with diff
diff "$before_file" "$after_file" > "$diff_file"
if [ -s "$diff_file" ]; then
echo "Detected changes in files:"
cat "$diff_file"
# Extract only added files (lines starting with ">")
grep "^>" "$diff_file" > "${diff_file}.added" || true
if [ -s "${diff_file}.added" ]; then
echo "New files created during test:"
cat "${diff_file}.added" | sed 's/^> //'
# Check for tmp files
grep -i "tmp\|temp" "${diff_file}.added" > "${diff_file}.tmp" || true
if [ -s "${diff_file}.tmp" ]; then
echo "WARNING: Temporary files detected:"
cat "${diff_file}.tmp"
echo "Printing docker logs due to temporary file detection:"
docker logs "$container_name" # Print logs when temp files are found
return 1
fi
fi
# Extract only removed files (lines starting with "<")
grep "^<" "$diff_file" > "${diff_file}.removed" || true
if [ -s "${diff_file}.removed" ]; then
echo "Files removed during test:"
cat "${diff_file}.removed" | sed 's/^< //'
fi
else
echo "No file changes detected during test."
fi
return 0
}
# Function to test a Docker Compose configuration # Function to test a Docker Compose configuration
test_compose() { test_compose() {
local compose_file=$1 local compose_file=$1
@ -91,7 +221,7 @@ main() {
# Building Docker images # Building Docker images
# docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile . # docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile .
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite . docker build --build-arg VERSION_TAG=alpha -t docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .
# Test each configuration # Test each configuration
run_tests "Stirling-PDF-Ultra-Lite" "./exampleYmlFiles/docker-compose-latest-ultra-lite.yml" run_tests "Stirling-PDF-Ultra-Lite" "./exampleYmlFiles/docker-compose-latest-ultra-lite.yml"
@ -147,16 +277,55 @@ main() {
run_tests "Stirling-PDF-Security-Fat-with-login" "./exampleYmlFiles/test_cicd.yml" run_tests "Stirling-PDF-Security-Fat-with-login" "./exampleYmlFiles/test_cicd.yml"
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
# Create directory for file snapshots if it doesn't exist
SNAPSHOT_DIR="$PROJECT_ROOT/testing/file_snapshots"
mkdir -p "$SNAPSHOT_DIR"
# Capture file list before running behave tests
BEFORE_FILE="$SNAPSHOT_DIR/files_before_behave.txt"
AFTER_FILE="$SNAPSHOT_DIR/files_after_behave.txt"
DIFF_FILE="$SNAPSHOT_DIR/files_diff.txt"
# Define container name variable for consistency
CONTAINER_NAME="Stirling-PDF-Security-Fat-with-login"
capture_file_list "$CONTAINER_NAME" "$BEFORE_FILE"
cd "testing/cucumber" cd "testing/cucumber"
if python -m behave; then if python -m behave; then
# Wait 10 seconds before capturing the file list after tests
echo "Waiting 5 seconds for any file operations to complete..."
sleep 5
# Capture file list after running behave tests
cd "$PROJECT_ROOT"
capture_file_list "$CONTAINER_NAME" "$AFTER_FILE"
# Compare file lists
if compare_file_lists "$BEFORE_FILE" "$AFTER_FILE" "$DIFF_FILE" "$CONTAINER_NAME"; then
echo "No unexpected temporary files found."
passed_tests+=("Stirling-PDF-Regression")
else
echo "WARNING: Unexpected temporary files detected after behave tests!"
failed_tests+=("Stirling-PDF-Regression-Temp-Files")
fi
passed_tests+=("Stirling-PDF-Regression") passed_tests+=("Stirling-PDF-Regression")
else else
failed_tests+=("Stirling-PDF-Regression") failed_tests+=("Stirling-PDF-Regression")
echo "Printing docker logs of failed regression" echo "Printing docker logs of failed regression"
docker logs "Stirling-PDF-Security-Fat-with-login" docker logs "$CONTAINER_NAME"
echo "Printed docker logs of failed regression" echo "Printed docker logs of failed regression"
# Still capture file list after failure for analysis
# Wait 10 seconds before capturing the file list
echo "Waiting 5 seconds before capturing file list..."
sleep 10
cd "$PROJECT_ROOT"
capture_file_list "$CONTAINER_NAME" "$AFTER_FILE"
compare_file_lists "$BEFORE_FILE" "$AFTER_FILE" "$DIFF_FILE" "$CONTAINER_NAME"
fi fi
cd "$PROJECT_ROOT"
fi fi
docker-compose -f "./exampleYmlFiles/test_cicd.yml" down docker-compose -f "./exampleYmlFiles/test_cicd.yml" down

View File

@ -2,122 +2,173 @@
# Function to check a single webpage # Function to check a single webpage
check_webpage() { check_webpage() {
local url=$(echo "$1" | tr -d '\r') # Remove carriage returns local url=$(echo "$1" | tr -d '\r') # Remove carriage returns
local base_url=$(echo "$2" | tr -d '\r') local base_url=$(echo "$2" | tr -d '\r')
local full_url="${base_url}${url}" local full_url="${base_url}${url}"
local timeout=10 local timeout=10
echo -n "Testing $full_url ... " local result_file="$3"
# Use curl to fetch the page with timeout
response=$(curl -s -w "\n%{http_code}" --max-time $timeout "$full_url")
if [ $? -ne 0 ]; then
echo "FAILED - Connection error or timeout $full_url "
return 1
fi
# Split response into body and status code # Use curl to fetch the page with timeout
HTTP_STATUS=$(echo "$response" | tail -n1) response=$(curl -s -w "\n%{http_code}" --max-time $timeout "$full_url")
BODY=$(echo "$response" | sed '$d') if [ $? -ne 0 ]; then
echo "FAILED - Connection error or timeout $full_url" >> "$result_file"
return 1
fi
# Check HTTP status # Split response into body and status code
if [ "$HTTP_STATUS" != "200" ]; then HTTP_STATUS=$(echo "$response" | tail -n1)
echo "FAILED - HTTP Status: $HTTP_STATUS" BODY=$(echo "$response" | sed '$d')
return 1
fi
# Check if response contains HTML # Check HTTP status
if ! printf '%s' "$BODY" | grep -q "<!DOCTYPE html>\|<html"; then if [ "$HTTP_STATUS" != "200" ]; then
echo "FAILED - Response is not HTML" echo "FAILED - HTTP Status: $HTTP_STATUS - $full_url" >> "$result_file"
return 1 return 1
fi fi
echo "OK" # Check if response contains HTML
return 0 if ! printf '%s' "$BODY" | grep -q "<!DOCTYPE html>\|<html"; then
echo "FAILED - Response is not HTML - $full_url" >> "$result_file"
return 1
fi
echo "OK - $full_url" >> "$result_file"
return 0
} }
# Main function to test all URLs from the list # Function to test a URL and update counters
test_url() {
local url="$1"
local base_url="$2"
local tmp_dir="$3"
local url_index="$4"
local result_file="${tmp_dir}/result_${url_index}.txt"
if ! check_webpage "$url" "$base_url" "$result_file"; then
echo "1" > "${tmp_dir}/failed_${url_index}"
else
echo "0" > "${tmp_dir}/failed_${url_index}"
fi
}
# Main function to test all URLs from the list in parallel
test_all_urls() { test_all_urls() {
local url_file=$1 local url_file="$1"
local base_url=${2:-"http://localhost:8080"} local base_url="${2:-"http://localhost:8080"}"
local failed_count=0 local max_parallel="${3:-10}" # Default to 10 parallel processes
local total_count=0 local failed_count=0
local start_time=$(date +%s) local total_count=0
local start_time=$(date +%s)
local tmp_dir=$(mktemp -d)
local active_jobs=0
local url_index=0
echo "Starting webpage tests..." echo "Starting webpage tests..."
echo "Base URL: $base_url" echo "Base URL: $base_url"
echo "Number of lines: $(wc -l < "$url_file")" echo "Number of lines: $(wc -l < "$url_file")"
echo "----------------------------------------" echo "Max parallel jobs: $max_parallel"
echo "----------------------------------------"
while IFS= read -r url || [ -n "$url" ]; do
# Skip empty lines and comments
[[ -z "$url" || "$url" =~ ^#.*$ ]] && continue
((total_count++))
if ! check_webpage "$url" "$base_url"; then
((failed_count++))
fi
done < "$url_file"
local end_time=$(date +%s) # Process each URL
local duration=$((end_time - start_time)) while IFS= read -r url || [ -n "$url" ]; do
# Skip empty lines and comments
[[ -z "$url" || "$url" =~ ^#.*$ ]] && continue
((total_count++))
((url_index++))
echo "----------------------------------------" # Run the check in background
echo "Test Summary:" test_url "$url" "$base_url" "$tmp_dir" "$url_index" &
echo "Total tests: $total_count"
echo "Failed tests: $failed_count" # Track the job
echo "Passed tests: $((total_count - failed_count))" ((active_jobs++))
echo "Duration: ${duration} seconds"
# If we've reached max_parallel, wait for a job to finish
if [ $active_jobs -ge $max_parallel ]; then
wait -n # Wait for any child process to exit
((active_jobs--))
fi
done < "$url_file"
return $failed_count # Wait for remaining jobs to finish
wait
# Print results in order and count failures
for i in $(seq 1 $url_index); do
if [ -f "${tmp_dir}/result_${i}.txt" ]; then
cat "${tmp_dir}/result_${i}.txt"
fi
if [ -f "${tmp_dir}/failed_${i}" ]; then
failed_count=$((failed_count + $(cat "${tmp_dir}/failed_${i}")))
fi
done
# Clean up
rm -rf "$tmp_dir"
local end_time=$(date +%s)
local duration=$((end_time - start_time))
echo "----------------------------------------"
echo "Test Summary:"
echo "Total tests: $total_count"
echo "Failed tests: $failed_count"
echo "Passed tests: $((total_count - failed_count))"
echo "Duration: ${duration} seconds"
return $failed_count
} }
# Print usage information # Print usage information
usage() { usage() {
echo "Usage: $0 [-f url_file] [-b base_url]" echo "Usage: $0 [-f url_file] [-b base_url] [-p max_parallel]"
echo "Options:" echo "Options:"
echo " -f url_file Path to file containing URLs to test (required)" echo " -f url_file Path to file containing URLs to test (required)"
echo " -b base_url Base URL to prepend to test URLs (default: http://localhost:8080)" echo " -b base_url Base URL to prepend to test URLs (default: http://localhost:8080)"
exit 1 echo " -p max_parallel Maximum number of parallel requests (default: 10)"
exit 1
} }
# Main execution # Main execution
main() { main() {
local url_file="" local url_file=""
local base_url="http://localhost:8080" local base_url="http://localhost:8080"
local max_parallel=10
# Parse command line options # Parse command line options
while getopts ":f:b:h" opt; do while getopts ":f:b:p:h" opt; do
case $opt in case $opt in
f) url_file="$OPTARG" ;; f) url_file="$OPTARG" ;;
b) base_url="$OPTARG" ;; b) base_url="$OPTARG" ;;
h) usage ;; p) max_parallel="$OPTARG" ;;
\?) echo "Invalid option -$OPTARG" >&2; usage ;; h) usage ;;
esac \?) echo "Invalid option -$OPTARG" >&2; usage ;;
done esac
done
# Check if URL file is provided # Check if URL file is provided
if [ -z "$url_file" ]; then if [ -z "$url_file" ]; then
echo "Error: URL file is required" echo "Error: URL file is required"
usage usage
fi fi
# Check if URL file exists # Check if URL file exists
if [ ! -f "$url_file" ]; then if [ ! -f "$url_file" ]; then
echo "Error: URL list file not found: $url_file" echo "Error: URL list file not found: $url_file"
exit 1 exit 1
fi fi
# Run tests using the URL list # Run tests using the URL list
if test_all_urls "$url_file" "$base_url"; then if test_all_urls "$url_file" "$base_url" "$max_parallel"; then
echo "All webpage tests passed!" echo "All webpage tests passed!"
exit 0 exit 0
else else
echo "Some webpage tests failed!" echo "Some webpage tests failed!"
exit 1 exit 1
fi fi
} }
# Run main if script is executed directly # Run main if script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@" main "$@"
fi fi