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.
"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.
"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",
"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",
// Configure tool-specific properties.
@ -97,18 +115,17 @@
"Oracle.oracle-java", // Oracle Java extension with additional features for Java development
"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-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-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
"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.
"remoteUser": "devuser",
"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/push-docker.yml'
- any-glob-to-any-file: 'Dockerfile'
- any-glob-to-any-file: 'Dockerfile.*'
- any-glob-to-any-file: '!Dockerfile.dev'
- any-glob-to-any-file: 'Dockerfile.fat'
- any-glob-to-any-file: 'Dockerfile.ultra-lite'
- any-glob-to-any-file: 'exampleYmlFiles/*.yml'
- any-glob-to-any-file: 'scripts/download-security-jar.sh'
- any-glob-to-any-file: 'scripts/init.sh'
@ -67,7 +67,7 @@ Docker:
Devtools:
- changed-files:
- any-glob-to-any-file: '.devcontainer/**/*'
- any-glob-to-any-file: '!Dockerfile.dev'
- any-glob-to-any-file: 'Dockerfile.dev'
Test:
- changed-files:

2
.gitignore vendored
View File

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

View File

@ -1,53 +1,54 @@
# dockerfile.dev
# 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
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)
RUN apt-get update && apt-get install -y \
wget \
ca-certificates \
tzdata \
tini \
bash \
curl \
sudo \
libreoffice \
poppler-utils \
qpdf \
# settings.yml | tessdataDir: /usr/share/tesseract-ocr/5/tessdata
tesseract-ocr \
tesseract-ocr-eng \
fonts-dejavu \
fonts-noto \
python3 \
python3-pip \
fonts-terminus fonts-dejavu fonts-font-awesome fonts-noto fonts-noto-core fonts-noto-cjk fonts-noto-extra fonts-liberation fonts-linuxlibertine \
python3-uno \
python3-venv \
# ss -tln
iproute2 \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Setze die Environment Variable für setuptools
ENV SETUPTOOLS_USE_DISTUTILS=local
# 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 \
&& pip install --upgrade setuptools \
&& 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
ENV PATH="/opt/venv/bin:$PATH"
# Erstelle notwendige Verzeichnisse und lege einen NichtRoot Benutzer an
RUN mkdir -p /home/devuser/{configs,logs,customFiles,pipeline/watchedFolders,pipeline/finishedFolders} \
&& adduser --disabled-password --gecos '' devuser \
&& chown -R devuser:devuser /home/devuser
COPY . /workspace
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
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)
WORKDIR /workspace
RUN chmod +x /workspace/.devcontainer/git-init.sh
RUN sudo chmod +x /workspace/.devcontainer/init-setup.sh
# Wechsel zum NichtRoot Benutzer
USER devuser

View File

@ -1,5 +1,11 @@
# 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
WORKDIR /app
@ -10,7 +16,7 @@ COPY . .
# Build the application with DOCKER_ENABLE_SECURITY=false
RUN DOCKER_ENABLE_SECURITY=true \
STIRLING_PDF_DESKTOP_UI=false \
./gradlew clean build
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
# Main stage
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) |
| Basque (Euskara) (eu_ES) | ![51%](https://geps.dev/progress/51) |
| 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) |
| Czech (Česky) (cs_CZ) | ![96%](https://geps.dev/progress/96) |
| 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 (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| 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) |
| Hindi (हिंदी) (hi_IN) | ![97%](https://geps.dev/progress/97) |
| 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) |
| Slovakian (Slovensky) (sk_SK) | ![73%](https://geps.dev/progress/73) |
| 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) |
| Thai (ไทย) (th_TH) | ![84%](https://geps.dev/progress/84) |
| 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) |
| 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) |

View File

