mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-12 17:52:13 +02:00
Merge branch 'Stirling-Tools:main' into main
This commit is contained in:
commit
ecec1e6350
@ -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
19
.devcontainer/git-init.sh
Normal 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
|
75
.devcontainer/init-setup.sh
Normal file
75
.devcontainer/init-setup.sh
Normal 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 "=================================================================="
|
6
.github/labeler-config.yml
vendored
6
.github/labeler-config.yml
vendored
@ -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
2
.gitignore
vendored
@ -26,7 +26,7 @@ clientWebUI/
|
||||
!cucumber/exampleFiles/
|
||||
!cucumber/exampleFiles/example_html.zip
|
||||
exampleYmlFiles/stirling/
|
||||
|
||||
/testing/file_snapshots
|
||||
# Gradle
|
||||
.gradle
|
||||
.lock
|
||||
|
@ -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 Nicht‑Root 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 Nicht‑Root Benutzer
|
||||
USER devuser
|
||||
|
@ -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
|
||||
|
10
README.md
10
README.md
@ -120,7 +120,7 @@ Stirling-PDF currently supports 39 languages!
|
||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
@ -128,7 +128,7 @@ Stirling-PDF currently supports 39 languages!
|
||||
| English (English) (en_GB) |  |
|
||||
| English (US) (en_US) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
@ -148,13 +148,13 @@ Stirling-PDF currently supports 39 languages!
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Slovenian (Slovenščina) (sl_SI) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
whoami
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
echo "Devcontainer started..."
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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];
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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 =
|
||||
|
@ -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);
|
||||
|
@ -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<>();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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)) {
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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"),
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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=біт
|
||||
|
32
src/test/java/stirling/software/SPDF/utils/FileInfoTest.java
Normal file
32
src/test/java/stirling/software/SPDF/utils/FileInfoTest.java
Normal 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());
|
||||
}
|
||||
}
|
175
testing/test.sh
175
testing/test.sh
@ -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
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user