@ -25,7 +25,7 @@ ext {
}
group = "stirling.software"
version = "0.44.0"
version = "0.44.1"
java {
// 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",
description = "Returns total number of pages in PDF. Input:PDF Output:JSON Type:SISO")
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());
}
}
@ -47,7 +47,7 @@ public class AnalysisController {
summary = "Get basic PDF information",
description = "Returns page count, version, file size. Input:PDF Output:JSON Type:SISO")
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<>();
info.put("pageCount", document.getNumberOfPages());
info.put("pdfVersion", document.getVersion());
@ -62,7 +62,7 @@ public class AnalysisController {
description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO")
public Map<String, String> getDocumentProperties(@ModelAttribute PDFFile file)
throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput().getBytes())) {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
PDDocumentInformation info = document.getDocumentInformation();
Map<String, String> properties = new HashMap<>();
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")
public List<Map<String, Float>> getPageDimensions(@ModelAttribute PDFFile file)
throws IOException {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput().getBytes())) {
try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {
List<Map<String, Float>> dimensions = new ArrayList<>();
PDPageTree pages = document.getPages();
@ -103,7 +103,7 @@ public class AnalysisController {
description =
"Returns count and details of form fields. Input:PDF Output:JSON Type:SISO")
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<>();
PDAcroForm form = document.getDocumentCatalog().getAcroForm();
@ -125,7 +125,7 @@ public class AnalysisController {
summary = "Get annotation information",
description = "Returns count and types of annotations. Input:PDF Output:JSON Type:SISO")
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<>();
int totalAnnotations = 0;
Map<String, Integer> annotationTypes = new HashMap<>();
@ -150,7 +150,7 @@ public class AnalysisController {
description =
"Returns list of fonts used in the document. Input:PDF Output:JSON Type:SISO")
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<>();
Set<String> fontNames = new HashSet<>();
@ -172,7 +172,7 @@ public class AnalysisController {
description =
"Returns encryption and permission details. Input:PDF Output:JSON Type:SISO")
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<>();
PDEncryption encryption = document.getEncryption();

View File

@ -42,7 +42,7 @@ public class CropController {
description =
"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 {
PDDocument sourceDocument = pdfDocumentFactory.load(form.getFileInput().getBytes());
PDDocument sourceDocument = pdfDocumentFactory.load(form);
PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);

View File

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

View File

@ -63,7 +63,7 @@ public class MultiPageLayoutController {
: (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 =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
PDPage newPage = new PDPage(PDRectangle.A4);

View File

@ -250,7 +250,7 @@ public class RearrangePagesPDFController {
String sortType = request.getCustomMode();
try {
// 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
String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -59,7 +59,7 @@ public class ExtractCSVController {
String baseName = getBaseName(form.getFileInput().getOriginalFilename());
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);
SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
CSVFormat format =

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -67,7 +67,7 @@ public class ExtractImagesController {
MultipartFile file = request.getFileInput();
String format = request.getFormat();
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
boolean useMultithreading = shouldUseMultithreading(file, document);

View File

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

View File

@ -84,7 +84,7 @@ public class MetadataController {
allRequestParams = new java.util.HashMap<String, String>();
}
// 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
PDDocumentInformation info = document.getDocumentInformation();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,7 @@
package stirling.software.SPDF.model.api;
import java.io.IOException;
import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import io.swagger.v3.oas.annotations.Hidden;
@ -32,18 +30,6 @@ public class PDFWithPageNums extends PDFFile {
requiredMode = RequiredMode.NOT_REQUIRED)
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
public List<Integer> getPageNumbersList(PDDocument doc, boolean oneBased) {
int pageCount = 0;

View File

@ -10,9 +10,9 @@ import java.nio.file.StandardCopyOption;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.examples.util.DeletingRandomAccessFile;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
import org.apache.pdfbox.io.RandomAccessStreamCache.StreamCacheCreateFunction;
import org.apache.pdfbox.io.ScratchFile;
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
Path tempFile = createTempFile("pdf-stream-");
try {
Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);
return loadAdaptively(tempFile.toFile(), Files.size(tempFile));
} catch (IOException e) {
cleanupFile(tempFile);
throw e;
}
Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);
return loadAdaptively(tempFile.toFile(), Files.size(tempFile));
}
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 freeMemory = Runtime.getRuntime().freeMemory();
long totalMemory = Runtime.getRuntime().totalMemory();
@ -129,32 +142,38 @@ public class CustomPDDocumentFactory {
usedMemory / (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
// In loadAdaptively method, replace current caching strategy decision with:
if (freeMemoryPercent < MIN_FREE_MEMORY_PERCENTAGE
|| actualFreeMemory < MIN_FREE_MEMORY_BYTES) {
log.info(
"Low memory detected ({}%), forcing file-based cache",
String.format("%.2f", freeMemoryPercent));
cacheFunction = createScratchFileCacheFunction(MemoryUsageSetting.setupTempFileOnly());
return createScratchFileCacheFunction(MemoryUsageSetting.setupTempFileOnly());
} else if (contentSize < SMALL_FILE_THRESHOLD) {
log.info("Using memory-only cache for small document ({}KB)", contentSize / 1024);
cacheFunction = IOUtils.createMemoryOnlyStreamCache();
return IOUtils.createMemoryOnlyStreamCache();
} else if (contentSize < LARGE_FILE_THRESHOLD) {
// For medium files (10-50MB), use a mixed approach
log.info(
"Using mixed memory/file cache for medium document ({}MB)",
contentSize / (1024 * 1024));
cacheFunction =
createScratchFileCacheFunction(MemoryUsageSetting.setupMixed(LARGE_FILE_USAGE));
return createScratchFileCacheFunction(MemoryUsageSetting.setupMixed(LARGE_FILE_USAGE));
} else {
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;
if (source instanceof File file) {
document = loadFromFile(file, contentSize, cacheFunction);
@ -168,6 +187,50 @@ public class CustomPDDocumentFactory {
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) {
return () -> {
try {
@ -185,11 +248,7 @@ public class CustomPDDocumentFactory {
private PDDocument loadFromFile(File file, long size, StreamCacheCreateFunction cache)
throws IOException {
if (size >= EXTREMELY_LARGE_THRESHOLD) {
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);
return Loader.loadPDF(new DeletingRandomAccessFile(file), "", null, null, cache);
}
private PDDocument loadFromBytes(byte[] bytes, long size, StreamCacheCreateFunction cache)
@ -197,12 +256,9 @@ public class CustomPDDocumentFactory {
if (size >= SMALL_FILE_THRESHOLD) {
log.info("Writing large byte array to temp file");
Path tempFile = createTempFile("pdf-bytes-");
try {
Files.write(tempFile, bytes);
return Loader.loadPDF(tempFile.toFile(), "", null, null, cache);
} finally {
cleanupFile(tempFile);
}
Files.write(tempFile, bytes);
return loadFromFile(tempFile.toFile(), size, cache);
}
return Loader.loadPDF(bytes, "", null, null, cache);
}
@ -225,12 +281,9 @@ public class CustomPDDocumentFactory {
}
} else {
Path tempFile = createTempFile("pdf-save-");
try {
document.save(tempFile.toFile());
return Files.readAllBytes(tempFile);
} finally {
cleanupFile(tempFile);
}
document.save(tempFile.toFile());
return Files.readAllBytes(tempFile);
}
}
@ -258,17 +311,6 @@ public class CustomPDDocumentFactory {
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 */
public byte[] createNewBytesBasedOnOldDocument(byte[] oldDocument) throws IOException {
try (PDDocument document = load(oldDocument)) {
@ -339,20 +381,11 @@ public class CustomPDDocumentFactory {
/** Load from a MultipartFile */
public PDDocument load(MultipartFile pdfFile) throws IOException {
return load(pdfFile.getBytes());
return load(pdfFile.getInputStream());
}
/** Load with password from MultipartFile */
public PDDocument load(MultipartFile fileInput, String password) throws IOException {
return load(fileInput.getBytes(), 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;
return load(fileInput.getInputStream(), password);
}
}

View File

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

View File

@ -32,8 +32,15 @@ public class GeneralUtils {
public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
File tempFile = Files.createTempFile("temp", null).toFile();
try (FileOutputStream os = new FileOutputStream(tempFile)) {
os.write(multipartFile.getBytes());
try (InputStream inputStream = multipartFile.getInputStream();
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;
}

View File

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

View File

@ -2,122 +2,173 @@
# Function to check a single webpage
check_webpage() {
local url=$(echo "$1" | tr -d '\r') # Remove carriage returns
local base_url=$(echo "$2" | tr -d '\r')
local full_url="${base_url}${url}"
local timeout=10
echo -n "Testing $full_url ... "
# 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
local url=$(echo "$1" | tr -d '\r') # Remove carriage returns
local base_url=$(echo "$2" | tr -d '\r')
local full_url="${base_url}${url}"
local timeout=10
local result_file="$3"
# Split response into body and status code
HTTP_STATUS=$(echo "$response" | tail -n1)
BODY=$(echo "$response" | sed '$d')
# 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" >> "$result_file"
return 1
fi
# Check HTTP status
if [ "$HTTP_STATUS" != "200" ]; then
echo "FAILED - HTTP Status: $HTTP_STATUS"
return 1
fi
# Split response into body and status code
HTTP_STATUS=$(echo "$response" | tail -n1)
BODY=$(echo "$response" | sed '$d')
# Check if response contains HTML
if ! printf '%s' "$BODY" | grep -q "<!DOCTYPE html>\|<html"; then
echo "FAILED - Response is not HTML"
return 1
fi
# Check HTTP status
if [ "$HTTP_STATUS" != "200" ]; then
echo "FAILED - HTTP Status: $HTTP_STATUS - $full_url" >> "$result_file"
return 1
fi
echo "OK"
return 0
# Check if response contains HTML
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() {
local url_file=$1
local base_url=${2:-"http://localhost:8080"}
local failed_count=0
local total_count=0
local start_time=$(date +%s)
local url_file="$1"
local base_url="${2:-"http://localhost:8080"}"
local max_parallel="${3:-10}" # Default to 10 parallel processes
local failed_count=0
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 "Base URL: $base_url"
echo "Number of lines: $(wc -l < "$url_file")"
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"
echo "Starting webpage tests..."
echo "Base URL: $base_url"
echo "Number of lines: $(wc -l < "$url_file")"
echo "Max parallel jobs: $max_parallel"
echo "----------------------------------------"
local end_time=$(date +%s)
local duration=$((end_time - start_time))
# Process each URL
while IFS= read -r url || [ -n "$url" ]; do
# Skip empty lines and comments
[[ -z "$url" || "$url" =~ ^#.*$ ]] && continue
((total_count++))
((url_index++))
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"
# Run the check in background
test_url "$url" "$base_url" "$tmp_dir" "$url_index" &
# Track the job
((active_jobs++))
# 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
usage() {
echo "Usage: $0 [-f url_file] [-b base_url]"
echo "Options:"
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)"
exit 1
echo "Usage: $0 [-f url_file] [-b base_url] [-p max_parallel]"
echo "Options:"
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 " -p max_parallel Maximum number of parallel requests (default: 10)"
exit 1
}
# Main execution
main() {
local url_file=""
local base_url="http://localhost:8080"
local url_file=""
local base_url="http://localhost:8080"
local max_parallel=10
# Parse command line options
while getopts ":f:b:h" opt; do
case $opt in
f) url_file="$OPTARG" ;;
b) base_url="$OPTARG" ;;
h) usage ;;
\?) echo "Invalid option -$OPTARG" >&2; usage ;;
esac
done
# Parse command line options
while getopts ":f:b:p:h" opt; do
case $opt in
f) url_file="$OPTARG" ;;
b) base_url="$OPTARG" ;;
p) max_parallel="$OPTARG" ;;
h) usage ;;
\?) echo "Invalid option -$OPTARG" >&2; usage ;;
esac
done
# Check if URL file is provided
if [ -z "$url_file" ]; then
echo "Error: URL file is required"
usage
fi
# Check if URL file is provided
if [ -z "$url_file" ]; then
echo "Error: URL file is required"
usage
fi
# Check if URL file exists
if [ ! -f "$url_file" ]; then
echo "Error: URL list file not found: $url_file"
exit 1
fi
# Run tests using the URL list
if test_all_urls "$url_file" "$base_url"; then
echo "All webpage tests passed!"
exit 0
else
echo "Some webpage tests failed!"
exit 1
fi
# Check if URL file exists
if [ ! -f "$url_file" ]; then
echo "Error: URL list file not found: $url_file"
exit 1
fi
# Run tests using the URL list
if test_all_urls "$url_file" "$base_url" "$max_parallel"; then
echo "All webpage tests passed!"
exit 0
else
echo "Some webpage tests failed!"
exit 1
fi
}
# Run main if script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
main "$@"
fi