Compare commits

..

7 Commits

Author SHA1 Message Date
Anthony Stirling
205b849f25 Create launcher.sh 2024-12-09 12:23:30 +00:00
Anthony Stirling
16adfe1f59 Update mac-unix-artifact-creation.yml 2024-12-09 12:21:05 +00:00
Anthony Stirling
397352c9f1 Update mac-unix-artifact-creation.yml 2024-12-09 11:12:32 +00:00
Anthony Stirling
97675c2320 Update mac-unix-artifact-creation.yml 2024-12-09 11:07:50 +00:00
Anthony Stirling
f4cfcb2ac7 Create mac-unix-artifact-creation.yml 2024-12-09 10:27:53 +00:00
Anthony Stirling
56729648e9 Create create-unix-launcher.sh 2024-12-09 10:25:52 +00:00
Anthony Stirling
2317642fdc Create create-mac-launcher.sh 2024-12-09 10:25:39 +00:00
118 changed files with 2575 additions and 4261 deletions

View File

@@ -1,5 +1,5 @@
blank_issues_enabled: true blank_issues_enabled: true
contact_links: contact_links:
- name: 💬 Discord Server - name: 💬 Discord Server
url: https://discord.gg/HYmhKj45pU url: https://discord.gg/Cn8pWhQRxZ
about: You can join our Discord server for real time discussion and support about: You can join our Discord server for real time discussion and support

View File

@@ -76,7 +76,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: "3.12" python-version: "3.7"
- name: Pip requirements - name: Pip requirements
run: | run: |

View File

@@ -0,0 +1,67 @@
name: Create Application Bundles
on:
push:
branches:
- 'mac'
workflow_dispatch:
jobs:
create-unix-bundle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew bootJar
- name: Create tar.gz Bundle
run: |
mkdir -p Stirling-PDF-unix
cp build/libs/Stirling-PDF-*.jar Stirling-PDF-unix/Stirling-PDF.jar
cp scripts/launcher.sh Stirling-PDF-unix/Stirling-PDF
cp src/main/resources/static/favicon.ico Stirling-PDF-unix/icon.png
chmod +x Stirling-PDF-unix/Stirling-PDF
tar -czf Stirling-PDF-unix.tar.gz Stirling-PDF-unix/
- name: Upload Unix Bundle
uses: actions/upload-artifact@v4
with:
name: unix-bundle
path: Stirling-PDF-unix.tar.gz
create-mac-bundle:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew bootJar
- name: Create Mac App Bundle
run: |
cp build/libs/Stirling-PDF-*.jar build/libs/Stirling-PDF.jar
chmod +x scripts/create-mac-launcher.sh
./scripts/create-mac-launcher.sh
- name: Create DMG
run: |
hdiutil create -volname "Stirling-PDF" -srcfolder "Stirling-PDF.app" -ov -format UDZO "Stirling-PDF-mac.dmg"
- name: Upload Mac Bundle
uses: actions/upload-artifact@v4
with:
name: mac-bundle
path: Stirling-PDF-mac.dmg

View File

@@ -1,96 +0,0 @@
name: Test Installers Build
on:
workflow_dispatch:
release:
types: [created]
permissions:
contents: write
packages: write
jobs:
build-installers:
strategy:
matrix:
include:
- os: windows-latest
platform: win
ext: exe
#- os: macos-latest
# platform: mac
# ext: dmg
#- os: ubuntu-latest
# platform: linux
# ext: deb
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: "21"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@v4
with:
gradle-version: 8.7
# Install Windows dependencies
- name: Install WiX Toolset
if: matrix.os == 'windows-latest'
run: |
curl -L -o wix.exe https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe
.\wix.exe /install /quiet
# Install Linux dependencies
- name: Install Linux Dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y fakeroot rpm
# Get version number
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
shell: bash
- name: Get version number mac
id: versionNumberMac
run: echo "versionNumberMac=$(./gradlew printMacVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
shell: bash
# Build installer
- name: Build Installer
run: ./gradlew build jpackage -x test --info
env:
DOCKER_ENABLE_SECURITY: false
STIRLING_PDF_DESKTOP_UI: true
# Rename and collect artifacts based on OS
- name: Prepare artifacts
id: prepare
shell: bash
run: |
if [ "${{ matrix.os }}" = "windows-latest" ]; then
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.exe" "Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}"
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumberMac.outputs.versionNumberMac }}.dmg" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
else
mv "build/jpackage/stirling-pdf_${{ steps.versionNumber.outputs.versionNumber }}-1_amd64.deb" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
fi
# Upload installer as artifact for testing
- name: Upload Installer Artifact
uses: actions/upload-artifact@v4
with:
name: Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
path: Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
retention-days: 1
if-no-files-found: error
- name: Upload binaries to release
uses: softprops/action-gh-release@v2
with:
files: ./Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}

View File

@@ -35,28 +35,27 @@ jobs:
run: ./gradlew clean createExe run: ./gradlew clean createExe
env: env:
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }} DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
STIRLING_PDF_DESKTOP_UI: false
- name: Get version number - name: Get version number
id: versionNumber id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Rename binarie - name: Rename binarie
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe if: matrix.file_suffix != ''
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
- name: Upload Assets binarie - name: Upload Assets binarie
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
path: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe path: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
name: Stirling-PDF-Server${{ matrix.file_suffix }}.exe name: Stirling-PDF${{ matrix.file_suffix }}.exe
overwrite: true overwrite: true
retention-days: 1 retention-days: 1
if-no-files-found: error if-no-files-found: error
- name: Upload binaries to release - name: Upload binaries to release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe files: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
- name: Rename jar binaries - name: Rename jar binaries
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
@@ -74,43 +73,3 @@ jobs:
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar files: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
push-ui:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@v4
with:
gradle-version: 8.7
- name: Generate exe
run: ./gradlew clean createExe
env:
DOCKER_ENABLE_SECURITY: false
STIRLING_PDF_DESKTOP_UI: true
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Upload Assets binarie
uses: actions/upload-artifact@v4
with:
path: ./build/launch4j/Stirling-PDF.exe
name: Stirling-PDF.exe
overwrite: true
retention-days: 1
if-no-files-found: error
- name: Upload binaries to release
uses: softprops/action-gh-release@v2
with:
files: ./build/launch4j/Stirling-PDF.exe

1
.gitignore vendored
View File

@@ -161,4 +161,3 @@ out/
.pytest_cache .pytest_cache
.ipynb_checkpoints .ipynb_checkpoints
**/jcef-bundle/

View File

@@ -1,5 +1,5 @@
# use alpine # use alpine
FROM alpine:3.21.0 FROM alpine:3.20.3
ARG VERSION_TAG ARG VERSION_TAG

View File

@@ -2,7 +2,7 @@
<h1 align="center">Stirling-PDF</h1> <h1 align="center">Stirling-PDF</h1>
[![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf) [![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf)
[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/HYmhKj45pU) [![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/Cn8pWhQRxZ)
[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/) [![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/)
[![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf) [![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf)
@@ -191,44 +191,44 @@ Stirling-PDF currently supports 38 languages!
| Language | Progress | | Language | Progress |
| -------------------------------------------- | -------------------------------------- | | -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![94%](https://geps.dev/progress/94) | | Arabic (العربية) (ar_AR) | ![95%](https://geps.dev/progress/95) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![93%](https://geps.dev/progress/93) | | Azerbaijani (Azərbaycan Dili) (az_AZ) | ![93%](https://geps.dev/progress/93) |
| Basque (Euskara) (eu_ES) | ![53%](https://geps.dev/progress/53) | | Basque (Euskara) (eu_ES) | ![54%](https://geps.dev/progress/54) |
| Bulgarian (Български) (bg_BG) | ![90%](https://geps.dev/progress/90) | | Bulgarian (Български) (bg_BG) | ![90%](https://geps.dev/progress/90) |
| Catalan (Català) (ca_CA) | ![84%](https://geps.dev/progress/84) | | Catalan (Català) (ca_CA) | ![85%](https://geps.dev/progress/85) |
| Croatian (Hrvatski) (hr_HR) | ![91%](https://geps.dev/progress/91) | | Croatian (Hrvatski) (hr_HR) | ![92%](https://geps.dev/progress/92) |
| Czech (Česky) (cs_CZ) | ![91%](https://geps.dev/progress/91) | | Czech (Česky) (cs_CZ) | ![91%](https://geps.dev/progress/91) |
| Danish (Dansk) (da_DK) | ![90%](https://geps.dev/progress/90) | | Danish (Dansk) (da_DK) | ![90%](https://geps.dev/progress/90) |
| Dutch (Nederlands) (nl_NL) | ![89%](https://geps.dev/progress/89) | | Dutch (Nederlands) (nl_NL) | ![90%](https://geps.dev/progress/90) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![92%](https://geps.dev/progress/92) | | French (Français) (fr_FR) | ![93%](https://geps.dev/progress/93) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) | | German (Deutsch) (de_DE) | ![100%](https://geps.dev/progress/100) |
| Greek (Ελληνικά) (el_GR) | ![90%](https://geps.dev/progress/90) | | Greek (Ελληνικά) (el_GR) | ![91%](https://geps.dev/progress/91) |
| Hindi (हिंदी) (hi_IN) | ![88%](https://geps.dev/progress/88) | | Hindi (हिंदी) (hi_IN) | ![89%](https://geps.dev/progress/89) |
| Hungarian (Magyar) (hu_HU) | ![91%](https://geps.dev/progress/91) | | Hungarian (Magyar) (hu_HU) | ![92%](https://geps.dev/progress/92) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![91%](https://geps.dev/progress/91) | | Indonesian (Bahasa Indonesia) (id_ID) | ![91%](https://geps.dev/progress/91) |
| Irish (Gaeilge) (ga_IE) | ![83%](https://geps.dev/progress/83) | | Irish (Gaeilge) (ga_IE) | ![83%](https://geps.dev/progress/83) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | | Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Japanese (日本語) (ja_JP) | ![93%](https://geps.dev/progress/93) | | Japanese (日本語) (ja_JP) | ![81%](https://geps.dev/progress/81) |
| Korean (한국어) (ko_KR) | ![89%](https://geps.dev/progress/89) | | Korean (한국어) (ko_KR) | ![90%](https://geps.dev/progress/90) |
| Norwegian (Norsk) (no_NB) | ![82%](https://geps.dev/progress/82) | | Norwegian (Norsk) (no_NB) | ![83%](https://geps.dev/progress/83) |
| Persian (فارسی) (fa_IR) | ![99%](https://geps.dev/progress/99) | | Persian (فارسی) (fa_IR) | ![100%](https://geps.dev/progress/100) |
| Polish (Polski) (pl_PL) | ![90%](https://geps.dev/progress/90) | | Polish (Polski) (pl_PL) | ![91%](https://geps.dev/progress/91) |
| Portuguese (Português) (pt_PT) | ![91%](https://geps.dev/progress/91) | | Portuguese (Português) (pt_PT) | ![91%](https://geps.dev/progress/91) |
| Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) | | Portuguese Brazilian (Português) (pt_BR) | ![92%](https://geps.dev/progress/92) |
| Romanian (Română) (ro_RO) | ![85%](https://geps.dev/progress/85) | | Romanian (Română) (ro_RO) | ![85%](https://geps.dev/progress/85) |
| Russian (Русский) (ru_RU) | ![90%](https://geps.dev/progress/90) | | Russian (Русский) (ru_RU) | ![91%](https://geps.dev/progress/91) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![67%](https://geps.dev/progress/67) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![67%](https://geps.dev/progress/67) |
| Simplified Chinese (简体中文) (zh_CN) | ![93%](https://geps.dev/progress/93) | | Simplified Chinese (简体中文) (zh_CN) | ![86%](https://geps.dev/progress/86) |
| Slovakian (Slovensky) (sk_SK) | ![78%](https://geps.dev/progress/78) | | Slovakian (Slovensky) (sk_SK) | ![78%](https://geps.dev/progress/78) |
| Spanish (Español) (es_ES) | ![91%](https://geps.dev/progress/91) | | Spanish (Español) (es_ES) | ![92%](https://geps.dev/progress/92) |
| Swedish (Svenska) (sv_SE) | ![90%](https://geps.dev/progress/90) | | Swedish (Svenska) (sv_SE) | ![91%](https://geps.dev/progress/91) |
| Thai (ไทย) (th_TH) | ![90%](https://geps.dev/progress/90) | | Thai (ไทย) (th_TH) | ![91%](https://geps.dev/progress/91) |
| Traditional Chinese (繁體中文) (zh_TW) | ![91%](https://geps.dev/progress/91) | | Traditional Chinese (繁體中文) (zh_TW) | ![92%](https://geps.dev/progress/92) |
| Turkish (Türkçe) (tr_TR) | ![86%](https://geps.dev/progress/86) | | Turkish (Türkçe) (tr_TR) | ![87%](https://geps.dev/progress/87) |
| Ukrainian (Українська) (uk_UA) | ![76%](https://geps.dev/progress/76) | | Ukrainian (Українська) (uk_UA) | ![76%](https://geps.dev/progress/76) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![83%](https://geps.dev/progress/83) | | Vietnamese (Tiếng Việt) (vi_VN) | ![84%](https://geps.dev/progress/84) |
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.) ## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
@@ -405,7 +405,7 @@ To access your account settings, go to Account Settings in the settings cog menu
To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future. To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future.
For API usage, you must provide a header with `X-API-KEY` and the associated API key for that user. For API usage, you must provide a header with `X-API-Key` and the associated API key for that user.
## FAQ ## FAQ

View File

@@ -8,7 +8,6 @@ plugins {
id "com.diffplug.spotless" version "6.25.0" id "com.diffplug.spotless" version "6.25.0"
id "com.github.jk1.dependency-license-report" version "2.9" id "com.github.jk1.dependency-license-report" version "2.9"
//id "nebula.lint" version "19.0.3" //id "nebula.lint" version "19.0.3"
id("org.panteleyev.jpackageplugin") version "1.6.0"
} }
@@ -23,11 +22,11 @@ ext {
lombokVersion = "1.18.36" lombokVersion = "1.18.36"
bouncycastleVersion = "1.79" bouncycastleVersion = "1.79"
springSecuritySamlVersion = "6.4.1" springSecuritySamlVersion = "6.4.1"
openSamlVersion = "4.3.2" openSamlVersion = "4.3.2"
} }
group = "stirling.software" group = "stirling.software"
version = "0.36.3" version = "0.36.0"
java { java {
@@ -42,9 +41,6 @@ repositories {
maven { maven {
url 'https://build.shibboleth.net/maven/releases' url 'https://build.shibboleth.net/maven/releases'
} }
maven { url "https://build.shibboleth.net/maven/releases" }
maven { url "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
} }
licenseReport { licenseReport {
@@ -68,12 +64,6 @@ sourceSets {
exclude "stirling/software/SPDF/model/User.java" exclude "stirling/software/SPDF/model/User.java"
exclude "stirling/software/SPDF/repository/**" exclude "stirling/software/SPDF/repository/**"
} }
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
exclude "stirling/software/SPDF/UI/impl/**"
}
} }
} }
} }
@@ -84,153 +74,16 @@ openApi {
outputFileName = "SwaggerDoc.json" outputFileName = "SwaggerDoc.json"
} }
//0.11.5 to 2024.11.5
def getMacVersion(String version) {
def currentYear = java.time.Year.now().getValue()
def versionParts = version.split("\\.", 2)
return "${currentYear}.${versionParts.length > 1 ? versionParts[1] : versionParts[0]}"
}
jpackage {
input = "build/libs"
appName = "Stirling-PDF"
appVersion = project.version
vendor = "Stirling-Software"
appDescription = "Stirling PDF - Your Local PDF Editor"
mainJar = "Stirling-PDF-${project.version}.jar"
mainClass = "org.springframework.boot.loader.launch.JarLauncher"
icon = "src/main/resources/static/favicon.ico"
// JVM Options
javaOptions = [
"-DBROWSER_OPEN=true",
"-DSTIRLING_PDF_DESKTOP_UI=true",
"-Djava.awt.headless=false",
"-Dapple.awt.UIElement=true",
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
"--add-opens", "java.desktop/java.awt.event=ALL-UNNAMED",
"--add-opens", "java.desktop/sun.awt=ALL-UNNAMED"
]
verbose = true
destination = "${projectDir}/build/jpackage"
// Windows-specific configuration
windows {
launcherAsService = false
appVersion = project.version
winConsole = false
winDirChooser = true
winMenu = true
winShortcut = true
winPerUserInstall = true
winMenuGroup = "Stirling Software"
winUpgradeUuid = "2a43ed0c-b8c2-40cf-89e1-751129b87641" // Unique identifier for updates
winHelpUrl = "https://github.com/Stirling-Tools/Stirling-PDF"
winUpdateUrl = "https://github.com/Stirling-Tools/Stirling-PDF/releases"
type = "exe"
installDir = "C:/Program Files/Stirling-PDF"
}
// macOS-specific configuration
mac {
appVersion = getMacVersion(project.version.toString())
icon = "src/main/resources/static/favicon.icns"
type = "dmg"
macPackageIdentifier = "com.stirling.software.pdf"
macPackageName = "Stirling-PDF"
macAppCategory = "public.app-category.productivity"
macSign = false // Enable signing
macAppStore = false // Not targeting App Store initially
//installDir = "Applications"
// Add license and other documentation to DMG
/*macDmgContent = [
"README.md",
"LICENSE",
"CHANGELOG.md"
]*/
// Enable Mac-specific entitlements
//macEntitlements = "entitlements.plist" // You'll need to create this file
}
// Linux-specific configuration
linux {
appVersion = project.version
icon = "src/main/resources/static/favicon.png"
type = "deb" // Can also use "rpm" for Red Hat-based systems
// Debian package configuration
//linuxPackageName = "stirlingpdf"
linuxDebMaintainer = "support@stirlingpdf.com"
linuxMenuGroup = "Office;PDF;Productivity"
linuxAppCategory = "Office"
linuxAppRelease = "1"
linuxPackageDeps = true
installDir = "/opt/Stirling-PDF"
// RPM-specific settings
//linuxRpmLicenseType = "MIT"
}
// Common additional options
//jLinkOptions = [
// "--strip-debug",
// "--compress=2",
// "--no-header-files",
// "--no-man-pages"
//]
// Add any additional modules required
/*addModules = [
"java.base",
"java.desktop",
"java.logging",
"java.sql",
"java.xml",
"jdk.crypto.ec"
]*/
// Add copyright and license information
copyright = "Copyright © 2024 Stirling Software"
licenseFile = "LICENSE"
}
launch4j { launch4j {
icon = "${projectDir}/src/main/resources/static/favicon.ico" icon = "${projectDir}/src/main/resources/static/favicon.ico"
outfile="Stirling-PDF.exe" outfile="Stirling-PDF.exe"
headerType="console"
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
headerType = "gui"
} else {
headerType = "console"
}
jarTask = tasks.bootJar jarTask = tasks.bootJar
errTitle="Encountered error, Do you have Java 21?" errTitle="Encountered error, Do you have Java 21?"
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe" downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
variables=["BROWSER_OPEN=true"]
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
variables=["BROWSER_OPEN=true", "STIRLING_PDF_DESKTOP_UI=true"]
} else {
variables=["BROWSER_OPEN=true"]
}
jreMinVersion="17" jreMinVersion="17"
mutexName="Stirling-PDF" mutexName="Stirling-PDF"
@@ -270,13 +123,6 @@ configurations.all {
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
} }
dependencies { dependencies {
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
implementation "me.friwi:jcefmaven:127.3.1"
implementation "org.openjfx:javafx-controls:21"
implementation "org.openjfx:javafx-swing:21"
}
//security updates //security updates
implementation "org.springframework:spring-webmvc:6.2.0" implementation "org.springframework:spring-webmvc:6.2.0"
@@ -296,12 +142,12 @@ dependencies {
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") { if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE" implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE"
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
implementation "org.springframework.session:spring-session-core:$springBootVersion" implementation "org.springframework.session:spring-session-core:$springBootVersion"
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5' implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
// Don't upgrade h2database // Don't upgrade h2database
runtimeOnly "com.h2database:h2:2.3.232" runtimeOnly "com.h2database:h2:2.3.232"
@@ -425,14 +271,7 @@ jar {
tasks.named("test") { tasks.named("test") {
useJUnitPlatform() useJUnitPlatform()
} }
task printVersion {
doLast {
println project.version
}
}
task printMacVersion { task printVersion {
doLast { println project.version
println getMacVersion(project.version.toString())
}
} }

View File

@@ -15,10 +15,6 @@ import shutil
import re import re
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
API_HEADERS = {
'X-API-KEY': '123456789'
}
######### #########
# GIVEN # # GIVEN #
######### #########
@@ -231,7 +227,7 @@ def save_generated_pdf(context, filename):
def step_send_get_request(context, endpoint): def step_send_get_request(context, endpoint):
base_url = "http://localhost:8080" base_url = "http://localhost:8080"
full_url = f"{base_url}{endpoint}" full_url = f"{base_url}{endpoint}"
response = requests.get(full_url, headers=API_HEADERS) response = requests.get(full_url)
context.response = response context.response = response
@when('I send a GET request to "{endpoint}" with parameters') @when('I send a GET request to "{endpoint}" with parameters')
@@ -239,7 +235,7 @@ def step_send_get_request_with_params(context, endpoint):
base_url = "http://localhost:8080" base_url = "http://localhost:8080"
params = {row['parameter']: row['value'] for row in context.table} params = {row['parameter']: row['value'] for row in context.table}
full_url = f"{base_url}{endpoint}" full_url = f"{base_url}{endpoint}"
response = requests.get(full_url, params=params, headers=API_HEADERS) response = requests.get(full_url, params=params)
context.response = response context.response = response
@when('I send the API request to the endpoint "{endpoint}"') @when('I send the API request to the endpoint "{endpoint}"')
@@ -260,7 +256,7 @@ def step_send_api_request(context, endpoint):
print(f"form_data {file.name} with {mime_type}") print(f"form_data {file.name} with {mime_type}")
form_data.append((key, (file.name, file, mime_type))) form_data.append((key, (file.name, file, mime_type)))
response = requests.post(url, files=form_data, headers=API_HEADERS) response = requests.post(url, files=form_data)
context.response = response context.response = response
######## ########

View File

@@ -1,34 +0,0 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Security-Fat
image: stirlingtools/stirling-pdf:latest-fat
deploy:
resources:
limits:
memory: 4G
healthcheck:
test: ["CMD-SHELL", "curl -f -H 'X-API-KEY: 123456789' http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
interval: 5s
timeout: 10s
retries: 16
ports:
- 8080:8080
volumes:
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true"
PUID: 1002
PGID: 1002
UMASK: "022"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "true"
SECURITY_CUSTOMGLOBALAPIKEY: "123456789"
restart: on-failure:5

View File

@@ -0,0 +1,76 @@
#!/bin/bash
# scripts/create-mac-launcher.sh
# Create app structure
APP_NAME="Stirling-PDF"
APP_BUNDLE="$APP_NAME.app"
mkdir -p "$APP_BUNDLE/Contents/"{MacOS,Resources,Java}
# Convert icon
sips -s format icns "src/main/resources/static/favicon.ico" --out "$APP_BUNDLE/Contents/Resources/AppIcon.icns"
# Create Info.plist
cat > "$APP_BUNDLE/Contents/Info.plist" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>$APP_NAME</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>com.frooodle.stirlingpdf</string>
<key>CFBundleName</key>
<string>$APP_NAME</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.10.0</string>
<key>LSEnvironment</key>
<dict>
<key>BROWSER_OPEN</key>
<string>true</string>
</dict>
</dict>
</plist>
EOF
# Create launcher script
cat > "$APP_BUNDLE/Contents/MacOS/$APP_NAME" << 'EOF'
#!/bin/bash
# Configuration
MIN_JAVA_VERSION="17"
PREFERRED_JAVA_VERSION="21"
JAVA_DOWNLOAD_URL="https://download.oracle.com/java/21/latest/jdk-21_macos-x64_bin.dmg"
# Check Java version
if type -p java > /dev/null; then
_java=java
elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
_java="$JAVA_HOME/bin/java"
else
osascript -e 'display dialog "Java not found. Please install Java 21." buttons {"Download Java", "Cancel"} default button "Download Java"' \
&& open "$JAVA_DOWNLOAD_URL"
exit 1
fi
version=$("$_java" -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1)
if [[ "$version" -lt "$MIN_JAVA_VERSION" ]]; then
osascript -e 'display dialog "Wrong Java version. Please install Java 21." buttons {"Download Java", "Cancel"} default button "Download Java"' \
&& open "$JAVA_DOWNLOAD_URL"
exit 1
fi
# Run application
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
exec java -jar "$DIR/../Java/Stirling-PDF.jar" "$@"
EOF
chmod +x "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
# Copy JAR file
cp "build/libs/Stirling-PDF.jar" "$APP_BUNDLE/Contents/Java/"

View File

@@ -0,0 +1,36 @@
#!/bin/bash
# scripts/create-unix-launcher.sh
cat > launcher.sh << 'EOF'
#!/bin/bash
# Configuration
APP_NAME="Stirling-PDF"
MIN_JAVA_VERSION="17"
PREFERRED_JAVA_VERSION="21"
JAVA_DOWNLOAD_URL="https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz"
BROWSER_OPEN="true"
# Check Java version
if type -p java > /dev/null; then
_java=java
elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
_java="$JAVA_HOME/bin/java"
else
echo "Java not found. Please install Java 21."
xdg-open "$JAVA_DOWNLOAD_URL"
exit 1
fi
version=$("$_java" -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1)
if [[ "$version" -lt "$MIN_JAVA_VERSION" ]]; then
echo "Java version $version detected. Please install Java 21."
xdg-open "$JAVA_DOWNLOAD_URL"
exit 1
fi
# Run application
exec java -jar "$(dirname "$0")/Stirling-PDF.jar" "$@"
EOF
chmod +x launcher.sh

View File

@@ -77,11 +77,6 @@ ignore = [
'language.direction', 'language.direction',
] ]
[fa_IR]
ignore = [
'language.direction',
]
[fr_FR] [fr_FR]
ignore = [ ignore = [
'AddStampRequest.alphabet', 'AddStampRequest.alphabet',

View File

@@ -0,0 +1,28 @@
#!/bin/bash
# Configuration
MIN_JAVA_VERSION="17"
PREFERRED_JAVA_VERSION="21"
JAVA_DOWNLOAD_URL="https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz"
BROWSER_OPEN="true"
# Check Java version
if type -p java > /dev/null; then
_java=java
elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
_java="$JAVA_HOME/bin/java"
else
echo "Java not found. Please install Java 21."
xdg-open "$JAVA_DOWNLOAD_URL"
exit 1
fi
version=$("$_java" -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1)
if [[ "$version" -lt "$MIN_JAVA_VERSION" ]]; then
echo "Java version $version detected. Please install Java 21."
xdg-open "$JAVA_DOWNLOAD_URL"
exit 1
fi
# Run application
exec java -jar "$(dirname "$0")/Stirling-PDF.jar" "$@"

View File

@@ -28,7 +28,7 @@ public class LicenseKeyChecker {
this.checkLicense(); this.checkLicense();
} }
@Scheduled(initialDelay = 604800000, fixedRate = 604800000) // 7 days in milliseconds @Scheduled(initialDelay = 604800000,fixedRate = 604800000) // 7 days in milliseconds
public void checkLicensePeriodically() { public void checkLicensePeriodically() {
checkLicense(); checkLicense();
} }

View File

@@ -1,6 +1,5 @@
package stirling.software.SPDF; package stirling.software.SPDF;
import java.awt.*;
import java.io.IOException; import java.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.nio.file.Files; import java.nio.file.Files;
@@ -9,9 +8,6 @@ import java.nio.file.Paths;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import javax.swing.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -23,16 +19,13 @@ import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import io.github.pixee.security.SystemCommand; import io.github.pixee.security.SystemCommand;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.UI.WebBrowser;
import stirling.software.SPDF.config.ConfigInitializer; import stirling.software.SPDF.config.ConfigInitializer;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@SpringBootApplication @SpringBootApplication
@EnableScheduling @EnableScheduling
@Slf4j
public class SPdfApplication { public class SPdfApplication {
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class); private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
@@ -74,19 +67,36 @@ public class SPdfApplication {
} }
} }
@PostConstruct
public void init() {
baseUrlStatic = this.baseUrl;
// Check if the BROWSER_OPEN environment variable is set to true
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
if (browserOpen) {
try {
String url = baseUrl + ":" + getStaticPort();
String os = System.getProperty("os.name").toLowerCase();
Runtime rt = Runtime.getRuntime();
if (os.contains("win")) {
// For Windows
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
} else if (os.contains("mac")) {
SystemCommand.runCommand(rt, "open " + url);
} else if (os.contains("nix") || os.contains("nux")) {
SystemCommand.runCommand(rt, "xdg-open " + url);
}
} catch (Exception e) {
logger.error("Error opening browser: {}", e.getMessage());
}
}
logger.info("Running configs {}", applicationProperties.toString());
}
public static void main(String[] args) throws IOException, InterruptedException { public static void main(String[] args) throws IOException, InterruptedException {
SpringApplication app = new SpringApplication(SPdfApplication.class); SpringApplication app = new SpringApplication(SPdfApplication.class);
Properties props = new Properties();
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
System.setProperty("java.awt.headless", "false");
app.setHeadless(false);
props.put("java.awt.headless", "false");
props.put("spring.main.web-application-type", "servlet");
}
app.setAdditionalProfiles("default"); app.setAdditionalProfiles("default");
app.addInitializers(new ConfigInitializer()); app.addInitializers(new ConfigInitializer());
Map<String, String> propertyFiles = new HashMap<>(); Map<String, String> propertyFiles = new HashMap<>();
@@ -110,20 +120,14 @@ public class SPdfApplication {
} else { } else {
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist."); logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
} }
Properties finalProps = new Properties();
if (!propertyFiles.isEmpty()) { if (!propertyFiles.isEmpty()) {
finalProps.putAll( app.setDefaultProperties(
Collections.singletonMap( Collections.singletonMap(
"spring.config.additional-location", "spring.config.additional-location",
propertyFiles.get("spring.config.additional-location"))); propertyFiles.get("spring.config.additional-location")));
} }
if (!props.isEmpty()) {
finalProps.putAll(props);
}
app.setDefaultProperties(finalProps);
app.run(args); app.run(args);
// Ensure directories are created // Ensure directories are created
@@ -143,46 +147,6 @@ public class SPdfApplication {
logger.info("Navigate to {}", url); logger.info("Navigate to {}", url);
} }
@Autowired(required = false)
private WebBrowser webBrowser;
@PostConstruct
public void init() {
baseUrlStatic = this.baseUrl;
String url = baseUrl + ":" + getStaticPort();
if (webBrowser != null
&& Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
webBrowser.initWebUI(url);
} else {
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
if (browserOpen) {
try {
String os = System.getProperty("os.name").toLowerCase();
Runtime rt = Runtime.getRuntime();
if (os.contains("win")) {
// For Windows
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
} else if (os.contains("mac")) {
SystemCommand.runCommand(rt, "open " + url);
} else if (os.contains("nix") || os.contains("nux")) {
SystemCommand.runCommand(rt, "xdg-open " + url);
}
} catch (Exception e) {
logger.error("Error opening browser: {}", e.getMessage());
}
}
}
logger.info("Running configs {}", applicationProperties.toString());
}
@PreDestroy
public void cleanup() {
if (webBrowser != null) {
webBrowser.cleanup();
}
}
public static String getStaticBaseUrl() { public static String getStaticBaseUrl() {
return baseUrlStatic; return baseUrlStatic;
} }

View File

@@ -1,7 +0,0 @@
package stirling.software.SPDF.UI;
public interface WebBrowser {
void initWebUI(String url);
void cleanup();
}

View File

@@ -1,354 +0,0 @@
package stirling.software.SPDF.UI.impl;
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.io.File;
import java.io.InputStream;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.browser.CefBrowser;
import org.cef.callback.CefBeforeDownloadCallback;
import org.cef.callback.CefDownloadItem;
import org.cef.callback.CefDownloadItemCallback;
import org.cef.handler.CefDownloadHandlerAdapter;
import org.cef.handler.CefLoadHandlerAdapter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import me.friwi.jcefmaven.CefAppBuilder;
import me.friwi.jcefmaven.EnumProgress;
import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
import stirling.software.SPDF.UI.WebBrowser;
@Component
@Slf4j
@ConditionalOnProperty(
name = "STIRLING_PDF_DESKTOP_UI",
havingValue = "true",
matchIfMissing = false)
public class DesktopBrowser implements WebBrowser {
private static CefApp cefApp;
private static CefClient client;
private static CefBrowser browser;
private static JFrame frame;
private static LoadingWindow loadingWindow;
private static volatile boolean browserInitialized = false;
private static TrayIcon trayIcon;
private static SystemTray systemTray;
public DesktopBrowser() {
SwingUtilities.invokeLater(
() -> {
loadingWindow = new LoadingWindow(null, "Initializing...");
loadingWindow.setVisible(true);
});
}
public void initWebUI(String url) {
CompletableFuture.runAsync(
() -> {
try {
CefAppBuilder builder = new CefAppBuilder();
configureCefSettings(builder);
builder.setProgressHandler(createProgressHandler());
// Build and initialize CEF
cefApp = builder.build();
client = cefApp.createClient();
// Set up download handler
setupDownloadHandler();
// Create browser and frame on EDT
SwingUtilities.invokeAndWait(
() -> {
browser = client.createBrowser(url, false, false);
setupMainFrame();
setupLoadHandler();
// Show the frame immediately but transparent
frame.setVisible(true);
});
} catch (Exception e) {
log.error("Error initializing JCEF browser: ", e);
cleanup();
}
});
}
private void configureCefSettings(CefAppBuilder builder) {
CefSettings settings = builder.getCefSettings();
settings.cache_path = new File("jcef-bundle").getAbsolutePath();
settings.root_cache_path = new File("jcef-bundle").getAbsolutePath();
settings.persist_session_cookies = true;
settings.windowless_rendering_enabled = false;
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO;
builder.setAppHandler(
new MavenCefAppHandlerAdapter() {
@Override
public void stateHasChanged(org.cef.CefApp.CefAppState state) {
log.info("CEF state changed: " + state);
if (state == CefApp.CefAppState.TERMINATED) {
System.exit(0);
}
}
});
}
private void setupDownloadHandler() {
client.addDownloadHandler(
new CefDownloadHandlerAdapter() {
@Override
public boolean onBeforeDownload(
CefBrowser browser,
CefDownloadItem downloadItem,
String suggestedName,
CefBeforeDownloadCallback callback) {
callback.Continue("", true);
return true;
}
@Override
public void onDownloadUpdated(
CefBrowser browser,
CefDownloadItem downloadItem,
CefDownloadItemCallback callback) {
if (downloadItem.isComplete()) {
log.info("Download completed: " + downloadItem.getFullPath());
} else if (downloadItem.isCanceled()) {
log.info("Download canceled: " + downloadItem.getFullPath());
}
}
});
}
private ConsoleProgressHandler createProgressHandler() {
return new ConsoleProgressHandler() {
@Override
public void handleProgress(EnumProgress state, float percent) {
Objects.requireNonNull(state, "state cannot be null");
SwingUtilities.invokeLater(
() -> {
if (loadingWindow != null) {
switch (state) {
case LOCATING:
loadingWindow.setStatus("Locating Files...");
loadingWindow.setProgress(0);
break;
case DOWNLOADING:
if (percent >= 0) {
loadingWindow.setStatus(
String.format(
"Downloading additional files: %.0f%%",
percent));
loadingWindow.setProgress((int) percent);
}
break;
case EXTRACTING:
loadingWindow.setStatus("Extracting files...");
loadingWindow.setProgress(60);
break;
case INITIALIZING:
loadingWindow.setStatus("Initializing UI...");
loadingWindow.setProgress(80);
break;
case INITIALIZED:
loadingWindow.setStatus("Finalising startup...");
loadingWindow.setProgress(90);
break;
}
}
});
}
};
}
private void setupMainFrame() {
frame = new JFrame("Stirling-PDF");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setUndecorated(true);
frame.setOpacity(0.0f);
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setDoubleBuffered(true);
contentPane.add(browser.getUIComponent(), BorderLayout.CENTER);
frame.setContentPane(contentPane);
frame.addWindowListener(
new java.awt.event.WindowAdapter() {
@Override
public void windowClosing(java.awt.event.WindowEvent windowEvent) {
cleanup();
System.exit(0);
}
});
frame.setSize(1280, 768);
frame.setLocationRelativeTo(null);
loadIcon();
}
private void setupLoadHandler() {
client.addLoadHandler(
new CefLoadHandlerAdapter() {
@Override
public void onLoadingStateChange(
CefBrowser browser,
boolean isLoading,
boolean canGoBack,
boolean canGoForward) {
if (!isLoading && !browserInitialized) {
browserInitialized = true;
SwingUtilities.invokeLater(
() -> {
if (loadingWindow != null) {
Timer timer =
new Timer(
500,
e -> {
loadingWindow.dispose();
loadingWindow = null;
frame.dispose();
frame.setOpacity(1.0f);
frame.setUndecorated(false);
frame.pack();
frame.setSize(1280, 800);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.requestFocus();
frame.toFront();
browser.getUIComponent()
.requestFocus();
});
timer.setRepeats(false);
timer.start();
}
});
}
}
});
}
private void setupTrayIcon(Image icon) {
if (!SystemTray.isSupported()) {
log.warn("System tray is not supported");
return;
}
try {
systemTray = SystemTray.getSystemTray();
// Create popup menu
PopupMenu popup = new PopupMenu();
// Create menu items
MenuItem showItem = new MenuItem("Show");
showItem.addActionListener(
e -> {
frame.setVisible(true);
frame.setState(Frame.NORMAL);
});
MenuItem exitItem = new MenuItem("Exit");
exitItem.addActionListener(
e -> {
cleanup();
System.exit(0);
});
// Add menu items to popup menu
popup.add(showItem);
popup.addSeparator();
popup.add(exitItem);
// Create tray icon
trayIcon = new TrayIcon(icon, "Stirling-PDF", popup);
trayIcon.setImageAutoSize(true);
// Add double-click behavior
trayIcon.addActionListener(
e -> {
frame.setVisible(true);
frame.setState(Frame.NORMAL);
});
// Add tray icon to system tray
systemTray.add(trayIcon);
// Modify frame behavior to minimize to tray
frame.addWindowStateListener(
new WindowStateListener() {
public void windowStateChanged(WindowEvent e) {
if (e.getNewState() == Frame.ICONIFIED) {
frame.setVisible(false);
}
}
});
} catch (AWTException e) {
log.error("Error setting up system tray icon", e);
}
}
private void loadIcon() {
try {
Image icon = null;
String[] iconPaths = {"/static/favicon.ico"};
for (String path : iconPaths) {
if (icon != null) break;
try {
try (InputStream is = getClass().getResourceAsStream(path)) {
if (is != null) {
icon = ImageIO.read(is);
break;
}
}
} catch (Exception e) {
log.debug("Could not load icon from " + path, e);
}
}
if (icon != null) {
frame.setIconImage(icon);
setupTrayIcon(icon);
} else {
log.warn("Could not load icon from any source");
}
} catch (Exception e) {
log.error("Error loading icon", e);
}
}
@PreDestroy
public void cleanup() {
if (browser != null) browser.close(true);
if (client != null) client.dispose();
if (cefApp != null) cefApp.dispose();
if (loadingWindow != null) loadingWindow.dispose();
}
}

View File

@@ -1,114 +0,0 @@
package stirling.software.SPDF.UI.impl;
import java.awt.*;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.swing.*;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LoadingWindow extends JDialog {
private final JProgressBar progressBar;
private final JLabel statusLabel;
private final JPanel mainPanel;
private final JLabel brandLabel;
public LoadingWindow(Frame parent, String initialUrl) {
super(parent, "Initializing Stirling-PDF", true);
// Initialize components
mainPanel = new JPanel();
mainPanel.setBackground(Color.WHITE);
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 20, 30));
mainPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
// Configure GridBagConstraints
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(5, 5, 5, 5);
gbc.weightx = 1.0; // Add horizontal weight
gbc.weighty = 0.0; // Add vertical weight
// Add icon
try {
try (InputStream is = getClass().getResourceAsStream("/static/favicon.ico")) {
if (is != null) {
Image img = ImageIO.read(is);
if (img != null) {
Image scaledImg = img.getScaledInstance(48, 48, Image.SCALE_SMOOTH);
JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
gbc.gridy = 0;
mainPanel.add(iconLabel, gbc);
}
}
}
} catch (Exception e) {
log.error("Failed to load icon", e);
}
// URL Label with explicit size
brandLabel = new JLabel(initialUrl);
brandLabel.setHorizontalAlignment(SwingConstants.CENTER);
brandLabel.setPreferredSize(new Dimension(300, 25));
brandLabel.setText("Stirling-PDF");
gbc.gridy = 1;
mainPanel.add(brandLabel, gbc);
// Status label with explicit size
statusLabel = new JLabel("Initializing...");
statusLabel.setHorizontalAlignment(SwingConstants.CENTER);
statusLabel.setPreferredSize(new Dimension(300, 25));
gbc.gridy = 2;
mainPanel.add(statusLabel, gbc);
// Progress bar with explicit size
progressBar = new JProgressBar(0, 100);
progressBar.setStringPainted(true);
progressBar.setPreferredSize(new Dimension(300, 25));
gbc.gridy = 3;
mainPanel.add(progressBar, gbc);
// Set dialog properties
setContentPane(mainPanel);
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
setResizable(false);
setUndecorated(false);
// Set size and position
setSize(400, 200);
setLocationRelativeTo(parent);
setAlwaysOnTop(true);
setProgress(0);
setStatus("Starting...");
}
public void setProgress(final int progress) {
SwingUtilities.invokeLater(
() -> {
try {
progressBar.setValue(Math.min(Math.max(progress, 0), 100));
progressBar.setString(progress + "%");
mainPanel.revalidate();
mainPanel.repaint();
} catch (Exception e) {
log.error("Error updating progress", e);
}
});
}
public void setStatus(final String status) {
log.info(status);
SwingUtilities.invokeLater(
() -> {
try {
statusLabel.setText(status != null ? status : "");
mainPanel.revalidate();
mainPanel.repaint();
} catch (Exception e) {
log.error("Error updating status", e);
}
});
}
}

View File

@@ -260,9 +260,6 @@ public class EndpointConfiguration {
// Pdftohtml dependent endpoints // Pdftohtml dependent endpoints
addEndpointToGroup("Pdftohtml", "pdf-to-html"); addEndpointToGroup("Pdftohtml", "pdf-to-html");
// disabled for now while we resolve issues
disableEndpoint("pdf-to-pdfa");
} }
private void processEnvironmentConfigs() { private void processEnvironmentConfigs() {

View File

@@ -1,14 +1,11 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.IOException; import java.io.IOException;
import java.util.Properties;
import java.util.UUID; import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import io.micrometer.common.util.StringUtils; import io.micrometer.common.util.StringUtils;
@@ -26,18 +23,6 @@ public class InitialSetup {
@Autowired private ApplicationProperties applicationProperties; @Autowired private ApplicationProperties applicationProperties;
@PostConstruct @PostConstruct
public void init() throws IOException {
initUUIDKey();
initSecretKey();
initEnableCSRFSecurity();
initLegalUrls();
initSetAppVersion();
}
public void initUUIDKey() throws IOException { public void initUUIDKey() throws IOException {
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID(); String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
if (!GeneralUtils.isValidUUID(uuid)) { if (!GeneralUtils.isValidUUID(uuid)) {
@@ -47,6 +32,7 @@ public class InitialSetup {
} }
} }
@PostConstruct
public void initSecretKey() throws IOException { public void initSecretKey() throws IOException {
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey(); String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
if (!GeneralUtils.isValidUUID(secretKey)) { if (!GeneralUtils.isValidUUID(secretKey)) {
@@ -56,24 +42,13 @@ public class InitialSetup {
} }
} }
public void initEnableCSRFSecurity() throws IOException { @PostConstruct
if (GeneralUtils.isVersionHigher(
"0.36.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
if (!csrf) {
GeneralUtils.saveKeyToConfig("security.csrfDisabled", false, false);
GeneralUtils.saveKeyToConfig("system.enableAnalytics", "true", false);
applicationProperties.getSecurity().setCsrfDisabled(false);
}
}
}
public void initLegalUrls() throws IOException { public void initLegalUrls() throws IOException {
// Initialize Terms and Conditions // Initialize Terms and Conditions
String termsUrl = applicationProperties.getLegal().getTermsAndConditions(); String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
if (StringUtils.isEmpty(termsUrl)) { if (StringUtils.isEmpty(termsUrl)) {
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions"; String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl, false); GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl);
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl); applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
} }
@@ -81,23 +56,8 @@ public class InitialSetup {
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy(); String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
if (StringUtils.isEmpty(privacyUrl)) { if (StringUtils.isEmpty(privacyUrl)) {
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy"; String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl, false); GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl);
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl); applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
} }
} }
public void initSetAppVersion() throws IOException {
String appVersion = "0.0.0";
Resource resource = new ClassPathResource("version.properties");
Properties props = new Properties();
try {
props.load(resource.getInputStream());
appVersion = props.getProperty("version");
} catch (Exception e) {
}
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.appVersion", appVersion, false);
}
} }

View File

@@ -75,6 +75,5 @@ public class InitialSecuritySetup {
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId()); userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId()); log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
} }
userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());
} }
} }

View File

@@ -99,7 +99,7 @@ public class SecurityConfiguration {
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) { if (applicationProperties.getSecurity().getCsrfDisabled()) {
http.csrf(csrf -> csrf.disable()); http.csrf(csrf -> csrf.disable());
} }
@@ -116,7 +116,7 @@ public class SecurityConfiguration {
csrf -> csrf ->
csrf.ignoringRequestMatchers( csrf.ignoringRequestMatchers(
request -> { request -> {
String apiKey = request.getHeader("X-API-KEY"); String apiKey = request.getHeader("X-API-Key");
// If there's no API key, don't ignore CSRF // If there's no API key, don't ignore CSRF
// (return false) // (return false)
@@ -289,17 +289,17 @@ public class SecurityConfiguration {
} }
} else { } else {
// if (!applicationProperties.getSecurity().getCsrfDisabled()) { if (!applicationProperties.getSecurity().getCsrfDisabled()) {
// CookieCsrfTokenRepository cookieRepo = CookieCsrfTokenRepository cookieRepo =
// CookieCsrfTokenRepository.withHttpOnlyFalse(); CookieCsrfTokenRepository.withHttpOnlyFalse();
// CsrfTokenRequestAttributeHandler requestHandler = CsrfTokenRequestAttributeHandler requestHandler =
// new CsrfTokenRequestAttributeHandler(); new CsrfTokenRequestAttributeHandler();
// requestHandler.setCsrfRequestAttributeName(null); requestHandler.setCsrfRequestAttributeName(null);
// http.csrf( http.csrf(
// csrf -> csrf ->
// csrf.csrfTokenRepository(cookieRepo) csrf.csrfTokenRepository(cookieRepo)
// .csrfTokenRequestHandler(requestHandler)); .csrfTokenRequestHandler(requestHandler));
// } }
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
} }

View File

@@ -71,7 +71,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
// Check for API key in the request headers if no authentication exists // Check for API key in the request headers if no authentication exists
if (authentication == null || !authentication.isAuthenticated()) { if (authentication == null || !authentication.isAuthenticated()) {
String apiKey = request.getHeader("X-API-KEY"); String apiKey = request.getHeader("X-API-Key");
if (apiKey != null && !apiKey.trim().isEmpty()) { if (apiKey != null && !apiKey.trim().isEmpty()) {
try { try {
// Use API key to authenticate. This requires you to have an authentication // Use API key to authenticate. This requires you to have an authentication

View File

@@ -59,7 +59,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
String identifier = null; String identifier = null;
// Check for API key in the request headers // Check for API key in the request headers
String apiKey = request.getHeader("X-API-KEY"); String apiKey = request.getHeader("X-API-Key");
if (apiKey != null && !apiKey.trim().isEmpty()) { if (apiKey != null && !apiKey.trim().isEmpty()) {
identifier = identifier =
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames "API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
@@ -79,7 +79,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
Role userRole = Role userRole =
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication()); getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
if (request.getHeader("X-API-KEY") != null) { if (request.getHeader("X-API-Key") != null) {
// It's an API call // It's an API call
processRequest( processRequest(
userRole.getApiCallsPerDay(), userRole.getApiCallsPerDay(),

View File

@@ -390,37 +390,6 @@ public class UserService implements UserServiceInterface {
} }
} }
@Transactional
public void syncCustomApiUser(String customApiKey) throws IOException {
if (customApiKey == null || customApiKey.trim().length() == 0) {
return;
}
String username = "CUSTOM_API_USER";
Optional<User> existingUser = findByUsernameIgnoreCase(username);
if (!existingUser.isPresent()) {
// Create new user with API role
User user = new User();
user.setUsername(username);
user.setPassword(UUID.randomUUID().toString());
user.setEnabled(true);
user.setFirstLogin(false);
user.setAuthenticationType(AuthenticationType.WEB);
user.setApiKey(customApiKey);
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
userRepository.save(user);
databaseBackupHelper.exportDatabase();
} else {
// Update API key if it has changed
User user = existingUser.get();
if (!customApiKey.equals(user.getApiKey())) {
user.setApiKey(customApiKey);
userRepository.save(user);
databaseBackupHelper.exportDatabase();
}
}
}
@Override @Override
public long getTotalUsersCount() { public long getTotalUsersCount() {
return userRepository.count(); return userRepository.count();

View File

@@ -52,115 +52,84 @@ public class SplitPDFController {
"This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO") "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request) public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request)
throws IOException { throws IOException {
MultipartFile file = request.getFileInput();
String pages = request.getPageNumbers();
// open the pdf document
PDDocument document = null; PDDocument document = Loader.loadPDF(file.getBytes());
Path zipFile = null; // PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
int totalPages = document.getNumberOfPages();
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
if (!pageNumbers.contains(totalPages - 1)) {
// Create a mutable ArrayList so we can add to it
pageNumbers = new ArrayList<>(pageNumbers);
pageNumbers.add(totalPages - 1);
}
logger.info(
"Splitting PDF into pages: {}",
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
int previousPageNumber = 0;
try { for (int splitPoint : pageNumbers) {
try (PDDocument splitDocument =
MultipartFile file = request.getFileInput(); pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
String pages = request.getPageNumbers(); for (int i = previousPageNumber; i <= splitPoint; i++) {
// open the pdf document PDPage page = document.getPage(i);
splitDocument.addPage(page);
document = Loader.loadPDF(file.getBytes()); logger.info("Adding page {} to split document", i);
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
int totalPages = document.getNumberOfPages();
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
if (!pageNumbers.contains(totalPages - 1)) {
// Create a mutable ArrayList so we can add to it
pageNumbers = new ArrayList<>(pageNumbers);
pageNumbers.add(totalPages - 1);
}
logger.info(
"Splitting PDF into pages: {}",
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
splitDocumentsBoas = new ArrayList<>();
int previousPageNumber = 0;
for (int splitPoint : pageNumbers) {
try (PDDocument splitDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
for (int i = previousPageNumber; i <= splitPoint; i++) {
PDPage page = document.getPage(i);
splitDocument.addPage(page);
logger.info("Adding page {} to split document", i);
}
previousPageNumber = splitPoint + 1;
// Transfer metadata to split pdf
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos);
splitDocumentsBoas.add(baos);
} catch (Exception e) {
logger.error("Failed splitting documents and saving them", e);
throw e;
} }
} previousPageNumber = splitPoint + 1;
// closing the original document // Transfer metadata to split pdf
document.close(); // PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
zipFile = Files.createTempFile("split_documents", ".zip"); ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos);
String filename = splitDocumentsBoas.add(baos);
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
// loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = filename + "_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry();
logger.info("Wrote split document {} to zip file", fileName);
}
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed writing to zip", e); logger.error("Failed splitting documents and saving them", e);
throw e; throw e;
} }
logger.info(
"Successfully created zip file with split documents: {}", zipFile.toString());
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
// return the Resource in the response
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
} finally {
try {
// Close the main document
if (document != null) {
document.close();
}
// Close all ByteArrayOutputStreams
for (ByteArrayOutputStream baos : splitDocumentsBoas) {
if (baos != null) {
baos.close();
}
}
// Delete temporary zip file
if (zipFile != null) {
Files.deleteIfExists(zipFile);
}
} catch (Exception e) {
logger.error("Error while cleaning up resources", e);
}
} }
// closing the original document
document.close();
Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
// loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = filename + "_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry();
logger.info("Wrote split document {} to zip file", fileName);
}
} catch (Exception e) {
logger.error("Failed writing to zip", e);
throw e;
}
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
// return the Resource in the response
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
} }
} }

View File

@@ -59,86 +59,70 @@ public class SplitPdfByChaptersController {
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request) public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
throws Exception { throws Exception {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
PDDocument sourceDocument = null; boolean includeMetadata = request.getIncludeMetadata();
Path zipFile = null; Integer bookmarkLevel =
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
try { if (bookmarkLevel < 0) {
boolean includeMetadata = request.getIncludeMetadata(); return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
Integer bookmarkLevel =
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
if (bookmarkLevel < 0) {
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
}
sourceDocument = Loader.loadPDF(file.getBytes());
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
if (outline == null) {
logger.warn("No outline found for {}", file.getOriginalFilename());
return ResponseEntity.badRequest().body("No outline found".getBytes());
}
List<Bookmark> bookmarks = new ArrayList<>();
try {
bookmarks =
extractOutlineItems(
sourceDocument,
outline.getFirstChild(),
bookmarks,
outline.getFirstChild().getNextSibling(),
0,
bookmarkLevel);
// to handle last page edge case
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
} catch (Exception e) {
logger.error("Unable to extract outline items", e);
return ResponseEntity.internalServerError()
.body("Unable to extract outline items".getBytes());
}
boolean allowDuplicates = request.getAllowDuplicates();
if (!allowDuplicates) {
/*
duplicates are generated when multiple bookmarks correspond to the same page,
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
the bookmarks that correspond to the same page, and treat them as a single bookmark
*/
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
}
for (Bookmark bookmark : bookmarks) {
logger.info(
"{}::::{} to {}",
bookmark.getTitle(),
bookmark.getStartPage(),
bookmark.getEndPage());
}
List<ByteArrayOutputStream> splitDocumentsBoas =
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
zipFile = createZipFile(bookmarks, splitDocumentsBoas);
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
sourceDocument.close();
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
} finally {
try {
if (sourceDocument != null) {
sourceDocument.close();
}
if (zipFile != null) {
Files.deleteIfExists(zipFile);
}
} catch (Exception e) {
logger.error("Error while cleaning up resources", e);
}
} }
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
if (outline == null) {
logger.warn("No outline found for {}", file.getOriginalFilename());
return ResponseEntity.badRequest().body("No outline found".getBytes());
}
List<Bookmark> bookmarks = new ArrayList<>();
try {
bookmarks =
extractOutlineItems(
sourceDocument,
outline.getFirstChild(),
bookmarks,
outline.getFirstChild().getNextSibling(),
0,
bookmarkLevel);
// to handle last page edge case
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
} catch (Exception e) {
logger.error("Unable to extract outline items", e);
return ResponseEntity.internalServerError()
.body("Unable to extract outline items".getBytes());
}
boolean allowDuplicates = request.getAllowDuplicates();
if (!allowDuplicates) {
/*
duplicates are generated when multiple bookmarks correspond to the same page,
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
the bookmarks that correspond to the same page, and treat them as a single bookmark
*/
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
}
for (Bookmark bookmark : bookmarks) {
logger.info(
"{}::::{} to {}",
bookmark.getTitle(),
bookmark.getStartPage(),
bookmark.getEndPage());
}
List<ByteArrayOutputStream> splitDocumentsBoas =
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
Path zipFile = createZipFile(bookmarks, splitDocumentsBoas);
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
sourceDocument.close();
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
} }
private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) { private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {

View File

@@ -105,13 +105,15 @@ public class SplitPdfBySectionsController {
if (sectionNum == horiz * verti) pageNum++; if (sectionNum == horiz * verti) pageNum++;
} }
data = Files.readAllBytes(zipFile); } catch (Exception e) {
return WebResponseUtils.bytesToWebResponse( logger.error("exception", e);
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
} finally { } finally {
data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile); Files.deleteIfExists(zipFile);
} }
return WebResponseUtils.bytesToWebResponse(
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
} }
public List<PDDocument> splitPdfPages( public List<PDDocument> splitPdfPages(

View File

@@ -65,137 +65,112 @@ public class ConvertImgPDFController {
String colorType = request.getColorType(); String colorType = request.getColorType();
String dpi = request.getDpi(); String dpi = request.getDpi();
Path tempFile = null; byte[] pdfBytes = file.getBytes();
Path tempOutputDir = null; ImageType colorTypeResult = ImageType.RGB;
Path tempPdfPath = null; if ("greyscale".equals(colorType)) {
colorTypeResult = ImageType.GRAY;
} else if ("blackwhite".equals(colorType)) {
colorTypeResult = ImageType.BINARY;
}
// returns bytes for image
boolean singleImage = "single".equals(singleOrMultiple);
byte[] result = null; byte[] result = null;
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
try { result =
byte[] pdfBytes = file.getBytes(); PdfUtils.convertFromPdf(
ImageType colorTypeResult = ImageType.RGB; pdfBytes,
if ("greyscale".equals(colorType)) { "webp".equalsIgnoreCase(imageFormat) ? "png" : imageFormat.toUpperCase(),
colorTypeResult = ImageType.GRAY; colorTypeResult,
} else if ("blackwhite".equals(colorType)) { singleImage,
colorTypeResult = ImageType.BINARY; Integer.valueOf(dpi),
} filename);
// returns bytes for image if (result == null || result.length == 0) {
boolean singleImage = "single".equals(singleOrMultiple); logger.error("resultant bytes for {} is null, error converting ", filename);
String filename = }
Filenames.toSimpleFileName(file.getOriginalFilename()) if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
.replaceFirst("[.][^.]+$", ""); throw new IOException("Python is not installed. Required for WebP conversion.");
} else if ("webp".equalsIgnoreCase(imageFormat)
result = && CheckProgramInstall.isPythonAvailable()) {
PdfUtils.convertFromPdf( // Write the output stream to a temp file
pdfBytes, Path tempFile = Files.createTempFile("temp_png", ".png");
"webp".equalsIgnoreCase(imageFormat) try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
? "png" fos.write(result);
: imageFormat.toUpperCase(), fos.flush();
colorTypeResult,
singleImage,
Integer.valueOf(dpi),
filename);
if (result == null || result.length == 0) {
logger.error("resultant bytes for {} is null, error converting ", filename);
}
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
throw new IOException("Python is not installed. Required for WebP conversion.");
} else if ("webp".equalsIgnoreCase(imageFormat)
&& CheckProgramInstall.isPythonAvailable()) {
// Write the output stream to a temp file
tempFile = Files.createTempFile("temp_png", ".png");
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
fos.write(result);
fos.flush();
}
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
List<String> command = new ArrayList<>();
command.add(pythonVersion);
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
// Create a temporary directory for the output WebP files
tempOutputDir = Files.createTempDirectory("webp_output");
if (singleImage) {
// Run the Python script to convert PNG to WebP
command.add(tempFile.toString());
command.add(tempOutputDir.toString());
command.add("--single");
} else {
// Save the uploaded PDF to a temporary file
tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
file.transferTo(tempPdfPath.toFile());
// Run the Python script to convert PDF to WebP
command.add(tempPdfPath.toString());
command.add(tempOutputDir.toString());
}
command.add("--dpi");
command.add(dpi);
ProcessExecutorResult resultProcess =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command);
// Find all WebP files in the output directory
List<Path> webpFiles =
Files.walk(tempOutputDir)
.filter(path -> path.toString().endsWith(".webp"))
.collect(Collectors.toList());
if (webpFiles.isEmpty()) {
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
throw new IOException(
"No WebP files were created. " + resultProcess.getMessages());
}
byte[] bodyBytes = new byte[0];
if (webpFiles.size() == 1) {
// Return the single WebP file directly
Path webpFilePath = webpFiles.get(0);
bodyBytes = Files.readAllBytes(webpFilePath);
} else {
// Create a ZIP file containing all WebP images
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
for (Path webpFile : webpFiles) {
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
Files.copy(webpFile, zos);
zos.closeEntry();
}
}
bodyBytes = zipOutputStream.toByteArray();
}
// Clean up the temporary files
Files.deleteIfExists(tempFile);
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
result = bodyBytes;
} }
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
List<String> command = new ArrayList<>();
command.add(pythonVersion);
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
// Create a temporary directory for the output WebP files
Path tempOutputDir = Files.createTempDirectory("webp_output");
if (singleImage) { if (singleImage) {
String docName = filename + "." + imageFormat; // Run the Python script to convert PNG to WebP
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat)); command.add(tempFile.toString());
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType); command.add(tempOutputDir.toString());
command.add("--single");
} else { } else {
String zipFilename = filename + "_convertedToImages.zip"; // Save the uploaded PDF to a temporary file
return WebResponseUtils.bytesToWebResponse( Path tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM); file.transferTo(tempPdfPath.toFile());
// Run the Python script to convert PDF to WebP
command.add(tempPdfPath.toString());
command.add(tempOutputDir.toString());
}
command.add("--dpi");
command.add(dpi);
ProcessExecutorResult resultProcess =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command);
// Find all WebP files in the output directory
List<Path> webpFiles =
Files.walk(tempOutputDir)
.filter(path -> path.toString().endsWith(".webp"))
.collect(Collectors.toList());
if (webpFiles.isEmpty()) {
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
throw new IOException("No WebP files were created. " + resultProcess.getMessages());
} }
} finally { byte[] bodyBytes = new byte[0];
try {
// Clean up temporary files if (webpFiles.size() == 1) {
if (tempFile != null) { // Return the single WebP file directly
Files.deleteIfExists(tempFile); Path webpFilePath = webpFiles.get(0);
bodyBytes = Files.readAllBytes(webpFilePath);
} else {
// Create a ZIP file containing all WebP images
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
for (Path webpFile : webpFiles) {
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
Files.copy(webpFile, zos);
zos.closeEntry();
}
} }
if (tempPdfPath != null) { bodyBytes = zipOutputStream.toByteArray();
Files.deleteIfExists(tempPdfPath);
}
if (tempOutputDir != null) {
FileUtils.deleteDirectory(tempOutputDir.toFile());
}
} catch (Exception e) {
logger.error("Error cleaning up temporary files", e);
} }
// Clean up the temporary files
Files.deleteIfExists(tempFile);
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
result = bodyBytes;
}
if (singleImage) {
String docName = filename + "." + imageFormat;
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
} else {
String zipFilename = filename + "_convertedToImages.zip";
return WebResponseUtils.bytesToWebResponse(
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
} }
} }

View File

@@ -87,7 +87,7 @@ public class OCRController {
Files.createDirectories(tempOutputDir); Files.createDirectories(tempOutputDir);
Files.createDirectories(tempImagesDir); Files.createDirectories(tempImagesDir);
Process process = null;
try { try {
// Save input file // Save input file
inputFile.transferTo(tempInputFile.toFile()); inputFile.transferTo(tempInputFile.toFile());
@@ -139,7 +139,7 @@ public class OCRController {
command.add("pdf"); // Always output PDF command.add("pdf"); // Always output PDF
ProcessBuilder pb = new ProcessBuilder(command); ProcessBuilder pb = new ProcessBuilder(command);
process = pb.start(); Process process = pb.start();
// Capture any error output // Capture any error output
try (BufferedReader reader = try (BufferedReader reader =
@@ -188,10 +188,6 @@ public class OCRController {
.body(pdfContent); .body(pdfContent);
} finally { } finally {
if (process != null) {
process.destroy();
}
// Clean up temporary files // Clean up temporary files
deleteDirectory(tempDir); deleteDirectory(tempDir);
} }

View File

@@ -221,7 +221,7 @@ public class PipelineProcessor {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
String apiKey = getApiKeyForUser(); String apiKey = getApiKeyForUser();
headers.add("X-API-KEY", apiKey); headers.add("X-API-Key", apiKey);
headers.setContentType(MediaType.MULTIPART_FORM_DATA); headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// Create HttpEntity with the body and headers // Create HttpEntity with the body and headers

View File

@@ -595,9 +595,7 @@ public class GetInfoOnPDF {
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument())); permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent())); permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
permissionsNode.put( permissionsNode.put("Extracting for accessibility", getPermissionState(ap.canExtractForAccessibility()));
"Extracting for accessibility",
getPermissionState(ap.canExtractForAccessibility()));
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm())); permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
permissionsNode.put("Modifying", getPermissionState(ap.canModify())); permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations())); permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));

View File

@@ -39,7 +39,10 @@ public class PasswordController {
} }
@PostMapping(consumes = "multipart/form-data", value = "/remove-password") @PostMapping(consumes = "multipart/form-data", value = "/remove-password")
@Operation(summary = "Remove password from a PDF file", description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO") @Operation(
summary = "Remove password from a PDF file",
description =
"This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request) public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
throws IOException { throws IOException {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
@@ -49,12 +52,15 @@ public class PasswordController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(fileInput.getOriginalFilename()) Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "") .replaceFirst("[.][^.]+$", "")
+ "_password_removed.pdf"); + "_password_removed.pdf");
} }
@PostMapping(consumes = "multipart/form-data", value = "/add-password") @PostMapping(consumes = "multipart/form-data", value = "/add-password")
@Operation(summary = "Add password to a PDF file", description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF") @Operation(
summary = "Add password to a PDF file",
description =
"This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF")
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request) public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
throws IOException { throws IOException {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
@@ -92,12 +98,12 @@ public class PasswordController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(fileInput.getOriginalFilename()) Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "") .replaceFirst("[.][^.]+$", "")
+ "_permissions.pdf"); + "_permissions.pdf");
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(fileInput.getOriginalFilename()) Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "") .replaceFirst("[.][^.]+$", "")
+ "_passworded.pdf"); + "_passworded.pdf");
} }
} }

View File

@@ -92,29 +92,20 @@ public class ValidateSignatureController {
SignerInformationStore signerStore = signedData.getSignerInfos(); SignerInformationStore signerStore = signedData.getSignerInfos();
for (SignerInformation signer : signerStore.getSigners()) { for (SignerInformation signer : signerStore.getSigners()) {
X509CertificateHolder certHolder = X509CertificateHolder certHolder = (X509CertificateHolder) certStore.getMatches(signer.getSID()).iterator().next();
(X509CertificateHolder) X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder);
certStore.getMatches(signer.getSID()).iterator().next();
X509Certificate cert =
new JcaX509CertificateConverter().getCertificate(certHolder);
boolean isValid = boolean isValid = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
result.setValid(isValid); result.setValid(isValid);
// Additional validations // Additional validations
result.setChainValid( result.setChainValid(customCert != null
customCert != null ? certValidationService.validateCertificateChainWithCustomCert(cert, customCert)
? certValidationService : certValidationService.validateCertificateChain(cert));
.validateCertificateChainWithCustomCert(
cert, customCert)
: certValidationService.validateCertificateChain(cert));
result.setTrustValid( result.setTrustValid(customCert != null
customCert != null ? certValidationService.validateTrustWithCustomCert(cert, customCert)
? certValidationService.validateTrustWithCustomCert( : certValidationService.validateTrustStore(cert));
cert, customCert)
: certValidationService.validateTrustStore(cert));
result.setNotRevoked(!certValidationService.isRevoked(cert)); result.setNotRevoked(!certValidationService.isRevoked(cert));
result.setNotExpired(!cert.getNotAfter().before(new Date())); result.setNotExpired(!cert.getNotAfter().before(new Date()));
@@ -132,18 +123,17 @@ public class ValidateSignatureController {
result.setValidFrom(cert.getNotBefore().toString()); result.setValidFrom(cert.getNotBefore().toString());
result.setValidUntil(cert.getNotAfter().toString()); result.setValidUntil(cert.getNotAfter().toString());
result.setSignatureAlgorithm(cert.getSigAlgName()); result.setSignatureAlgorithm(cert.getSigAlgName());
// Get key size (if possible) // Get key size (if possible)
try { try {
result.setKeySize( result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
} catch (Exception e) { } catch (Exception e) {
// If not RSA or error, set to 0 // If not RSA or error, set to 0
result.setKeySize(0); result.setKeySize(0);
} }
result.setVersion(String.valueOf(cert.getVersion())); result.setVersion(String.valueOf(cert.getVersion()));
// Set key usage // Set key usage
List<String> keyUsages = new ArrayList<>(); List<String> keyUsages = new ArrayList<>();
boolean[] keyUsageFlags = cert.getKeyUsage(); boolean[] keyUsageFlags = cert.getKeyUsage();
@@ -160,11 +150,9 @@ public class ValidateSignatureController {
} }
} }
result.setKeyUsages(keyUsages); result.setKeyUsages(keyUsages);
// Check if self-signed // Check if self-signed
result.setSelfSigned( result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()));
cert.getSubjectX500Principal()
.equals(cert.getIssuerX500Principal()));
} }
} catch (Exception e) { } catch (Exception e) {
result.setValid(false); result.setValid(false);

View File

@@ -73,7 +73,6 @@ public class ApplicationProperties {
private int loginAttemptCount; private int loginAttemptCount;
private long loginResetTimeMinutes; private long loginResetTimeMinutes;
private String loginMethod = "all"; private String loginMethod = "all";
private String customGlobalAPIKey;
public Boolean isAltLogin() { public Boolean isAltLogin() {
return saml2.getEnabled() || oauth2.getEnabled(); return saml2.getEnabled() || oauth2.getEnabled();
@@ -286,7 +285,6 @@ public class ApplicationProperties {
public static class AutomaticallyGenerated { public static class AutomaticallyGenerated {
@ToString.Exclude private String key; @ToString.Exclude private String key;
private String UUID; private String UUID;
private String appVersion;
} }
@Data @Data

View File

@@ -16,15 +16,16 @@ public class SignatureValidationResult {
private boolean trustValid; private boolean trustValid;
private boolean notExpired; private boolean notExpired;
private boolean notRevoked; private boolean notRevoked;
private String issuerDN; // Certificate issuer's Distinguished Name private String issuerDN; // Certificate issuer's Distinguished Name
private String subjectDN; // Certificate subject's Distinguished Name private String subjectDN; // Certificate subject's Distinguished Name
private String serialNumber; // Certificate serial number private String serialNumber; // Certificate serial number
private String validFrom; // Certificate validity start date private String validFrom; // Certificate validity start date
private String validUntil; // Certificate validity end date private String validUntil; // Certificate validity end date
private String signatureAlgorithm; // Algorithm used for signing private String signatureAlgorithm;// Algorithm used for signing
private int keySize; // Key size in bits private int keySize; // Key size in bits
private String version; // Certificate version private String version; // Certificate version
private List<String> keyUsages; // List of key usage purposes private List<String> keyUsages; // List of key usage purposes
private boolean isSelfSigned; // Whether the certificate is self-signed private boolean isSelfSigned; // Whether the certificate is self-signed
} }

View File

@@ -1,5 +1,6 @@
package stirling.software.SPDF.service; package stirling.software.SPDF.service;
import io.github.pixee.security.BoundedLineReader;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -23,8 +24,6 @@ import java.util.Set;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import io.github.pixee.security.BoundedLineReader;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
@Service @Service

View File

@@ -88,45 +88,15 @@ public class GeneralUtils {
public static boolean isURLReachable(String urlStr) { public static boolean isURLReachable(String urlStr) {
try { try {
// Parse the URL
URL url = URI.create(urlStr).toURL(); URL url = URI.create(urlStr).toURL();
// Allow only http and https protocols
String protocol = url.getProtocol();
if (!protocol.equals("http") && !protocol.equals("https")) {
return false; // Disallow other protocols
}
// Check if the host is a local address
String host = url.getHost();
if (isLocalAddress(host)) {
return false; // Exclude local addresses
}
// Check if the URL is reachable
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("HEAD"); connection.setRequestMethod("HEAD");
// connection.setConnectTimeout(5000); // Set connection timeout
// connection.setReadTimeout(5000); // Set read timeout
int responseCode = connection.getResponseCode(); int responseCode = connection.getResponseCode();
return (200 <= responseCode && responseCode <= 399); return (200 <= responseCode && responseCode <= 399);
} catch (Exception e) { } catch (MalformedURLException e) {
return false; // Return false in case of any exception return false;
} } catch (IOException e) {
} return false;
private static boolean isLocalAddress(String host) {
try {
// Resolve DNS to IP address
InetAddress address = InetAddress.getByName(host);
// Check for local addresses
return address.isAnyLocalAddress() || // Matches 0.0.0.0 or similar
address.isLoopbackAddress() || // Matches 127.0.0.1 or ::1
address.isSiteLocalAddress() || // Matches private IPv4 ranges: 192.168.x.x, 10.x.x.x, 172.16.x.x to 172.31.x.x
address.getHostAddress().startsWith("fe80:"); // Matches link-local IPv6 addresses
} catch (Exception e) {
return false; // Return false for invalid or unresolved addresses
} }
} }
@@ -319,10 +289,6 @@ public class GeneralUtils {
saveKeyToConfig(id, key, true); saveKeyToConfig(id, key, true);
} }
public static void saveKeyToConfig(String id, boolean key) throws IOException {
saveKeyToConfig(id, key, true);
}
public static void saveKeyToConfig(String id, String key, boolean autoGenerated) public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
throws IOException { throws IOException {
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
@@ -341,24 +307,6 @@ public class GeneralUtils {
settingsYml.save(); settingsYml.save();
} }
public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated)
throws IOException {
Path path = Paths.get("configs", "settings.yml");
final YamlFile settingsYml = new YamlFile(path.toFile());
DumperOptions yamlOptionssettingsYml =
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
yamlOptionssettingsYml.setSplitLines(false);
settingsYml.loadWithComments();
YamlFileWrapper writer = settingsYml.path(id).set(key);
if (autoGenerated) {
writer.comment("# Automatically Generated Settings (Do Not Edit Directly)");
}
settingsYml.save();
}
public static String generateMachineFingerprint() { public static String generateMachineFingerprint() {
try { try {
// Get the MAC address // Get the MAC address
@@ -401,33 +349,4 @@ public class GeneralUtils {
return "GenericID"; return "GenericID";
} }
} }
public static boolean isVersionHigher(String currentVersion, String compareVersion) {
if (currentVersion == null || compareVersion == null) {
return false;
}
// Split versions into components
String[] current = currentVersion.split("\\.");
String[] compare = compareVersion.split("\\.");
// Get the length of the shorter version array
int length = Math.min(current.length, compare.length);
// Compare each component
for (int i = 0; i < length; i++) {
int currentPart = Integer.parseInt(current[i]);
int comparePart = Integer.parseInt(compare[i]);
if (currentPart > comparePart) {
return true;
}
if (currentPart < comparePart) {
return false;
}
}
// If all components so far are equal, the longer version is considered higher
return current.length > compare.length;
}
} }

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=الصفحات المحددة
multiTool.undo=تراجع multiTool.undo=تراجع
multiTool.redo=إعادة إجراء 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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية! multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Seçilmiş Səhifə(lər)
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=Bu xüsusiyyət bizim <a href="{0}">multi-alət səhifə</a>mizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin! multiTool-advert.message=Bu xüsusiyyət bizim <a href="{0}">multi-alət səhifə</a>mizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Ausgewählte Seite(n)
multiTool.undo=Rückgängig machen multiTool.undo=Rückgängig machen
multiTool.redo=Wiederherstellen multiTool.redo=Wiederherstellen
#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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen! multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -122,7 +122,6 @@ enterpriseEdition.warning=این ویژگی فقط برای کاربران حر
enterpriseEdition.yamlAdvert=Stirling PDF Pro از فایل‌های پیکربندی YAML و دیگر ویژگی‌های SSO پشتیبانی می‌کند. enterpriseEdition.yamlAdvert=Stirling PDF Pro از فایل‌های پیکربندی YAML و دیگر ویژگی‌های SSO پشتیبانی می‌کند.
enterpriseEdition.ssoAdvert=به دنبال ویژگی‌های بیشتر برای مدیریت کاربران هستید؟ Stirling PDF Pro را بررسی کنید enterpriseEdition.ssoAdvert=به دنبال ویژگی‌های بیشتر برای مدیریت کاربران هستید؟ Stirling PDF Pro را بررسی کنید
################# #################
# Analytics # # Analytics #
################# #################
@@ -516,7 +515,6 @@ home.validateSignature.title=اعتبارسنجی امضای PDF
home.validateSignature.desc=تأیید امضاها و گواهی‌های دیجیتال در اسناد PDF home.validateSignature.desc=تأیید امضاها و گواهی‌های دیجیتال در اسناد PDF
validateSignature.tags=امضا، تأیید، اعتبارسنجی، PDF، گواهی‌نامه، امضای دیجیتال validateSignature.tags=امضا، تأیید، اعتبارسنجی، PDF، گواهی‌نامه، امضای دیجیتال
#replace-invert-color
replace-color.title=جایگزینی/معکوس کردن رنگ replace-color.title=جایگزینی/معکوس کردن رنگ
replace-color.header=جایگزینی/معکوس کردن رنگ PDF replace-color.header=جایگزینی/معکوس کردن رنگ PDF
home.replaceColorPdf.title=جایگزینی و معکوس کردن رنگ home.replaceColorPdf.title=جایگزینی و معکوس کردن رنگ
@@ -537,6 +535,7 @@ replace-color.submit=جایگزینی
########################### ###########################
# # # #
# WEB PAGES # # WEB PAGES #
@@ -612,7 +611,6 @@ MarkdownToPDF.help=در حال پیشرفت
MarkdownToPDF.credit=از WeasyPrint استفاده می‌کند MarkdownToPDF.credit=از WeasyPrint استفاده می‌کند
#url-to-pdf #url-to-pdf
URLToPDF.title=URL به PDF URLToPDF.title=URL به PDF
URLToPDF.header=URL به PDF URLToPDF.header=URL به PDF
@@ -905,7 +903,7 @@ compress.selectText.5=اندازه PDF مورد انتظار (مثلاً ۲۵MB
compress.submit=فشرده‌سازی compress.submit=فشرده‌سازی
#Add image # Add image
addImage.title=افزودن تصویر addImage.title=افزودن تصویر
addImage.header=افزودن تصویر به PDF addImage.header=افزودن تصویر به PDF
addImage.everyPage=هر صفحه؟ addImage.everyPage=هر صفحه؟
@@ -913,7 +911,7 @@ addImage.upload=افزودن تصویر
addImage.submit=افزودن تصویر addImage.submit=افزودن تصویر
#merge # Merge
merge.title=ادغام merge.title=ادغام
merge.header=ادغام چندین PDF (۲+) merge.header=ادغام چندین PDF (۲+)
merge.sortByName=مرتب‌سازی بر اساس نام merge.sortByName=مرتب‌سازی بر اساس نام
@@ -922,7 +920,7 @@ merge.removeCertSign=حذف امضای دیجیتال در فایل ادغام
merge.submit=ادغام merge.submit=ادغام
#pdfOrganiser # PDF Organizer
pdfOrganiser.title=سازماندهی صفحات pdfOrganiser.title=سازماندهی صفحات
pdfOrganiser.header=سازماندهی صفحات PDF pdfOrganiser.header=سازماندهی صفحات PDF
pdfOrganiser.submit=بازآرایی صفحات pdfOrganiser.submit=بازآرایی صفحات
@@ -940,7 +938,7 @@ pdfOrganiser.mode.10=ادغام فرد-زوج
pdfOrganiser.placeholder=(مثال: ۱,۳,۲ یا ۴-۸,۲,۱۰-۱۲ یا 2n-1) pdfOrganiser.placeholder=(مثال: ۱,۳,۲ یا ۴-۸,۲,۱۰-۱۲ یا 2n-1)
#multiTool # Multi Tool
multiTool.title=ابزار چندگانه PDF multiTool.title=ابزار چندگانه PDF
multiTool.header=ابزار چندگانه PDF multiTool.header=ابزار چندگانه PDF
multiTool.uploadPrompts=نام فایل multiTool.uploadPrompts=نام فایل
@@ -965,24 +963,14 @@ multiTool.dragDropMessage=صفحه(ها) انتخاب شده‌اند
multiTool.undo=واگرد multiTool.undo=واگرد
multiTool.redo=بازگرداندن multiTool.redo=بازگرداندن
#decrypt # Multi Tool Advert
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.
#multiTool-advert
multiTool-advert.message=این ویژگی همچنین در <a href="{0}">صفحه ابزار چندگانه ما</a> موجود است. برای رابط کاربری صفحه به صفحه پیشرفته و ویژگی‌های اضافی بررسی کنید! multiTool-advert.message=این ویژگی همچنین در <a href="{0}">صفحه ابزار چندگانه ما</a> موجود است. برای رابط کاربری صفحه به صفحه پیشرفته و ویژگی‌های اضافی بررسی کنید!
#view pdf # View PDF
viewPdf.title=مشاهده PDF viewPdf.title=مشاهده PDF
viewPdf.header=مشاهده PDF viewPdf.header=مشاهده PDF
#pageRemover # Page Remover
pageRemover.title=حذف صفحات pageRemover.title=حذف صفحات
pageRemover.header=حذف صفحات PDF pageRemover.header=حذف صفحات PDF
pageRemover.pagesToDelete=صفحات برای حذف (یک لیست از اعداد صفحه جدا شده با کاما وارد کنید): pageRemover.pagesToDelete=صفحات برای حذف (یک لیست از اعداد صفحه جدا شده با کاما وارد کنید):
@@ -990,14 +978,14 @@ pageRemover.submit=حذف صفحات
pageRemover.placeholder=(مثال: ۱,۲,۶ یا ۱-۱۰,۱۵-۳۰) pageRemover.placeholder=(مثال: ۱,۲,۶ یا ۱-۱۰,۱۵-۳۰)
#rotate # Rotate
rotate.title=چرخش PDF rotate.title=چرخش PDF
rotate.header=چرخش PDF rotate.header=چرخش PDF
rotate.selectAngle=زاویه چرخش را انتخاب کنید (به مضرب‌های ۹۰ درجه): rotate.selectAngle=زاویه چرخش را انتخاب کنید (به مضرب‌های ۹۰ درجه):
rotate.submit=چرخش rotate.submit=چرخش
#split-pdfs # Split PDFs
split.title=تقسیم PDF split.title=تقسیم PDF
split.header=تقسیم PDF split.header=تقسیم PDF
split.desc.1=اعدادی که انتخاب می‌کنید شماره صفحه‌هایی هستند که می‌خواهید بر روی آنها تقسیم انجام دهید split.desc.1=اعدادی که انتخاب می‌کنید شماره صفحه‌هایی هستند که می‌خواهید بر روی آنها تقسیم انجام دهید
@@ -1026,7 +1014,7 @@ imageToPDF.selectText.4=ادغام در یک PDF واحد
imageToPDF.selectText.5=تبدیل به PDF های جداگانه imageToPDF.selectText.5=تبدیل به PDF های جداگانه
#pdfToImage # PDF to Image
pdfToImage.title=PDF به تصویر pdfToImage.title=PDF به تصویر
pdfToImage.header=PDF به تصویر pdfToImage.header=PDF به تصویر
pdfToImage.selectText=فرمت تصویر pdfToImage.selectText=فرمت تصویر
@@ -1041,7 +1029,7 @@ pdfToImage.submit=تبدیل
pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است. pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است.
#addPassword # Add Password
addPassword.title=افزودن گذرواژه addPassword.title=افزودن گذرواژه
addPassword.header=افزودن گذرواژه (رمزنگاری) addPassword.header=افزودن گذرواژه (رمزنگاری)
addPassword.selectText.1=انتخاب PDF برای رمزنگاری addPassword.selectText.1=انتخاب PDF برای رمزنگاری
@@ -1063,7 +1051,7 @@ addPassword.selectText.16=محدودیت‌های باز شدن خود سند
addPassword.submit=رمزنگاری addPassword.submit=رمزنگاری
#watermark # Watermark
watermark.title=افزودن واترمارک watermark.title=افزودن واترمارک
watermark.header=افزودن واترمارک watermark.header=افزودن واترمارک
watermark.customColor=رنگ متن سفارشی watermark.customColor=رنگ متن سفارشی
@@ -1082,7 +1070,7 @@ watermark.type.1=متن
watermark.type.2=تصویر watermark.type.2=تصویر
#Change permissions # Change Permissions
permissions.title=تغییر مجوزها permissions.title=تغییر مجوزها
permissions.header=تغییر مجوزها permissions.header=تغییر مجوزها
permissions.warning=برای اینکه این مجوزها غیرقابل تغییر باشند، توصیه می‌شود آنها را با گذرواژه از طریق صفحه افزودن گذرواژه تنظیم کنید permissions.warning=برای اینکه این مجوزها غیرقابل تغییر باشند، توصیه می‌شود آنها را با گذرواژه از طریق صفحه افزودن گذرواژه تنظیم کنید
@@ -1099,7 +1087,7 @@ permissions.selectText.10=جلوگیری از چاپ فرمت‌های مختل
permissions.submit=تغییر permissions.submit=تغییر
#remove password # Remove Password
removePassword.title=حذف گذرواژه removePassword.title=حذف گذرواژه
removePassword.header=حذف گذرواژه (رمزگشایی) removePassword.header=حذف گذرواژه (رمزگشایی)
removePassword.selectText.1=PDFی را برای رمزگشایی انتخاب کنید removePassword.selectText.1=PDFی را برای رمزگشایی انتخاب کنید
@@ -1107,7 +1095,7 @@ removePassword.selectText.2=گذرواژه
removePassword.submit=حذف removePassword.submit=حذف
#changeMetadata # Change Metadata
changeMetadata.title=عنوان: changeMetadata.title=عنوان:
changeMetadata.header=تغییر متاداده‌ها changeMetadata.header=تغییر متاداده‌ها
changeMetadata.selectText.1=لطفاً متغیرهایی که مایل به تغییر آنها هستید را ویرایش کنید changeMetadata.selectText.1=لطفاً متغیرهایی که مایل به تغییر آنها هستید را ویرایش کنید
@@ -1126,7 +1114,7 @@ changeMetadata.selectText.5=افزودن ورودی متاداده سفارشی
changeMetadata.submit=تغییر changeMetadata.submit=تغییر
#pdfToPDFA # PDF to PDF/A
pdfToPDFA.title=PDF به PDF/A pdfToPDFA.title=PDF به PDF/A
pdfToPDFA.header=PDF به PDF/A pdfToPDFA.header=PDF به PDF/A
pdfToPDFA.credit=این سرویس از qpdf برای تبدیل PDF/A استفاده می‌کند pdfToPDFA.credit=این سرویس از qpdf برای تبدیل PDF/A استفاده می‌کند
@@ -1136,7 +1124,7 @@ pdfToPDFA.outputFormat=فرمت خروجی
pdfToPDFA.pdfWithDigitalSignature=PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد. pdfToPDFA.pdfWithDigitalSignature=PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد.
#PDFToWord # PDF to Word
PDFToWord.title=PDF به ورد PDFToWord.title=PDF به ورد
PDFToWord.header=PDF به ورد PDFToWord.header=PDF به ورد
PDFToWord.selectText.1=فرمت فایل خروجی PDFToWord.selectText.1=فرمت فایل خروجی
@@ -1144,7 +1132,7 @@ PDFToWord.credit=این سرویس از LibreOffice برای تبدیل فایل
PDFToWord.submit=تبدیل PDFToWord.submit=تبدیل
#PDFToPresentation # PDF to Presentation
PDFToPresentation.title=PDF به ارائه PDFToPresentation.title=PDF به ارائه
PDFToPresentation.header=PDF به ارائه PDFToPresentation.header=PDF به ارائه
PDFToPresentation.selectText.1=فرمت فایل خروجی PDFToPresentation.selectText.1=فرمت فایل خروجی
@@ -1152,7 +1140,7 @@ PDFToPresentation.credit=این سرویس از LibreOffice برای تبدیل
PDFToPresentation.submit=تبدیل PDFToPresentation.submit=تبدیل
#PDFToText # PDF to Text
PDFToText.title=PDF به RTF (متن) PDFToText.title=PDF به RTF (متن)
PDFToText.header=PDF به RTF (متن) PDFToText.header=PDF به RTF (متن)
PDFToText.selectText.1=فرمت فایل خروجی PDFToText.selectText.1=فرمت فایل خروجی
@@ -1160,26 +1148,27 @@ PDFToText.credit=این سرویس از LibreOffice برای تبدیل فایل
PDFToText.submit=تبدیل PDFToText.submit=تبدیل
#PDFToHTML # PDF to HTML
PDFToHTML.title=PDF به HTML PDFToHTML.title=PDF به HTML
PDFToHTML.header=PDF به HTML PDFToHTML.header=PDF به HTML
PDFToHTML.credit=این سرویس از pdftohtml برای تبدیل فایل استفاده می‌کند. PDFToHTML.credit=این سرویس از pdftohtml برای تبدیل فایل استفاده می‌کند.
PDFToHTML.submit=تبدیل PDFToHTML.submit=تبدیل
#PDFToXML # PDF to XML
PDFToXML.title=PDF به XML PDFToXML.title=PDF به XML
PDFToXML.header=PDF به XML PDFToXML.header=PDF به XML
PDFToXML.credit=این سرویس از LibreOffice برای تبدیل فایل استفاده می‌کند. PDFToXML.credit=این سرویس از LibreOffice برای تبدیل فایل استفاده می‌کند.
PDFToXML.submit=تبدیل PDFToXML.submit=تبدیل
#PDFToCSV # PDF to CSV
PDFToCSV.title=PDF به CSV PDFToCSV.title=PDF به CSV
PDFToCSV.header=PDF به CSV PDFToCSV.header=PDF به CSV
PDFToCSV.prompt=صفحه‌ای که می‌خواهید جدول استخراج شود را انتخاب کنید PDFToCSV.prompt=صفحه‌ای که می‌خواهید جدول استخراج شود را انتخاب کنید
PDFToCSV.submit=استخراج PDFToCSV.submit=استخراج
#split-by-size-or-count
# Split by Size or Count
split-by-size-or-count.title=تقسیم PDF بر اساس اندازه یا تعداد split-by-size-or-count.title=تقسیم PDF بر اساس اندازه یا تعداد
split-by-size-or-count.header=تقسیم PDF بر اساس اندازه یا تعداد split-by-size-or-count.header=تقسیم PDF بر اساس اندازه یا تعداد
split-by-size-or-count.type.label=انتخاب نوع تقسیم split-by-size-or-count.type.label=انتخاب نوع تقسیم
@@ -1191,7 +1180,7 @@ split-by-size-or-count.value.placeholder=اندازه را وارد کنید (م
split-by-size-or-count.submit=ارسال split-by-size-or-count.submit=ارسال
#overlay-pdfs # Overlay PDFs
overlay-pdfs.header=ترکیب فایل‌های PDF overlay-pdfs.header=ترکیب فایل‌های PDF
overlay-pdfs.baseFile.label=انتخاب فایل پایه PDF overlay-pdfs.baseFile.label=انتخاب فایل پایه PDF
overlay-pdfs.overlayFiles.label=انتخاب فایل‌های ترکیبی PDF overlay-pdfs.overlayFiles.label=انتخاب فایل‌های ترکیبی PDF
@@ -1207,7 +1196,7 @@ overlay-pdfs.position.background=پس‌زمینه
overlay-pdfs.submit=ارسال overlay-pdfs.submit=ارسال
#split-by-sections # Split by Sections
split-by-sections.title=تقسیم PDF به بخش‌ها split-by-sections.title=تقسیم PDF به بخش‌ها
split-by-sections.header=تقسیم PDF به بخش‌ها split-by-sections.header=تقسیم PDF به بخش‌ها
split-by-sections.horizontal.label=تقسیمات افقی split-by-sections.horizontal.label=تقسیمات افقی
@@ -1218,7 +1207,7 @@ split-by-sections.submit=تقسیم PDF
split-by-sections.merge=ادغام به یک PDF split-by-sections.merge=ادغام به یک PDF
#printFile # Print File
printFile.title=چاپ فایل printFile.title=چاپ فایل
printFile.header=چاپ فایل به چاپگر printFile.header=چاپ فایل به چاپگر
printFile.selectText.1=انتخاب فایل برای چاپ printFile.selectText.1=انتخاب فایل برای چاپ
@@ -1226,7 +1215,7 @@ printFile.selectText.2=نام چاپگر را وارد کنید
printFile.submit=چاپ printFile.submit=چاپ
#licenses # Licenses
licenses.nav=مجوزها licenses.nav=مجوزها
licenses.title=مجوزهای شخص ثالث licenses.title=مجوزهای شخص ثالث
licenses.header=مجوزهای شخص ثالث licenses.header=مجوزهای شخص ثالث
@@ -1234,7 +1223,7 @@ licenses.module=ماژول
licenses.version=نسخه licenses.version=نسخه
licenses.license=مجوز licenses.license=مجوز
#survey # Survey
survey.nav=نظرسنجی survey.nav=نظرسنجی
survey.title=نظرسنجی Stirling-PDF survey.title=نظرسنجی Stirling-PDF
survey.description=Stirling-PDF هیچ ردیابی ندارد، بنابراین ما می‌خواهیم از کاربران خود بشنویم تا Stirling-PDF را بهبود دهیم! survey.description=Stirling-PDF هیچ ردیابی ندارد، بنابراین ما می‌خواهیم از کاربران خود بشنویم تا Stirling-PDF را بهبود دهیم!
@@ -1246,7 +1235,7 @@ survey.button=شرکت در نظرسنجی
survey.dontShowAgain=دیگر نشان نده survey.dontShowAgain=دیگر نشان نده
#error # Error
error.sorry=متأسفیم برای مشکل موجود! error.sorry=متأسفیم برای مشکل موجود!
error.needHelp=نیاز به کمک / یافتن مشکلی؟ error.needHelp=نیاز به کمک / یافتن مشکلی؟
error.contactTip=اگر هنوز مشکلی دارید، دریغ نکنید که با ما تماس بگیرید. می‌توانید یک تیکت در صفحه GitHub ما ارسال کنید یا از طریق Discord با ما تماس بگیرید: error.contactTip=اگر هنوز مشکلی دارید، دریغ نکنید که با ما تماس بگیرید. می‌توانید یک تیکت در صفحه GitHub ما ارسال کنید یا از طریق Discord با ما تماس بگیرید:
@@ -1260,13 +1249,14 @@ error.githubSubmit=GitHub - ارسال تیکت
error.discordSubmit=Discord - ارسال پست پشتیبانی error.discordSubmit=Discord - ارسال پست پشتیبانی
#remove-image # Remove Image
removeImage.title=حذف تصویر removeImage.title=حذف تصویر
removeImage.header=حذف تصویر removeImage.header=حذف تصویر
removeImage.removeImage=حذف تصویر removeImage.removeImage=حذف تصویر
removeImage.submit=حذف تصویر removeImage.submit=حذف تصویر
# Split by Chapters
splitByChapters.title=تقسیم PDF بر اساس فصل‌ها splitByChapters.title=تقسیم PDF بر اساس فصل‌ها
splitByChapters.header=تقسیم PDF بر اساس فصل‌ها splitByChapters.header=تقسیم PDF بر اساس فصل‌ها
splitByChapters.bookmarkLevel=سطح نشانک splitByChapters.bookmarkLevel=سطح نشانک
@@ -1278,20 +1268,20 @@ splitByChapters.desc.3=شامل متادیتا: اگر انتخاب شده، م
splitByChapters.desc.4=اجازه‌ی تکرار: اگر انتخاب شده باشد، اجازه می‌دهد نشانک‌های متعدد در یک صفحه، فایل‌های PDF جداگانه ایجاد کنند. splitByChapters.desc.4=اجازه‌ی تکرار: اگر انتخاب شده باشد، اجازه می‌دهد نشانک‌های متعدد در یک صفحه، فایل‌های PDF جداگانه ایجاد کنند.
splitByChapters.submit=تقسیم PDF splitByChapters.submit=تقسیم PDF
#File Chooser # File Chooser
fileChooser.click=کلیک کنید fileChooser.click=کلیک کنید
fileChooser.or=یا fileChooser.or=یا
fileChooser.dragAndDrop=بکشید و رها کنید fileChooser.dragAndDrop=بکشید و رها کنید
fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید
#release notes # Release Notes
releases.footer=نسخه‌ها releases.footer=نسخه‌ها
releases.title=یادداشت‌های نسخه releases.title=یادداشت‌های نسخه
releases.header=یادداشت‌های نسخه releases.header=یادداشت‌های نسخه
releases.current.version=نسخه فعلی releases.current.version=نسخه فعلی
releases.note=یادداشت‌های نسخه فقط به زبان انگلیسی موجود است releases.note=یادداشت‌های نسخه فقط به زبان انگلیسی موجود است
#Validate Signature # Validate Signature
validateSignature.title=اعتبارسنجی امضاهای PDF validateSignature.title=اعتبارسنجی امضاهای PDF
validateSignature.header=اعتبارسنجی امضای دیجیتال validateSignature.header=اعتبارسنجی امضای دیجیتال
validateSignature.selectPDF=فایل PDF امضاشده را انتخاب کنید validateSignature.selectPDF=فایل PDF امضاشده را انتخاب کنید

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) sélectionnées
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles ! multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles !

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Pagina(e) selezionata(e)
multiTool.undo=Annulla multiTool.undo=Annulla
multiTool.redo=Rifai multiTool.redo=Rifai
#decrypt
decrypt.passwordPrompt=Questo file è protetto da password. Inserisci la password:
decrypt.cancelled=Operazione annullata per il PDF: {0}
decrypt.noPassword=Nessuna password fornita per il PDF crittografato: {0}
decrypt.invalidPassword=Riprova con la password corretta.
decrypt.invalidPasswordHeader=Password errata o crittografia non supportata per il PDF: {0}
decrypt.unexpectedError=Si è verificato un errore durante l'elaborazione del file. Riprova..
decrypt.serverError=Errore del server durante la decrittazione: {0}
decrypt.success=File decrittografato con successo.
#multiTool-advert #multiTool-advert
multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive! multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive!

View File

@@ -56,12 +56,12 @@ userNotFoundMessage=ユーザーが見つかりません。
incorrectPasswordMessage=現在のパスワードが正しくありません。 incorrectPasswordMessage=現在のパスワードが正しくありません。
usernameExistsMessage=新しいユーザー名はすでに存在します。 usernameExistsMessage=新しいユーザー名はすでに存在します。
invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。 invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
invalidPasswordMessage=パスワードは空にすることはできません。また、先頭・末尾にスペースを含めることもできません。 invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=新しいパスワードと新しいパスワードの確認は一致する必要があります。 confirmPasswordErrorMessage=新しいパスワードと新しいパスワードの確認は一致する必要があります。
deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。 deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。 deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
disabledCurrentUserMessage=現在のユーザーを無効にすることはできません disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。 downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
userAlreadyExistsOAuthMessage=ユーザーは既にOAuth2ユーザーとして存在します。 userAlreadyExistsOAuthMessage=ユーザーは既にOAuth2ユーザーとして存在します。
userAlreadyExistsWebMessage=ユーザーは既にWebユーザーとして存在します。 userAlreadyExistsWebMessage=ユーザーは既にWebユーザーとして存在します。
@@ -76,12 +76,12 @@ donate=寄付する
color= color=
sponsor=スポンサー sponsor=スポンサー
info=Info info=Info
pro=pro pro=Pro
page=ページ page=Page
pages=ページ pages=Pages
loading=読込中... loading=Loading...
addToDoc=ドキュメントに追加 addToDoc=Add to Document
reset=リセット reset=Reset
legal.privacy=プライバシーポリシー legal.privacy=プライバシーポリシー
legal.terms=利用規約 legal.terms=利用規約
@@ -92,7 +92,7 @@ legal.impressum=著作権利者情報
############### ###############
# Pipeline # # Pipeline #
############### ###############
pipeline.header=パイプラインメニュー (Beta) pipeline.header=パイプラインメニュー (Alpha)
pipeline.uploadButton=カスタムのアップロード pipeline.uploadButton=カスタムのアップロード
pipeline.configureButton=設定 pipeline.configureButton=設定
pipeline.defaultOption=カスタム pipeline.defaultOption=カスタム
@@ -117,21 +117,21 @@ pipelineOptions.validateButton=検証
######################## ########################
# ENTERPRISE EDITION # # ENTERPRISE EDITION #
######################## ########################
enterpriseEdition.button=Proにアップグレード enterpriseEdition.button=Upgrade to Pro
enterpriseEdition.warning=この機能はProユーザーのみが利用できます。 enterpriseEdition.warning=This feature is only available to Pro users.
enterpriseEdition.yamlAdvert=Stirling PDF Proは、YAML構成ファイルやその他のSSO機能をサポートしています。 enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
enterpriseEdition.ssoAdvert=より多くのユーザー管理機能をお探しですか? Stirling PDF Proをご覧ください enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
################# #################
# Analytics # # Analytics #
################# #################
analytics.title=Stirling PDFをもっと良くしたいですか analytics.title=Do you want make Stirling PDF better?
analytics.paragraph1=Stirling PDFでは、製品の改善に役立つ分析機能をオプトインしています。個人情報やファイルの内容を追跡することはありません。 analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
analytics.paragraph2=Stirling-PDFの成長を支援しユーザーをより深く理解できるように分析を有効にすることを検討してください。 analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
analytics.enable=分析を有効にする analytics.enable=Enable analytics
analytics.disable=分析を無効にする analytics.disable=Disable analytics
analytics.settings=config/settings.ymlファイルでアナリティクスの設定を変更できます。 analytics.settings=You can change the settings for analytics in the config/settings.yml file
############# #############
# NAVBAR # # NAVBAR #
@@ -142,14 +142,14 @@ navbar.language=言語
navbar.settings=設定 navbar.settings=設定
navbar.allTools=ツール navbar.allTools=ツール
navbar.multiTool=マルチツール navbar.multiTool=マルチツール
navbar.search=検索 navbar.search=Search
navbar.sections.organize=整理 navbar.sections.organize=整理
navbar.sections.convertTo=PDFへ変換 navbar.sections.convertTo=PDFへ変換
navbar.sections.convertFrom=PDFから変換 navbar.sections.convertFrom=PDFから変換
navbar.sections.security=署名とセキュリティ navbar.sections.security=署名とセキュリティ
navbar.sections.advance=アドバンスド navbar.sections.advance=アドバンスド
navbar.sections.edit=閲覧と編集 navbar.sections.edit=閲覧と編集
navbar.sections.popular=人気 navbar.sections.popular=Popular
############# #############
# SETTINGS # # SETTINGS #
@@ -208,7 +208,7 @@ adminUserSettings.user=ユーザー
adminUserSettings.addUser=新しいユーザを追加 adminUserSettings.addUser=新しいユーザを追加
adminUserSettings.deleteUser=ユーザの削除 adminUserSettings.deleteUser=ユーザの削除
adminUserSettings.confirmDeleteUser=ユーザを本当に削除しますか? adminUserSettings.confirmDeleteUser=ユーザを本当に削除しますか?
adminUserSettings.confirmChangeUserStatus=ユーザーを無効/有効にする必要がありますか? adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。 adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
adminUserSettings.roles=役割 adminUserSettings.roles=役割
adminUserSettings.role=役割 adminUserSettings.role=役割
@@ -247,8 +247,8 @@ database.fileNotFound=ファイルが見つかりません
database.fileNullOrEmpty=ファイルは null または空であってはなりません database.fileNullOrEmpty=ファイルは null または空であってはなりません
database.failedImportFile=ファイルのインポートに失敗 database.failedImportFile=ファイルのインポートに失敗
session.expired=セッションが期限切れです。ページを更新してもう一度お試しください。 session.expired=Your session has expired. Please refresh the page and try again.
session.refreshPage=ページを更新 session.refreshPage=Refresh Page
############# #############
# HOME-PAGE # # HOME-PAGE #
@@ -488,52 +488,52 @@ overlay-pdfs.tags=Overlay
home.split-by-sections.title=PDFをセクションで分割 home.split-by-sections.title=PDFをセクションで分割
home.split-by-sections.desc=PDFの各ページを縦横に分割します。 home.split-by-sections.desc=PDFの各ページを縦横に分割します。
split-by-sections.tags=Section Split, Divide, Customize,Customise split-by-sections.tags=Section Split, Divide, Customize
home.AddStampRequest.title=PDFにスタンプを追加 home.AddStampRequest.title=PDFにスタンプを追加
home.AddStampRequest.desc=設定した位置にテキストや画像のスタンプを追加できます home.AddStampRequest.desc=設定した位置にテキストや画像のスタンプを追加できます
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize,Customise AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
home.PDFToBook.title=PDFを書籍に変換 home.PDFToBook.title=PDFを書籍に変換
home.PDFToBook.desc=calibreを使用してPDFを書籍/コミック形式に変換します home.PDFToBook.desc=calibreを使用してPDFを書籍/コミック形式に変換します
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.BookToPDF.title=PDFを書籍に変換 home.BookToPDF.title=PDFを書籍に変換
home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.removeImagePdf.title=画像の削除 home.removeImagePdf.title=画像の削除
home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします
removeImagePdf.tags=Remove Image,Page operations,Back end,server side removeImagePdf.tags=Remove Image,Page operations,Back end,server side
home.splitPdfByChapters.title=PDFをチャプターごとに分割 home.splitPdfByChapters.title=Split PDF by Chapters
home.splitPdfByChapters.desc=チャプターの構造に基づいてPDFを複数のファイルに分割します home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
splitPdfByChapters.tags=split,chapters,bookmarks,organize splitPdfByChapters.tags=split,chapters,bookmarks,organize
home.validateSignature.title=PDF署名の検証 home.validateSignature.title=Validate PDF Signature
home.validateSignature.desc=PDF文書のデジタル署名と証明書を検証します home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
#replace-invert-color #replace-invert-color
replace-color.title=色の置換・反転 replace-color.title=Replace-Invert-Color
replace-color.header=PDFの色の置換・反転 replace-color.header=Replace-Invert Color PDF
home.replaceColorPdf.title=色の置換と反転 home.replaceColorPdf.title=Replace and Invert Color
home.replaceColorPdf.desc=PDF内のテキストと背景の色を置き換え、PDFのフルカラーを反転してファイルサイズを縮小します。 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 replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
replace-color.selectText.1=色の置換または反転オプション replace-color.selectText.1=Replace or Invert color Options
replace-color.selectText.2=デフォルト(デフォルトの高コントラスト色) replace-color.selectText.2=Default(Default high contrast colors)
replace-color.selectText.3=カスタム(カスタマイズされた色) replace-color.selectText.3=Custom(Customized colors)
replace-color.selectText.4=フル反転(すべての色を反転) replace-color.selectText.4=Full-Invert(Invert all colors)
replace-color.selectText.5=高コントラストカラーオプション replace-color.selectText.5=High contrast color options
replace-color.selectText.6=黒背景に白文字 replace-color.selectText.6=white text on black background
replace-color.selectText.7=白背景に黒文字 replace-color.selectText.7=Black text on white background
replace-color.selectText.8=黒背景に黄色文字 replace-color.selectText.8=Yellow text on black background
replace-color.selectText.9=黒背景に緑文字 replace-color.selectText.9=Green text on black background
replace-color.selectText.10=テキストの色を選択 replace-color.selectText.10=Choose text Color
replace-color.selectText.11=背景色を選択 replace-color.selectText.11=Choose background Color
replace-color.submit=置換 replace-color.submit=Replace
@@ -560,9 +560,9 @@ login.oauth2AccessDenied=アクセス拒否
login.oauth2InvalidTokenResponse=無効なトークン応答 login.oauth2InvalidTokenResponse=無効なトークン応答
login.oauth2InvalidIdToken=無効なIDトークン login.oauth2InvalidIdToken=無効なIDトークン
login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。 login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
login.alreadyLoggedIn=すでにログインしています login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=デバイスからログアウトしてもう一度お試しください。 login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
login.toManySessions=アクティブなセッションが多すぎます login.toManySessions=You have too many active sessions
#auto-redact #auto-redact
autoRedact.title=自動塗りつぶし autoRedact.title=自動塗りつぶし
@@ -578,8 +578,8 @@ autoRedact.submitButton=送信
#showJS #showJS
showJS.title=Javascriptを表示 showJS.title=JavaScriptを表示
showJS.header=Javascriptを表示 showJS.header=JavaScriptを表示
showJS.downloadJS=Javascriptをダウンロード showJS.downloadJS=Javascriptをダウンロード
showJS.submit=表示 showJS.submit=表示
@@ -757,7 +757,7 @@ certSign.showSig=署名を表示
certSign.reason=理由 certSign.reason=理由
certSign.location=場所 certSign.location=場所
certSign.name=名前 certSign.name=名前
certSign.showLogo=ロゴを表示 certSign.showLogo=Show Logo
certSign.submit=PDFに署名 certSign.submit=PDFに署名
@@ -792,9 +792,9 @@ compare.highlightColor.2=ハイライトカラー 2:
compare.document.1=ドキュメント 1 compare.document.1=ドキュメント 1
compare.document.2=ドキュメント 2 compare.document.2=ドキュメント 2
compare.submit=比較 compare.submit=比較
compare.complex.message=提供された文書の一方または両方が大きなファイルであるため、比較の精度が低下する可能性があります。 compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
compare.large.file.message=提供された文書の1つまたは両方が大きすぎて処理できません compare.large.file.message=One or Both of the provided documents are too large to process
compare.no.text.message=選択したPDFの1つまたは両方にテキストコンテンツがありません。比較するには、テキストを含むPDFを選択してください。 compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
#BookToPDF #BookToPDF
BookToPDF.title=書籍やコミックをPDFに変換 BookToPDF.title=書籍やコミックをPDFに変換
@@ -803,8 +803,8 @@ BookToPDF.credit=calibreを使用
BookToPDF.submit=変換 BookToPDF.submit=変換
#PDFToBook #PDFToBook
PDFToBook.title=PDFを書籍に変換 PDFToBook.title=書籍をPDFに変換
PDFToBook.header=PDFを書籍に変換 PDFToBook.header=書籍をPDFに変換
PDFToBook.selectText.1=フォーマット PDFToBook.selectText.1=フォーマット
PDFToBook.credit=calibreを使用 PDFToBook.credit=calibreを使用
PDFToBook.submit=変換 PDFToBook.submit=変換
@@ -817,17 +817,17 @@ sign.draw=署名を書く
sign.text=テキスト入力 sign.text=テキスト入力
sign.clear=クリア sign.clear=クリア
sign.add=追加 sign.add=追加
sign.saved=保存された署名 sign.saved=Saved Signatures
sign.save=署名を保存 sign.save=Save Signature
sign.personalSigs=個人署名 sign.personalSigs=Personal Signatures
sign.sharedSigs=共有署名 sign.sharedSigs=Shared Signatures
sign.noSavedSigs=保存された署名が見つかりません sign.noSavedSigs=No saved signatures found
sign.addToAll=すべてのページに追加 sign.addToAll=Add to all pages
sign.delete=削除 sign.delete=Delete
sign.first=最初のページ sign.first=First page
sign.last=最後のページ sign.last=Last page
sign.next=次のページ sign.next=Next page
sign.previous=前のページ sign.previous=Previous page
#repair #repair
repair.title=修復 repair.title=修復
@@ -944,39 +944,29 @@ pdfOrganiser.placeholder=(例:1,3,2または4-8,2,10-12または2n-1)
multiTool.title=PDFマルチツール multiTool.title=PDFマルチツール
multiTool.header=PDFマルチツール multiTool.header=PDFマルチツール
multiTool.uploadPrompts=ファイル名 multiTool.uploadPrompts=ファイル名
multiTool.selectAll=すべて選択 multiTool.selectAll=Select All
multiTool.deselectAll=選択を解除 multiTool.deselectAll=Deselect All
multiTool.selectPages=ページ選択 multiTool.selectPages=Page Select
multiTool.selectedPages=選択したページ multiTool.selectedPages=Selected Pages
multiTool.page=ページ multiTool.page=Page
multiTool.deleteSelected=選択項目を削除 multiTool.deleteSelected=Delete Selected
multiTool.downloadAll=エクスポート multiTool.downloadAll=Export
multiTool.downloadSelected=選択項目をエクスポート multiTool.downloadSelected=Export Selected
multiTool.insertPageBreak=改ページを挿入 multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=ファイルを追加 multiTool.addFile=Add File
multiTool.rotateLeft=左回転 multiTool.rotateLeft=Rotate Left
multiTool.rotateRight=右回転 multiTool.rotateRight=Rotate Right
multiTool.split=分割 multiTool.split=Split
multiTool.moveLeft=左に移動 multiTool.moveLeft=Move Left
multiTool.moveRight=右に移動 multiTool.moveRight=Move Right
multiTool.delete=削除 multiTool.delete=Delete
multiTool.dragDropMessage=選択されたページ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=元に戻す multiTool.undo=Undo
multiTool.redo=やり直す multiTool.redo=Redo
#decrypt
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
multiTool-advert.message=この機能は、<a href="{0}">マルチツール</a>でもご利用いただけます。強化されたページごとのUIと追加機能についてはこちらをご覧ください。 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!
#view pdf #view pdf
viewPdf.title=PDFを表示 viewPdf.title=PDFを表示
@@ -1132,8 +1122,8 @@ pdfToPDFA.header=PDFをPDF/Aに変換
pdfToPDFA.credit=本サービスはPDF/Aの変換にqpdfを使用しています。 pdfToPDFA.credit=本サービスはPDF/Aの変換にqpdfを使用しています。
pdfToPDFA.submit=変換 pdfToPDFA.submit=変換
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
pdfToPDFA.outputFormat=出力形式 pdfToPDFA.outputFormat=Output format
pdfToPDFA.pdfWithDigitalSignature=PDFにはデジタル署名が含まれています。これは次の手順で削除されます。 pdfToPDFA.pdfWithDigitalSignature=PDF にはデジタル署名が含まれています。これは次の手順で削除されます。
#PDFToWord #PDFToWord
@@ -1238,8 +1228,8 @@ licenses.license=ライセンス
survey.nav=アンケート survey.nav=アンケート
survey.title=Stirling-PDFのアンケート survey.title=Stirling-PDFのアンケート
survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください
survey.changes=Stirling-PDFは前回の調査から変更されました。詳細についてはこちらのブログ投稿をご覧ください。 survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
survey.changes2=これらの変更により私たちは有償のビジネスサポートと資金援助を受けています survey.changes2=With these changes we are getting paid business support and funding
survey.please=アンケートにご協力ください! survey.please=アンケートにご協力ください!
survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。) survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。)
survey.button=アンケートに答える survey.button=アンケートに答える
@@ -1267,61 +1257,61 @@ removeImage.removeImage=画像の削除
removeImage.submit=画像を削除 removeImage.submit=画像を削除
splitByChapters.title=PDFをチャプターごとに分割 splitByChapters.title=Split PDF by Chapters
splitByChapters.header=PDFをチャプターごとに分割 splitByChapters.header=Split PDF by Chapters
splitByChapters.bookmarkLevel=ブックマークレベル splitByChapters.bookmarkLevel=Bookmark Level
splitByChapters.includeMetadata=メタデータを含める splitByChapters.includeMetadata=Include Metadata
splitByChapters.allowDuplicates=重複を許可する splitByChapters.allowDuplicates=Allow Duplicates
splitByChapters.desc.1=このツールは、チャプター構造に基づいてPDFファイルを複数のPDFに分割します。 splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
splitByChapters.desc.2=ブックマークレベル:分割に使用するブックマークのレベルを選択します最上位レベルの場合は0、第2レベルの場合は1など 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=メタデータを含める:チェックすると、元のPDFのメタデータが各分割PDFに含まれます。 splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=重複を許可:チェックすると同じページ上の複数のブックマークから個別のPDFを作成できます。 splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=PDFを分割 splitByChapters.submit=Split PDF
#File Chooser #File Chooser
fileChooser.click=クリック fileChooser.click=Click
fileChooser.or=または fileChooser.or=or
fileChooser.dragAndDrop=ドラッグ&ドロップ fileChooser.dragAndDrop=Drag & Drop
fileChooser.hoveredDragAndDrop=ファイルをここにドラッグ&ドロップ fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes
releases.footer=リリース releases.footer=Releases
releases.title=リリースノート releases.title=Release Notes
releases.header=リリースノート releases.header=Release Notes
releases.current.version=現在のリリース releases.current.version=Current Release
releases.note=リリースノートは英語でのみで提供されています releases.note=Release notes are only available in English
#Validate Signature #Validate Signature
validateSignature.title=PDF署名の検証 validateSignature.title=Validate PDF Signatures
validateSignature.header=デジタル署名の検証 validateSignature.header=Validate Digital Signatures
validateSignature.selectPDF=署名済みPDFファイルを選択 validateSignature.selectPDF=Select signed PDF file
validateSignature.submit=署名の検証 validateSignature.submit=Validate Signatures
validateSignature.results=検証結果 validateSignature.results=Validation Results
validateSignature.status=状態 validateSignature.status=Status
validateSignature.signer=署名者 validateSignature.signer=Signer
validateSignature.date=日付 validateSignature.date=Date
validateSignature.reason=理由 validateSignature.reason=Reason
validateSignature.location=場所 validateSignature.location=Location
validateSignature.noSignatures=この文書にはデジタル署名が見つかりません validateSignature.noSignatures=No digital signatures found in this document
validateSignature.status.valid=有効 validateSignature.status.valid=Valid
validateSignature.status.invalid=無効 validateSignature.status.invalid=Invalid
validateSignature.chain.invalid=証明書チェーンの検証に失敗しました - 署名者の身元を確認できません validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
validateSignature.trust.invalid=証明書が信頼ストアにありません - ソースを検証できません validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
validateSignature.cert.expired=証明書の有効期限が切れています validateSignature.cert.expired=Certificate has expired
validateSignature.cert.revoked=証明書は取り消されました validateSignature.cert.revoked=Certificate has been revoked
validateSignature.signature.info=署名情報 validateSignature.signature.info=Signature Information
validateSignature.signature=署名 validateSignature.signature=Signature
validateSignature.signature.mathValid=署名は数学的には有効ですが: validateSignature.signature.mathValid=Signature is mathematically valid BUT:
validateSignature.selectCustomCert=カスタム証明書ファイル X.509 (オプション) validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
validateSignature.cert.info=証明書の詳細 validateSignature.cert.info=Certificate Details
validateSignature.cert.issuer=発行者 validateSignature.cert.issuer=Issuer
validateSignature.cert.subject=主題 validateSignature.cert.subject=Subject
validateSignature.cert.serialNumber=シリアルナンバー validateSignature.cert.serialNumber=Serial Number
validateSignature.cert.validFrom=有効開始日 validateSignature.cert.validFrom=Valid From
validateSignature.cert.validUntil=有効期限 validateSignature.cert.validUntil=Valid Until
validateSignature.cert.algorithm=アルゴリズム validateSignature.cert.algorithm=Algorithm
validateSignature.cert.keySize=キーサイズ validateSignature.cert.keySize=Key Size
validateSignature.cert.version=バージョン validateSignature.cert.version=Version
validateSignature.cert.keyUsage=キーの使用法 validateSignature.cert.keyUsage=Key Usage
validateSignature.cert.selfSigned=自己署名 validateSignature.cert.selfSigned=Self-Signed
validateSignature.cert.bits=ビット validateSignature.cert.bits=bits

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

File diff suppressed because it is too large Load Diff

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

File diff suppressed because it is too large Load Diff

View File

@@ -965,16 +965,6 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=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.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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!

View File

@@ -13,7 +13,7 @@
security: security:
enableLogin: false # set to 'true' to enable login enableLogin: false # set to 'true' to enable login
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production) csrfDisabled: true # set to 'true' to disable CSRF protection (not recommended for production)
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1 loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2) loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2)
@@ -102,8 +102,7 @@ metrics:
AutomaticallyGenerated: AutomaticallyGenerated:
key: example key: example
UUID: example UUID: example
appVersion: 0.35.0
processExecutor: processExecutor:
sessionLimit: # Process executor instances limits sessionLimit: # Process executor instances limits
libreOfficeSessionLimit: 1 libreOfficeSessionLimit: 1

View File

@@ -172,13 +172,6 @@
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{
"moduleName": "com.google.code.gson:gson",
"moduleUrl": "https://github.com/google/gson",
"moduleVersion": "2.11.0",
"moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{ {
"moduleName": "com.google.errorprone:error_prone_annotations", "moduleName": "com.google.errorprone:error_prone_annotations",
"moduleUrl": "https://errorprone.info/error_prone_annotations", "moduleUrl": "https://errorprone.info/error_prone_annotations",
@@ -598,34 +591,6 @@
"moduleLicense": "GPL2 w/ CPE", "moduleLicense": "GPL2 w/ CPE",
"moduleLicenseUrl": "https://oss.oracle.com/licenses/CDDL+GPL-1.1" "moduleLicenseUrl": "https://oss.oracle.com/licenses/CDDL+GPL-1.1"
}, },
{
"moduleName": "me.friwi:gluegen-rt",
"moduleUrl": "http://jogamp.org/gluegen/www/",
"moduleVersion": "v2.4.0-rc-20210111",
"moduleLicense": "BSD-4 License",
"moduleLicenseUrl": "http://www.spdx.org/licenses/BSD-4-Clause"
},
{
"moduleName": "me.friwi:jcef-api",
"moduleUrl": "https://bitbucket.org/chromiumembedded/java-cef/",
"moduleVersion": "jcef-99c2f7a+cef-127.3.1+g6cbb30e+chromium-127.0.6533.100",
"moduleLicense": "BSD License",
"moduleLicenseUrl": "https://bitbucket.org/chromiumembedded/java-cef/src/master/LICENSE.txt"
},
{
"moduleName": "me.friwi:jcefmaven",
"moduleUrl": "https://github.com/jcefmaven/jcefmaven/",
"moduleVersion": "127.3.1",
"moduleLicense": "Apache-2.0 License",
"moduleLicenseUrl": "https://github.com/jcefmaven/jcefmaven/blob/master/LICENSE"
},
{
"moduleName": "me.friwi:jogl-all",
"moduleUrl": "http://jogamp.org/jogl/www/",
"moduleVersion": "v2.4.0-rc-20210111",
"moduleLicense": "Ubuntu Font Licence 1.0",
"moduleLicenseUrl": "http://font.ubuntu.com/ufl/ubuntu-font-licence-1.0.txt"
},
{ {
"moduleName": "net.bytebuddy:byte-buddy", "moduleName": "net.bytebuddy:byte-buddy",
"moduleVersion": "1.15.10", "moduleVersion": "1.15.10",
@@ -666,13 +631,6 @@
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{
"moduleName": "org.apache.commons:commons-compress",
"moduleUrl": "https://commons.apache.org/proper/commons-compress/",
"moduleVersion": "1.21",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{ {
"moduleName": "org.apache.commons:commons-csv", "moduleName": "org.apache.commons:commons-csv",
"moduleUrl": "https://commons.apache.org/proper/commons-csv/", "moduleUrl": "https://commons.apache.org/proper/commons-csv/",
@@ -1121,30 +1079,6 @@
"moduleLicense": "Eclipse Public License, Version 2.0", "moduleLicense": "Eclipse Public License, Version 2.0",
"moduleLicenseUrl": "https://github.com/locationtech/jts/blob/master/LICENSE_EPLv2.txt" "moduleLicenseUrl": "https://github.com/locationtech/jts/blob/master/LICENSE_EPLv2.txt"
}, },
{
"moduleName": "org.openjfx:javafx-base",
"moduleVersion": "21",
"moduleLicense": "GPLv2+CE",
"moduleLicenseUrl": "https://openjdk.java.net/legal/gplv2+ce.html"
},
{
"moduleName": "org.openjfx:javafx-controls",
"moduleVersion": "21",
"moduleLicense": "GPLv2+CE",
"moduleLicenseUrl": "https://openjdk.java.net/legal/gplv2+ce.html"
},
{
"moduleName": "org.openjfx:javafx-graphics",
"moduleVersion": "21",
"moduleLicense": "GPLv2+CE",
"moduleLicenseUrl": "https://openjdk.java.net/legal/gplv2+ce.html"
},
{
"moduleName": "org.openjfx:javafx-swing",
"moduleVersion": "21",
"moduleLicense": "GPLv2+CE",
"moduleLicenseUrl": "https://openjdk.java.net/legal/gplv2+ce.html"
},
{ {
"moduleName": "org.opensaml:opensaml-core", "moduleName": "org.opensaml:opensaml-core",
"moduleVersion": "4.3.2", "moduleVersion": "4.3.2",
@@ -1545,7 +1479,7 @@
}, },
{ {
"moduleName": "org.thymeleaf.extras:thymeleaf-extras-springsecurity5", "moduleName": "org.thymeleaf.extras:thymeleaf-extras-springsecurity5",
"moduleVersion": "3.1.3.RELEASE", "moduleVersion": "3.1.2.RELEASE",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@@ -1557,7 +1491,7 @@
}, },
{ {
"moduleName": "org.thymeleaf:thymeleaf-spring5", "moduleName": "org.thymeleaf:thymeleaf-spring5",
"moduleVersion": "3.1.3.RELEASE", "moduleVersion": "3.1.2.RELEASE",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },

View File

@@ -65,7 +65,6 @@
overflow: hidden; overflow: hidden;
margin: -20px; margin: -20px;
padding: 20px; padding: 20px;
box-sizing:content-box;
} }
.feature-group-container.animated-group { .feature-group-container.animated-group {

View File

@@ -1,131 +0,0 @@
export class DecryptFile {
async decryptFile(file, requiresPassword) {
try {
async function getCsrfToken() {
const cookieValue = document.cookie
.split('; ')
.find((row) => row.startsWith('XSRF-TOKEN='))
?.split('=')[1];
if (cookieValue) {
return cookieValue;
}
const csrfElement = document.querySelector('input[name="_csrf"]');
return csrfElement ? csrfElement.value : null;
}
const csrfToken = await getCsrfToken();
const formData = new FormData();
formData.append('fileInput', file);
if (requiresPassword) {
const password = prompt(`${window.decrypt.passwordPrompt}`);
if (password === null) {
// User cancelled
console.error(`Password prompt cancelled for PDF: ${file.name}`);
return null; // No file to return
}
if (!password) {
// No password provided
console.error(`No password provided for encrypted PDF: ${file.name}`);
this.showErrorBanner(
`${window.decrypt.noPassword.replace('{0}', file.name)}`,
'',
`${window.decrypt.unexpectedError}`
);
return null; // No file to return
}
formData.append('password', password);
}
// Send decryption request
const response = await fetch('/api/v1/security/remove-password', {
method: 'POST',
body: formData,
headers: csrfToken ? {'X-XSRF-TOKEN': csrfToken} : undefined,
});
if (response.ok) {
const decryptedBlob = await response.blob();
this.removeErrorBanner();
return new File([decryptedBlob], file.name, {type: 'application/pdf'});
} else {
const errorText = await response.text();
console.error(`${window.decrypt.invalidPassword} ${errorText}`);
this.showErrorBanner(
`${window.decrypt.invalidPassword}`,
errorText,
`${window.decrypt.invalidPasswordHeader.replace('{0}', file.name)}`
);
return null; // No file to return
}
} catch (error) {
// Handle network or unexpected errors
console.error(`Failed to decrypt PDF: ${file.name}`, error);
this.showErrorBanner(
`${window.decrypt.unexpectedError.replace('{0}', file.name)}`,
`${error.message || window.decrypt.unexpectedError}`,
error
);
return null; // No file to return
}
}
async checkFileEncrypted(file) {
try {
if (file.type !== 'application/pdf') {
return {isEncrypted: false, requiresPassword: false};
}
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
const arrayBuffer = await file.arrayBuffer();
const arrayBufferForPdfLib = arrayBuffer.slice(0);
const loadingTask = pdfjsLib.getDocument({
data: arrayBuffer,
});
await loadingTask.promise;
try {
//Uses PDFLib.PDFDocument to check if unpassworded but encrypted
const pdfDoc = await PDFLib.PDFDocument.load(arrayBufferForPdfLib);
return {isEncrypted: false, requiresPassword: false};
} catch (error) {
if (error.message.includes('Input document to `PDFDocument.load` is encrypted')) {
return {isEncrypted: true, requiresPassword: false};
}
console.error('Error checking encryption:', error);
throw new Error('Failed to determine if the file is encrypted.');
}
} catch (error) {
if (error.name === 'PasswordException') {
if (error.code === pdfjsLib.PasswordResponses.NEED_PASSWORD) {
return {isEncrypted: true, requiresPassword: true};
} else if (error.code === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
return {isEncrypted: true, requiresPassword: false};
}
}
console.error('Error checking encryption:', error);
throw new Error('Failed to determine if the file is encrypted.');
}
}
showErrorBanner(message, stackTrace, error) {
const errorContainer = document.getElementById('errorContainer');
errorContainer.style.display = 'block'; // Display the banner
errorContainer.querySelector('.alert-heading').textContent = error;
errorContainer.querySelector('p').textContent = message;
document.querySelector('#traceContent').textContent = stackTrace;
}
removeErrorBanner() {
const errorContainer = document.getElementById('errorContainer');
errorContainer.style.display = 'none'; // Hide the banner
errorContainer.querySelector('.alert-heading').textContent = '';
errorContainer.querySelector('p').textContent = '';
document.querySelector('#traceContent').textContent = '';
}
}

View File

@@ -1,27 +0,0 @@
document.getElementById('download-pdf').addEventListener('click', async () => {
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
let decryptedFile = modifiedPdf;
let isEncrypted = false;
let requiresPassword = false;
await this.decryptFile
.checkFileEncrypted(decryptedFile)
.then((result) => {
isEncrypted = result.isEncrypted;
requiresPassword = result.requiresPassword;
})
.catch((error) => {
console.error(error);
});
if (decryptedFile.type === 'application/pdf' && isEncrypted) {
decryptedFile = await this.decryptFile.decryptFile(decryptedFile, requiresPassword);
if (!decryptedFile) {
throw new Error('File decryption failed.');
}
}
const modifiedPdfBytes = await modifiedPdf.save();
const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = originalFileName + '_signed.pdf';
link.click();
});

View File

@@ -42,7 +42,7 @@
event.preventDefault(); event.preventDefault();
firstErrorOccurred = false; firstErrorOccurred = false;
const url = this.action; const url = this.action;
let files = $('#fileInput-input')[0].files; const files = $('#fileInput-input')[0].files;
const formData = new FormData(this); const formData = new FormData(this);
const submitButton = document.getElementById('submitBtn'); const submitButton = document.getElementById('submitBtn');
const showGameBtn = document.getElementById('show-game-btn'); const showGameBtn = document.getElementById('show-game-btn');
@@ -71,16 +71,6 @@
}, 5000); }, 5000);
try { try {
if (!url.includes('remove-password')) {
// Check if any PDF files are encrypted and handle decryption if necessary
const decryptedFiles = await checkAndDecryptFiles(url, files);
files = decryptedFiles;
// Append decrypted files to formData
decryptedFiles.forEach((file, index) => {
formData.set(`fileInput`, file);
});
}
submitButton.textContent = 'Processing...'; submitButton.textContent = 'Processing...';
submitButton.disabled = true; submitButton.disabled = true;
@@ -143,98 +133,6 @@
} }
} }
async function checkAndDecryptFiles(url, files) {
const decryptedFiles = [];
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
// Extract the base URL
const baseUrl = new URL(url);
let removePasswordUrl = `${baseUrl.origin}`;
// Check if there's a path before /api/
const apiIndex = baseUrl.pathname.indexOf('/api/');
if (apiIndex > 0) {
removePasswordUrl += baseUrl.pathname.substring(0, apiIndex);
}
// Append the new endpoint
removePasswordUrl += '/api/v1/security/remove-password';
console.log(`Remove password URL: ${removePasswordUrl}`);
for (const file of files) {
console.log(`Processing file: ${file.name}`);
if (file.type !== 'application/pdf') {
console.log(`Skipping non-PDF file: ${file.name}`);
decryptedFiles.push(file);
continue;
}
try {
const arrayBuffer = await file.arrayBuffer();
const loadingTask = pdfjsLib.getDocument({data: arrayBuffer});
console.log(`Attempting to load PDF: ${file.name}`);
const pdf = await loadingTask.promise;
console.log(`File is not encrypted: ${file.name}`);
decryptedFiles.push(file); // If no error, file is not encrypted
} catch (error) {
if (error.name === 'PasswordException' && error.code === 1) {
console.log(`PDF requires password: ${file.name}`, error);
console.log(`Attempting to remove password from PDF: ${file.name} with password.`);
const password = prompt(`${window.translations.decrypt.passwordPrompt}`);
if (!password) {
console.error(`No password provided for encrypted PDF: ${file.name}`);
showErrorBanner(
`${window.translations.decrypt.noPassword.replace('{0}', file.name)}`,
`${window.translations.decrypt.unexpectedError}`
);
throw error;
}
try {
// Prepare FormData for the decryption request
const formData = new FormData();
formData.append('fileInput', file);
formData.append('password', password);
// Use handleSingleDownload to send the request
const decryptionResult = await fetch(removePasswordUrl, {method: 'POST', body: formData});
if (decryptionResult && decryptionResult.blob) {
const decryptedBlob = await decryptionResult.blob();
const decryptedFile = new File([decryptedBlob], file.name, {type: 'application/pdf'});
/* // Create a link element to download the file
const link = document.createElement('a');
link.href = URL.createObjectURL(decryptedBlob);
link.download = 'test.pdf';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
*/
decryptedFiles.push(decryptedFile);
console.log(`Successfully decrypted PDF: ${file.name}`);
} else {
throw new Error('Decryption failed: No valid response from server');
}
} catch (decryptError) {
console.error(`Failed to decrypt PDF: ${file.name}`, decryptError);
showErrorBanner(
`${window.translations.invalidPasswordHeader.replace('{0}', file.name)}`,
`${window.translations.invalidPassword}`
);
throw decryptError;
}
} else {
console.log(`Error loading PDF: ${file.name}`, error);
throw error;
}
}
}
return decryptedFiles;
}
async function handleSingleDownload(url, formData, isMulti = false, isZip = false) { async function handleSingleDownload(url, formData, isMulti = false, isZip = false) {
const startTime = performance.now(); const startTime = performance.now();
const file = formData.get('fileInput'); const file = formData.get('fileInput');
@@ -242,7 +140,7 @@
let errorMessage = null; let errorMessage = null;
try { try {
const response = await window.fetchWithCsrf(url, {method: 'POST', body: formData}); const response = await fetch(url, {method: 'POST', body: formData});
const contentType = response.headers.get('content-type'); const contentType = response.headers.get('content-type');
if (!response.ok) { if (!response.ok) {

View File

@@ -1,6 +1,6 @@
const DraggableUtils = { const DraggableUtils = {
boxDragContainer: document.getElementById('box-drag-container'), boxDragContainer: document.getElementById("box-drag-container"),
pdfCanvas: document.getElementById('pdf-canvas'), pdfCanvas: document.getElementById("pdf-canvas"),
nextId: 0, nextId: 0,
pdfDoc: null, pdfDoc: null,
pageIndex: 0, pageIndex: 0,
@@ -9,17 +9,19 @@ const DraggableUtils = {
lastInteracted: null, lastInteracted: null,
init() { init() {
interact('.draggable-canvas') interact(".draggable-canvas")
.draggable({ .draggable({
listeners: { listeners: {
move: (event) => { move: (event) => {
const target = event.target; const target = event.target;
const x = (parseFloat(target.getAttribute('data-bs-x')) || 0) + event.dx; const x = (parseFloat(target.getAttribute("data-bs-x")) || 0)
const y = (parseFloat(target.getAttribute('data-bs-y')) || 0) + event.dy; + event.dx;
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0)
+ event.dy;
target.style.transform = `translate(${x}px, ${y}px)`; target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute('data-bs-x', x); target.setAttribute("data-bs-x", x);
target.setAttribute('data-bs-y', y); target.setAttribute("data-bs-y", y);
this.onInteraction(target); this.onInteraction(target);
//update the last interacted element //update the last interacted element
@@ -28,12 +30,12 @@ const DraggableUtils = {
}, },
}) })
.resizable({ .resizable({
edges: {left: true, right: true, bottom: true, top: true}, edges: { left: true, right: true, bottom: true, top: true },
listeners: { listeners: {
move: (event) => { move: (event) => {
var target = event.target; var target = event.target;
var x = parseFloat(target.getAttribute('data-bs-x')) || 0; var x = parseFloat(target.getAttribute("data-bs-x")) || 0;
var y = parseFloat(target.getAttribute('data-bs-y')) || 0; var y = parseFloat(target.getAttribute("data-bs-y")) || 0;
// check if control key is pressed // check if control key is pressed
if (event.ctrlKey) { if (event.ctrlKey) {
@@ -42,7 +44,8 @@ const DraggableUtils = {
let width = event.rect.width; let width = event.rect.width;
let height = event.rect.height; let height = event.rect.height;
if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) { if (Math.abs(event.deltaRect.width) >= Math.abs(
event.deltaRect.height)) {
height = width / aspectRatio; height = width / aspectRatio;
} else { } else {
width = height * aspectRatio; width = height * aspectRatio;
@@ -52,18 +55,19 @@ const DraggableUtils = {
event.rect.height = height; event.rect.height = height;
} }
target.style.width = event.rect.width + 'px'; target.style.width = event.rect.width + "px";
target.style.height = event.rect.height + 'px'; target.style.height = event.rect.height + "px";
// translate when resizing from top or left edges // translate when resizing from top or left edges
x += event.deltaRect.left; x += event.deltaRect.left;
y += event.deltaRect.top; y += event.deltaRect.top;
target.style.transform = 'translate(' + x + 'px,' + y + 'px)'; target.style.transform = "translate(" + x + "px," + y + "px)";
target.setAttribute('data-bs-x', x); target.setAttribute("data-bs-x", x);
target.setAttribute('data-bs-y', y); target.setAttribute("data-bs-y", y);
target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height); target.textContent = Math.round(event.rect.width) + "\u00D7"
+ Math.round(event.rect.height);
this.onInteraction(target); this.onInteraction(target);
}, },
@@ -71,7 +75,7 @@ const DraggableUtils = {
modifiers: [ modifiers: [
interact.modifiers.restrictSize({ interact.modifiers.restrictSize({
min: {width: 5, height: 5}, min: { width: 5, height: 5 },
}), }),
], ],
inertia: true, inertia: true,
@@ -91,8 +95,8 @@ const DraggableUtils = {
const stepY = target.offsetHeight * 0.05; const stepY = target.offsetHeight * 0.05;
// Get the current x and y coordinates // Get the current x and y coordinates
let x = parseFloat(target.getAttribute('data-bs-x')) || 0; let x = (parseFloat(target.getAttribute('data-bs-x')) || 0);
let y = parseFloat(target.getAttribute('data-bs-y')) || 0; let y = (parseFloat(target.getAttribute('data-bs-y')) || 0);
// Check which key was pressed and update the coordinates accordingly // Check which key was pressed and update the coordinates accordingly
switch (event.key) { switch (event.key) {
@@ -131,15 +135,15 @@ const DraggableUtils = {
}, },
createDraggableCanvas() { createDraggableCanvas() {
const createdCanvas = document.createElement('canvas'); const createdCanvas = document.createElement("canvas");
createdCanvas.id = `draggable-canvas-${this.nextId++}`; createdCanvas.id = `draggable-canvas-${this.nextId++}`;
createdCanvas.classList.add('draggable-canvas'); createdCanvas.classList.add("draggable-canvas");
const x = 0; const x = 0;
const y = 20; const y = 20;
createdCanvas.style.transform = `translate(${x}px, ${y}px)`; createdCanvas.style.transform = `translate(${x}px, ${y}px)`;
createdCanvas.setAttribute('data-bs-x', x); createdCanvas.setAttribute("data-bs-x", x);
createdCanvas.setAttribute('data-bs-y', y); createdCanvas.setAttribute("data-bs-y", y);
//Click element in order to enable arrow keys //Click element in order to enable arrow keys
createdCanvas.addEventListener('click', () => { createdCanvas.addEventListener('click', () => {
@@ -182,29 +186,29 @@ const DraggableUtils = {
newHeight = newHeight * scaleMultiplier; newHeight = newHeight * scaleMultiplier;
} }
createdCanvas.style.width = newWidth + 'px'; createdCanvas.style.width = newWidth + "px";
createdCanvas.style.height = newHeight + 'px'; createdCanvas.style.height = newHeight + "px";
var myContext = createdCanvas.getContext('2d'); var myContext = createdCanvas.getContext("2d");
myContext.drawImage(myImage, 0, 0); myContext.drawImage(myImage, 0, 0);
resolve(createdCanvas); resolve(createdCanvas);
}; };
}); });
}, },
deleteAllDraggableCanvases() { deleteAllDraggableCanvases() {
this.boxDragContainer.querySelectorAll('.draggable-canvas').forEach((el) => el.remove()); this.boxDragContainer.querySelectorAll(".draggable-canvas").forEach((el) => el.remove());
}, },
async addAllPagesDraggableCanvas(element) { async addAllPagesDraggableCanvas(element) {
if (element) { if (element) {
let currentPage = this.pageIndex; let currentPage = this.pageIndex
if (!this.elementAllPages.includes(element)) { if (!this.elementAllPages.includes(element)) {
this.elementAllPages.push(element); this.elementAllPages.push(element)
element.style.filter = 'sepia(1) hue-rotate(90deg) brightness(1.2)'; element.style.filter = 'sepia(1) hue-rotate(90deg) brightness(1.2)';
let newElement = { let newElement = {
element: element, "element": element,
offsetWidth: element.width, "offsetWidth": element.width,
offsetHeight: element.height, "offsetHeight": element.height
}; }
let pagesMap = this.documentsMap.get(this.pdfDoc); let pagesMap = this.documentsMap.get(this.pdfDoc);
@@ -212,20 +216,21 @@ const DraggableUtils = {
pagesMap = {}; pagesMap = {};
this.documentsMap.set(this.pdfDoc, pagesMap); this.documentsMap.set(this.pdfDoc, pagesMap);
} }
let page = this.pageIndex; let page = this.pageIndex
for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) { for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
if (pagesMap[`${pageIndex}-offsetWidth`]) { if (pagesMap[`${pageIndex}-offsetWidth`]) {
if (!pagesMap[pageIndex].includes(newElement)) { if (!pagesMap[pageIndex].includes(newElement)) {
pagesMap[pageIndex].push(newElement); pagesMap[pageIndex].push(newElement);
} }
} else { } else {
pagesMap[pageIndex] = []; pagesMap[pageIndex] = []
pagesMap[pageIndex].push(newElement); pagesMap[pageIndex].push(newElement)
pagesMap[`${pageIndex}-offsetWidth`] = pagesMap[`${page}-offsetWidth`]; pagesMap[`${pageIndex}-offsetWidth`] = pagesMap[`${page}-offsetWidth`];
pagesMap[`${pageIndex}-offsetHeight`] = pagesMap[`${page}-offsetHeight`]; pagesMap[`${pageIndex}-offsetHeight`] = pagesMap[`${page}-offsetHeight`];
} }
await this.goToPage(pageIndex); await this.goToPage(pageIndex)
} }
} else { } else {
const index = this.elementAllPages.indexOf(element); const index = this.elementAllPages.indexOf(element);
@@ -242,17 +247,17 @@ const DraggableUtils = {
for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) { for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
if (pagesMap[`${pageIndex}-offsetWidth`] && pageIndex != currentPage) { if (pagesMap[`${pageIndex}-offsetWidth`] && pageIndex != currentPage) {
const pageElements = pagesMap[pageIndex]; const pageElements = pagesMap[pageIndex];
pageElements.forEach((elementPage) => { pageElements.forEach(elementPage => {
const elementIndex = pageElements.findIndex((elementPage) => elementPage['element'].id === element.id); const elementIndex = pageElements.findIndex(elementPage => elementPage['element'].id === element.id);
if (elementIndex !== -1) { if (elementIndex !== -1) {
pageElements.splice(elementIndex, 1); pageElements.splice(elementIndex, 1);
} }
}); });
} }
await this.goToPage(pageIndex); await this.goToPage(pageIndex)
} }
} }
await this.goToPage(currentPage); await this.goToPage(currentPage)
} }
}, },
deleteDraggableCanvas(element) { deleteDraggableCanvas(element) {
@@ -266,7 +271,7 @@ const DraggableUtils = {
} }
}, },
getLastInteracted() { getLastInteracted() {
return this.boxDragContainer.querySelector('.draggable-canvas:last-of-type'); return this.boxDragContainer.querySelector(".draggable-canvas:last-of-type");
}, },
storePageContents() { storePageContents() {
@@ -275,7 +280,7 @@ const DraggableUtils = {
pagesMap = {}; pagesMap = {};
} }
const elements = [...this.boxDragContainer.querySelectorAll('.draggable-canvas')]; const elements = [...this.boxDragContainer.querySelectorAll(".draggable-canvas")];
const draggablesData = elements.map((el) => { const draggablesData = elements.map((el) => {
return { return {
element: el, element: el,
@@ -286,8 +291,8 @@ const DraggableUtils = {
elements.forEach((el) => this.boxDragContainer.removeChild(el)); elements.forEach((el) => this.boxDragContainer.removeChild(el));
pagesMap[this.pageIndex] = draggablesData; pagesMap[this.pageIndex] = draggablesData;
pagesMap[this.pageIndex + '-offsetWidth'] = this.pdfCanvas.offsetWidth; pagesMap[this.pageIndex + "-offsetWidth"] = this.pdfCanvas.offsetWidth;
pagesMap[this.pageIndex + '-offsetHeight'] = this.pdfCanvas.offsetHeight; pagesMap[this.pageIndex + "-offsetHeight"] = this.pdfCanvas.offsetHeight;
this.documentsMap.set(this.pdfDoc, pagesMap); this.documentsMap.set(this.pdfDoc, pagesMap);
}, },
@@ -324,8 +329,8 @@ const DraggableUtils = {
// render the page onto the canvas // render the page onto the canvas
var renderContext = { var renderContext = {
canvasContext: this.pdfCanvas.getContext('2d'), canvasContext: this.pdfCanvas.getContext("2d"),
viewport: page.getViewport({scale: 1}), viewport: page.getViewport({ scale: 1 }),
}; };
await page.render(renderContext).promise; await page.render(renderContext).promise;
@@ -353,7 +358,7 @@ const DraggableUtils = {
} }
}, },
parseTransform(element) {}, parseTransform(element) { },
async getOverlayedPdfDocument() { async getOverlayedPdfDocument() {
const pdfBytes = await this.pdfDoc.getData(); const pdfBytes = await this.pdfDoc.getData();
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, { const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
@@ -364,7 +369,7 @@ const DraggableUtils = {
const pagesMap = this.documentsMap.get(this.pdfDoc); const pagesMap = this.documentsMap.get(this.pdfDoc);
for (let pageIdx in pagesMap) { for (let pageIdx in pagesMap) {
if (pageIdx.includes('offset')) { if (pageIdx.includes("offset")) {
continue; continue;
} }
console.log(typeof pageIdx); console.log(typeof pageIdx);
@@ -372,8 +377,9 @@ const DraggableUtils = {
const page = pdfDocModified.getPage(parseInt(pageIdx)); const page = pdfDocModified.getPage(parseInt(pageIdx));
let draggablesData = pagesMap[pageIdx]; let draggablesData = pagesMap[pageIdx];
const offsetWidth = pagesMap[pageIdx + '-offsetWidth']; const offsetWidth = pagesMap[pageIdx + "-offsetWidth"];
const offsetHeight = pagesMap[pageIdx + '-offsetHeight']; const offsetHeight = pagesMap[pageIdx + "-offsetHeight"];
for (const draggableData of draggablesData) { for (const draggableData of draggablesData) {
// embed the draggable canvas // embed the draggable canvas
@@ -383,8 +389,8 @@ const DraggableUtils = {
const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes); const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes);
// calculate the position in the pdf document // calculate the position in the pdf document
const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, ''); const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, "");
const transformComponents = tansform.split(','); const transformComponents = tansform.split(",");
const draggablePositionPixels = { const draggablePositionPixels = {
x: parseFloat(transformComponents[0]), x: parseFloat(transformComponents[0]),
y: parseFloat(transformComponents[1]), y: parseFloat(transformComponents[1]),
@@ -423,8 +429,9 @@ const DraggableUtils = {
}; };
//Defining the image if the page has a 0-degree angle //Defining the image if the page has a 0-degree angle
let x = draggablePositionPdf.x; let x = draggablePositionPdf.x
let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height; let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height
//Defining the image position if it is at other angles //Defining the image position if it is at other angles
if (normalizedAngle === 90) { if (normalizedAngle === 90) {
@@ -444,7 +451,7 @@ const DraggableUtils = {
y: y, y: y,
width: draggablePositionPdf.width, width: draggablePositionPdf.width,
height: draggablePositionPdf.height, height: draggablePositionPdf.height,
rotate: rotation, rotate: rotation
}); });
} }
} }
@@ -453,6 +460,6 @@ const DraggableUtils = {
}, },
}; };
document.addEventListener('DOMContentLoaded', () => { document.addEventListener("DOMContentLoaded", () => {
DraggableUtils.init(); DraggableUtils.init();
}); });

View File

@@ -8,6 +8,7 @@ window.fetchWithCsrf = async function(url, options = {}) {
if (cookieValue) { if (cookieValue) {
return cookieValue; return cookieValue;
} }
const csrfElement = document.querySelector('input[name="_csrf"]'); const csrfElement = document.querySelector('input[name="_csrf"]');
return csrfElement ? csrfElement.value : null; return csrfElement ? csrfElement.value : null;
} }

View File

@@ -1,19 +1,20 @@
import FileIconFactory from './file-icon-factory.js'; import FileIconFactory from "./file-icon-factory.js";
import FileUtils from './file-utils.js'; import FileUtils from "./file-utils.js";
import UUID from './uuid.js'; import UUID from './uuid.js';
import {DecryptFile} from './DecryptFiles.js';
let isScriptExecuted = false; let isScriptExecuted = false;
if (!isScriptExecuted) { if (!isScriptExecuted) {
isScriptExecuted = true; isScriptExecuted = true;
document.addEventListener('DOMContentLoaded', function () { document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll('.custom-file-chooser').forEach(setupFileInput); document.querySelectorAll(".custom-file-chooser").forEach(setupFileInput);
}); });
} }
function setupFileInput(chooser) { function setupFileInput(chooser) {
const elementId = chooser.getAttribute('data-bs-element-id'); const elementId = chooser.getAttribute("data-bs-element-id");
const filesSelected = chooser.getAttribute('data-bs-files-selected'); const filesSelected = chooser.getAttribute("data-bs-files-selected");
const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt'); const pdfPrompt = chooser.getAttribute("data-bs-pdf-prompt");
const inputContainerId = chooser.getAttribute('data-bs-element-container-id'); const inputContainerId = chooser.getAttribute('data-bs-element-container-id');
let inputContainer = document.getElementById(inputContainerId); let inputContainer = document.getElementById(inputContainerId);
@@ -25,7 +26,7 @@ function setupFileInput(chooser) {
inputContainer.addEventListener('click', (e) => { inputContainer.addEventListener('click', (e) => {
let inputBtn = document.getElementById(elementId); let inputBtn = document.getElementById(elementId);
inputBtn.click(); inputBtn.click();
}); })
const dragenterListener = function () { const dragenterListener = function () {
dragCounter++; dragCounter++;
@@ -62,7 +63,7 @@ function setupFileInput(chooser) {
const files = dt.files; const files = dt.files;
const fileInput = document.getElementById(elementId); const fileInput = document.getElementById(elementId);
if (fileInput?.hasAttribute('multiple')) { if (fileInput?.hasAttribute("multiple")) {
pushFileListTo(files, allFiles); pushFileListTo(files, allFiles);
} else if (fileInput) { } else if (fileInput) {
allFiles = [files[0]]; allFiles = [files[0]];
@@ -77,7 +78,7 @@ function setupFileInput(chooser) {
dragCounter = 0; dragCounter = 0;
fileInput.dispatchEvent(new CustomEvent('change', {bubbles: true, detail: {source: 'drag-drop'}})); fileInput.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: {source: 'drag-drop'} }));
}; };
function pushFileListTo(fileList, container) { function pushFileListTo(fileList, container) {
@@ -86,7 +87,7 @@ function setupFileInput(chooser) {
} }
} }
['dragenter', 'dragover', 'dragleave', 'drop'].forEach((eventName) => { ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
document.body.addEventListener(eventName, preventDefaults, false); document.body.addEventListener(eventName, preventDefaults, false);
}); });
@@ -95,50 +96,37 @@ function setupFileInput(chooser) {
e.stopPropagation(); e.stopPropagation();
} }
document.body.addEventListener('dragenter', dragenterListener); document.body.addEventListener("dragenter", dragenterListener);
document.body.addEventListener('dragleave', dragleaveListener); document.body.addEventListener("dragleave", dragleaveListener);
document.body.addEventListener('drop', dropListener); document.body.addEventListener("drop", dropListener);
$('#' + elementId).on('change', async function (e) { $("#" + elementId).on("change", function (e) {
let element = e.target; let element = e.target;
const isDragAndDrop = e.detail?.source == 'drag-drop'; const isDragAndDrop = e.detail?.source == 'drag-drop';
if (element instanceof HTMLInputElement && element.hasAttribute('multiple')) { if (element instanceof HTMLInputElement && element.hasAttribute("multiple")) {
allFiles = isDragAndDrop ? allFiles : [...allFiles, ...element.files]; allFiles = isDragAndDrop ? allFiles : [... allFiles, ... element.files];
} else { } else {
allFiles = Array.from(isDragAndDrop ? allFiles : [element.files[0]]); allFiles = Array.from(isDragAndDrop ? allFiles : [element.files[0]]);
} }
allFiles = await Promise.all(
allFiles.map(async (file) => { allFiles = allFiles.map(file => {
let decryptedFile = file; if (!file.uniqueId) file.uniqueId = UUID.uuidv4();
try { return file;
const decryptFile = new DecryptFile(); });
const {isEncrypted, requiresPassword} = await decryptFile.checkFileEncrypted(file);
if (file.type === 'application/pdf' && isEncrypted) {
decryptedFile = await decryptFile.decryptFile(file, requiresPassword);
if (!decryptedFile) throw new Error('File decryption failed.');
}
decryptedFile.uniqueId = UUID.uuidv4();
return decryptedFile;
} catch (error) {
console.error(`Error decrypting file: ${file.name}`, error);
if (!file.uniqueId) file.uniqueId = UUID.uuidv4();
return file;
}
})
);
if (!isDragAndDrop) { if (!isDragAndDrop) {
let dataTransfer = toDataTransfer(allFiles); let dataTransfer = toDataTransfer(allFiles);
element.files = dataTransfer.files; element.files = dataTransfer.files;
} }
handleFileInputChange(this); handleFileInputChange(this);
this.dispatchEvent(new CustomEvent('file-input-change', {bubbles: true, detail: {elementId, allFiles}})); this.dispatchEvent(new CustomEvent("file-input-change", { bubbles: true }));
}); });
function toDataTransfer(files) { function toDataTransfer(files) {
let dataTransfer = new DataTransfer(); let dataTransfer = new DataTransfer();
files.forEach((file) => dataTransfer.items.add(file)); files.forEach(file => dataTransfer.items.add(file));
return dataTransfer; return dataTransfer;
} }
@@ -148,7 +136,7 @@ function setupFileInput(chooser) {
const filesInfo = files.map((f) => ({name: f.name, size: f.size, uniqueId: f.uniqueId})); const filesInfo = files.map((f) => ({name: f.name, size: f.size, uniqueId: f.uniqueId}));
const selectedFilesContainer = $(inputContainer).siblings('.selected-files'); const selectedFilesContainer = $(inputContainer).siblings(".selected-files");
selectedFilesContainer.empty(); selectedFilesContainer.empty();
filesInfo.forEach((info) => { filesInfo.forEach((info) => {
let fileContainerClasses = 'small-file-container d-flex flex-column justify-content-center align-items-center'; let fileContainerClasses = 'small-file-container d-flex flex-column justify-content-center align-items-center';
@@ -179,26 +167,28 @@ function setupFileInput(chooser) {
} }
function showOrHideSelectedFilesContainer(files) { function showOrHideSelectedFilesContainer(files) {
if (files && files.length > 0) chooser.style.setProperty('--selected-files-display', 'flex'); if (files && files.length > 0)
else chooser.style.setProperty('--selected-files-display', 'none'); chooser.style.setProperty('--selected-files-display', 'flex');
else
chooser.style.setProperty('--selected-files-display', 'none');
} }
function removeFileListener(e) { function removeFileListener(e) {
const fileId = e.target.getAttribute('data-file-id'); const fileId = (e.target).getAttribute('data-file-id');
let inputElement = document.getElementById(elementId); let inputElement = document.getElementById(elementId);
removeFileById(fileId, inputElement); removeFileById(fileId, inputElement);
showOrHideSelectedFilesContainer(allFiles); showOrHideSelectedFilesContainer(allFiles);
inputElement.dispatchEvent(new CustomEvent('file-input-change', {bubbles: true})); inputElement.dispatchEvent(new CustomEvent("file-input-change", { bubbles: true }));
} }
function removeFileById(fileId, inputElement) { function removeFileById(fileId, inputElement) {
let fileContainer = document.getElementById(fileId); let fileContainer = document.getElementById(fileId);
fileContainer.remove(); fileContainer.remove();
allFiles = allFiles.filter((v) => v.uniqueId != fileId); allFiles = allFiles.filter(v => v.uniqueId != fileId);
let dataTransfer = toDataTransfer(allFiles); let dataTransfer = toDataTransfer(allFiles);
if (inputElement) inputElement.files = dataTransfer.files; if (inputElement) inputElement.files = dataTransfer.files;
@@ -217,19 +207,23 @@ function setupFileInput(chooser) {
} }
function createFileInfoContainer(info) { function createFileInfoContainer(info) {
let fileInfoContainer = document.createElement('div'); let fileInfoContainer = document.createElement("div");
let fileInfoContainerClasses = 'file-info d-flex flex-column align-items-center justify-content-center'; let fileInfoContainerClasses = 'file-info d-flex flex-column align-items-center justify-content-center';
$(fileInfoContainer).addClass(fileInfoContainerClasses); $(fileInfoContainer).addClass(fileInfoContainerClasses);
$(fileInfoContainer).append(`<div title="${info.name}">${info.name}</div>`); $(fileInfoContainer).append(
`<div title="${info.name}">${info.name}</div>`
);
let fileSizeWithUnits = FileUtils.transformFileSize(info.size); let fileSizeWithUnits = FileUtils.transformFileSize(info.size);
$(fileInfoContainer).append(`<div title="${info.size}">${fileSizeWithUnits}</div>`); $(fileInfoContainer).append(
`<div title="${info.size}">${fileSizeWithUnits}</div>`
);
return fileInfoContainer; return fileInfoContainer;
} }
//Listen for event of file being removed and the filter it out of the allFiles array //Listen for event of file being removed and the filter it out of the allFiles array
document.addEventListener('fileRemoved', function (e) { document.addEventListener("fileRemoved", function (e) {
const fileId = e.detail; const fileId = e.detail;
let inputElement = document.getElementById(elementId); let inputElement = document.getElementById(elementId);
removeFileById(fileId, inputElement); removeFileById(fileId, inputElement);

View File

@@ -268,7 +268,7 @@ document.addEventListener("DOMContentLoaded", function () {
const parent = header.parentNode; const parent = header.parentNode;
const container = header.parentNode.querySelector(".feature-group-container"); const container = header.parentNode.querySelector(".feature-group-container");
if (parent.id !== "groupFavorites") { if (parent.id !== "groupFavorites") {
container.style.maxHeight = container.scrollHeight + "px"; container.style.maxHeight = container.clientHeight + "px";
} }
header.onclick = () => { header.onclick = () => {
expandCollapseToggle(parent); expandCollapseToggle(parent);

View File

@@ -5,7 +5,6 @@ import {SplitAllCommand} from './commands/split.js';
import {UndoManager} from './UndoManager.js'; import {UndoManager} from './UndoManager.js';
import {PageBreakCommand} from './commands/page-break.js'; import {PageBreakCommand} from './commands/page-break.js';
import {AddFilesCommand} from './commands/add-page.js'; import {AddFilesCommand} from './commands/add-page.js';
import {DecryptFile} from '../DecryptFiles.js';
class PdfContainer { class PdfContainer {
fileName; fileName;
@@ -41,8 +40,6 @@ class PdfContainer {
this.removeAllElements = this.removeAllElements.bind(this); this.removeAllElements = this.removeAllElements.bind(this);
this.resetPages = this.resetPages.bind(this); this.resetPages = this.resetPages.bind(this);
this.decryptFile = new DecryptFile();
this.undoManager = undoManager || new UndoManager(); this.undoManager = undoManager || new UndoManager();
this.pdfAdapters = pdfAdapters; this.pdfAdapters = pdfAdapters;
@@ -168,6 +165,7 @@ class PdfContainer {
input.click(); input.click();
}); });
} }
async addFilesFromFiles(files, nextSiblingElement, pages) { async addFilesFromFiles(files, nextSiblingElement, pages) {
this.fileName = files[0].name; this.fileName = files[0].name;
for (var i = 0; i < files.length; i++) { for (var i = 0; i < files.length; i++) {
@@ -175,37 +173,17 @@ class PdfContainer {
let processingTime, let processingTime,
errorMessage = null, errorMessage = null,
pageCount = 0; pageCount = 0;
try { try {
let decryptedFile = files[i]; const file = files[i];
let isEncrypted = false; if (file.type === 'application/pdf') {
let requiresPassword = false; const {renderer, pdfDocument} = await this.loadFile(file);
await this.decryptFile
.checkFileEncrypted(decryptedFile)
.then((result) => {
isEncrypted = result.isEncrypted;
requiresPassword = result.requiresPassword;
})
.catch((error) => {
console.error(error);
});
if (decryptedFile.type === 'application/pdf' && isEncrypted) {
decryptedFile = await this.decryptFile.decryptFile(decryptedFile, requiresPassword);
if (!decryptedFile) {
throw new Error('File decryption failed.');
}
}
if (decryptedFile.type === 'application/pdf') {
const {renderer, pdfDocument} = await this.loadFile(decryptedFile);
pageCount = renderer.pageCount || 0; pageCount = renderer.pageCount || 0;
pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages); pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages);
} else if (decryptedFile.type.startsWith('image/')) { } else if (file.type.startsWith('image/')) {
pages = await this.addImageFile(decryptedFile, nextSiblingElement, pages); pages = await this.addImageFile(file, nextSiblingElement, pages);
} }
processingTime = Date.now() - startTime; processingTime = Date.now() - startTime;
this.captureFileProcessingEvent(true, decryptedFile, processingTime, null, pageCount); this.captureFileProcessingEvent(true, file, processingTime, null, pageCount);
} catch (error) { } catch (error) {
processingTime = Date.now() - startTime; processingTime = Date.now() - startTime;
errorMessage = error.message || 'Unknown error'; errorMessage = error.message || 'Unknown error';
@@ -216,7 +194,6 @@ class PdfContainer {
document.querySelectorAll('.enable-on-file').forEach((element) => { document.querySelectorAll('.enable-on-file').forEach((element) => {
element.disabled = false; element.disabled = false;
}); });
return pages; return pages;
} }

View File

@@ -1,47 +0,0 @@
document.getElementById('download-pdf').addEventListener('click', async () => {
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
const modifiedPdfBytes = await modifiedPdf.save();
const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = originalFileName + '_addedImage.pdf';
link.click();
});
let originalFileName = '';
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
const fileInput = event.target;
fileInput.addEventListener('file-input-change', async (e) => {
const {allFiles} = e.detail;
if (allFiles && allFiles.length > 0) {
const file = allFiles[0];
originalFileName = file.name.replace(/\.[^/.]+$/, '');
const pdfData = await file.arrayBuffer();
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
const pdfDoc = await pdfjsLib.getDocument({data: pdfData}).promise;
await DraggableUtils.renderPage(pdfDoc, 0);
document.querySelectorAll('.show-on-file-selected').forEach((el) => {
el.style.cssText = '';
});
}
});
});
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.show-on-file-selected').forEach((el) => {
el.style.cssText = 'display:none !important';
});
});
const imageUpload = document.querySelector('input[name=image-upload]');
imageUpload.addEventListener('change', (e) => {
if (!e.target.files) {
return;
}
for (const imageFile of e.target.files) {
var reader = new FileReader();
reader.readAsDataURL(imageFile);
reader.onloadend = function (e) {
DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
};
}
});

View File

@@ -1,253 +0,0 @@
var canvas = document.getElementById('contrast-pdf-canvas');
var context = canvas.getContext('2d');
var originalImageData = null;
var allPages = [];
var pdfDoc = null;
var pdf = null; // This is the current PDF document
async function renderPDFAndSaveOriginalImageData(file) {
var fileReader = new FileReader();
fileReader.onload = async function () {
var data = new Uint8Array(this.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
pdf = await pdfjsLib.getDocument({data: data}).promise;
// Get the number of pages in the PDF
var numPages = pdf.numPages;
allPages = Array.from({length: numPages}, (_, i) => i + 1);
// Create a new PDF document
pdfDoc = await PDFLib.PDFDocument.create();
// Render the first page in the viewer
await renderPageAndAdjustImageProperties(1);
document.getElementById('sliders-container').style.display = 'block';
};
fileReader.readAsArrayBuffer(file);
}
// This function is now async and returns a promise
function renderPageAndAdjustImageProperties(pageNum) {
return new Promise(async function (resolve, reject) {
var page = await pdf.getPage(pageNum);
var scale = 1.5;
var viewport = page.getViewport({scale: scale});
canvas.height = viewport.height;
canvas.width = viewport.width;
var renderContext = {
canvasContext: context,
viewport: viewport,
};
var renderTask = page.render(renderContext);
renderTask.promise.then(function () {
originalImageData = context.getImageData(0, 0, canvas.width, canvas.height);
adjustImageProperties();
resolve();
});
canvas.classList.add('fixed-shadow-canvas');
});
}
function adjustImageProperties() {
var contrast = parseFloat(document.getElementById('contrast-slider').value);
var brightness = parseFloat(document.getElementById('brightness-slider').value);
var saturation = parseFloat(document.getElementById('saturation-slider').value);
contrast /= 100; // normalize to range [0, 2]
brightness /= 100; // normalize to range [0, 2]
saturation /= 100; // normalize to range [0, 2]
if (originalImageData) {
var newImageData = context.createImageData(originalImageData.width, originalImageData.height);
newImageData.data.set(originalImageData.data);
for (var i = 0; i < newImageData.data.length; i += 4) {
var r = newImageData.data[i];
var g = newImageData.data[i + 1];
var b = newImageData.data[i + 2];
// Adjust contrast
r = adjustContrastForPixel(r, contrast);
g = adjustContrastForPixel(g, contrast);
b = adjustContrastForPixel(b, contrast);
// Adjust brightness
r = adjustBrightnessForPixel(r, brightness);
g = adjustBrightnessForPixel(g, brightness);
b = adjustBrightnessForPixel(b, brightness);
// Adjust saturation
var rgb = adjustSaturationForPixel(r, g, b, saturation);
newImageData.data[i] = rgb[0];
newImageData.data[i + 1] = rgb[1];
newImageData.data[i + 2] = rgb[2];
}
context.putImageData(newImageData, 0, 0);
}
}
function rgbToHsl(r, g, b) {
(r /= 255), (g /= 255), (b /= 255);
var max = Math.max(r, g, b),
min = Math.min(r, g, b);
var h,
s,
l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
} else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return [h, s, l];
}
function hslToRgb(h, s, l) {
var r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
var hue2rgb = function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [r * 255, g * 255, b * 255];
}
function adjustContrastForPixel(pixel, contrast) {
// Normalize to range [-0.5, 0.5]
var normalized = pixel / 255 - 0.5;
// Apply contrast
normalized *= contrast;
// Denormalize back to [0, 255]
return (normalized + 0.5) * 255;
}
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function adjustSaturationForPixel(r, g, b, saturation) {
var hsl = rgbToHsl(r, g, b);
// Adjust saturation
hsl[1] = clamp(hsl[1] * saturation, 0, 1);
// Convert back to RGB
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
// Return adjusted RGB values
return rgb;
}
function adjustBrightnessForPixel(pixel, brightness) {
return Math.max(0, Math.min(255, pixel * brightness));
}
let inputFileName = '';
async function downloadPDF() {
for (var i = 0; i < allPages.length; i++) {
await renderPageAndAdjustImageProperties(allPages[i]);
const pngImageBytes = canvas.toDataURL('image/png');
const pngImage = await pdfDoc.embedPng(pngImageBytes);
const pngDims = pngImage.scale(1);
// Create a blank page matching the dimensions of the image
const page = pdfDoc.addPage([pngDims.width, pngDims.height]);
// Draw the PNG image
page.drawImage(pngImage, {
x: 0,
y: 0,
width: pngDims.width,
height: pngDims.height,
});
}
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save();
// Create a Blob
const blob = new Blob([pdfBytes.buffer], {type: 'application/pdf'});
// Create download link
const downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(blob);
let newFileName = inputFileName ? inputFileName.replace('.pdf', '') : 'download';
newFileName += '_adjusted_color.pdf';
downloadLink.download = newFileName;
downloadLink.click();
// After download, reset the viewer and clear stored data
allPages = []; // Clear the pages
originalImageData = null; // Clear the image data
// Go back to page 1 and render it in the viewer
if (pdf !== null) {
renderPageAndAdjustImageProperties(1);
}
}
// Event listeners
document.getElementById('fileInput-input').addEventListener('change', function (e) {
const fileInput = event.target;
fileInput.addEventListener('file-input-change', async (e) => {
const {allFiles} = e.detail;
if (allFiles && allFiles.length > 0) {
const file = allFiles[0];
inputFileName = file.name;
renderPDFAndSaveOriginalImageData(file);
}
});
});
document.getElementById('contrast-slider').addEventListener('input', function () {
document.getElementById('contrast-val').textContent = this.value;
adjustImageProperties();
});
document.getElementById('brightness-slider').addEventListener('input', function () {
document.getElementById('brightness-val').textContent = this.value;
adjustImageProperties();
});
document.getElementById('saturation-slider').addEventListener('input', function () {
document.getElementById('saturation-val').textContent = this.value;
adjustImageProperties();
});
document.getElementById('download-button').addEventListener('click', function () {
downloadPDF();
});

View File

@@ -1,150 +0,0 @@
const deleteAllCheckbox = document.querySelector('#deleteAll');
let inputs = document.querySelectorAll('input');
const customMetadataDiv = document.getElementById('customMetadata');
const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries');
deleteAllCheckbox.addEventListener('change', function (event) {
inputs.forEach((input) => {
// If it's the deleteAllCheckbox or any file input, skip
if (input === deleteAllCheckbox || input.type === 'file') {
return;
}
// Disable or enable based on the checkbox state
input.disabled = deleteAllCheckbox.checked;
});
});
const customModeCheckbox = document.getElementById('customModeCheckbox');
const addMetadataBtn = document.getElementById('addMetadataBtn');
const customMetadataFormContainer = document.getElementById('customMetadataEntries');
var count = 1;
const fileInput = document.querySelector('#fileInput-input');
const authorInput = document.querySelector('#author');
const creationDateInput = document.querySelector('#creationDate');
const creatorInput = document.querySelector('#creator');
const keywordsInput = document.querySelector('#keywords');
const modificationDateInput = document.querySelector('#modificationDate');
const producerInput = document.querySelector('#producer');
const subjectInput = document.querySelector('#subject');
const titleInput = document.querySelector('#title');
const trappedInput = document.querySelector('#trapped');
var lastPDFFileMeta = null;
var lastPDFFile = null;
fileInput.addEventListener('change', async function () {
fileInput.addEventListener('file-input-change', async (e) => {
const {allFiles} = e.detail;
if (allFiles && allFiles.length > 0) {
const file = allFiles[0];
while (otherMetadataEntriesDiv.firstChild) {
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
}
while (customMetadataFormContainer.firstChild) {
customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
}
var url = URL.createObjectURL(file);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
const pdf = await pdfjsLib.getDocument(url).promise;
const pdfMetadata = await pdf.getMetadata();
lastPDFFile = pdfMetadata?.info;
console.log(pdfMetadata);
if (!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) {
customModeCheckbox.disabled = true;
customModeCheckbox.checked = false;
} else {
customModeCheckbox.disabled = false;
}
authorInput.value = pdfMetadata?.info?.Author;
creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate);
creatorInput.value = pdfMetadata?.info?.Creator;
keywordsInput.value = pdfMetadata?.info?.Keywords;
modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate);
producerInput.value = pdfMetadata?.info?.Producer;
subjectInput.value = pdfMetadata?.info?.Subject;
titleInput.value = pdfMetadata?.info?.Title;
console.log(pdfMetadata?.info);
const trappedValue = pdfMetadata?.info?.Trapped;
// Get all options in the select element
const options = trappedInput.options;
// Loop through all options to find the one with a matching value
for (let i = 0; i < options.length; i++) {
if (options[i].value === trappedValue) {
options[i].selected = true;
break;
}
}
addExtra();
}
});
});
addMetadataBtn.addEventListener('click', () => {
const keyInput = document.createElement('input');
keyInput.type = 'text';
keyInput.placeholder = 'Key';
keyInput.className = 'form-control';
keyInput.name = `allRequestParams[customKey${count}]`;
const valueInput = document.createElement('input');
valueInput.type = 'text';
valueInput.placeholder = 'Value';
valueInput.className = 'form-control';
valueInput.name = `allRequestParams[customValue${count}]`;
count = count + 1;
const formGroup = document.createElement('div');
formGroup.className = 'mb-3';
formGroup.appendChild(keyInput);
formGroup.appendChild(valueInput);
customMetadataFormContainer.appendChild(formGroup);
});
function convertDateFormat(dateTimeString) {
if (!dateTimeString || dateTimeString.length < 17) {
return dateTimeString;
}
const year = dateTimeString.substring(2, 6);
const month = dateTimeString.substring(6, 8);
const day = dateTimeString.substring(8, 10);
const hour = dateTimeString.substring(10, 12);
const minute = dateTimeString.substring(12, 14);
const second = dateTimeString.substring(14, 16);
return year + '/' + month + '/' + day + ' ' + hour + ':' + minute + ':' + second;
}
function addExtra() {
const event = document.getElementById('customModeCheckbox');
if (event.checked && lastPDFFile.Custom != null) {
customMetadataDiv.style.display = 'block';
for (const [key, value] of Object.entries(lastPDFFile.Custom)) {
if (
key === 'Author' ||
key === 'CreationDate' ||
key === 'Creator' ||
key === 'Keywords' ||
key === 'ModDate' ||
key === 'Producer' ||
key === 'Subject' ||
key === 'Title' ||
key === 'Trapped'
) {
continue;
}
const entryDiv = document.createElement('div');
entryDiv.className = 'mb-3';
entryDiv.innerHTML = `<div class="mb-3"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
otherMetadataEntriesDiv.appendChild(entryDiv);
}
} else {
customMetadataDiv.style.display = 'none';
while (otherMetadataEntriesDiv.firstChild) {
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
}
}
}
customModeCheckbox.addEventListener('change', (event) => {
addExtra();
});

View File

@@ -1,159 +0,0 @@
let pdfCanvas = document.getElementById('cropPdfCanvas');
let overlayCanvas = document.getElementById('overlayCanvas');
let canvasesContainer = document.getElementById('canvasesContainer');
canvasesContainer.style.display = 'none';
let containerRect = canvasesContainer.getBoundingClientRect();
let context = pdfCanvas.getContext('2d');
let overlayContext = overlayCanvas.getContext('2d');
overlayCanvas.width = pdfCanvas.width;
overlayCanvas.height = pdfCanvas.height;
let isDrawing = false; // New flag to check if drawing is ongoing
let cropForm = document.getElementById('cropForm');
let fileInput = document.getElementById('fileInput-input');
let xInput = document.getElementById('x');
let yInput = document.getElementById('y');
let widthInput = document.getElementById('width');
let heightInput = document.getElementById('height');
let pdfDoc = null;
let currentPage = 1;
let totalPages = 0;
let startX = 0;
let startY = 0;
let rectWidth = 0;
let rectHeight = 0;
let pageScale = 1; // The scale which the pdf page renders
let timeId = null; // timeout id for resizing canvases event
function renderPageFromFile(file) {
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function (ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
});
};
reader.readAsArrayBuffer(file);
}
}
window.addEventListener('resize', function () {
clearTimeout(timeId);
timeId = setTimeout(function () {
if (fileInput.files.length == 0) return;
let canvasesContainer = document.getElementById('canvasesContainer');
let containerRect = canvasesContainer.getBoundingClientRect();
context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
pdfCanvas.width = containerRect.width;
pdfCanvas.height = containerRect.height;
overlayCanvas.width = containerRect.width;
overlayCanvas.height = containerRect.height;
let file = fileInput.files[0];
renderPageFromFile(file);
}, 1000);
});
fileInput.addEventListener('change', function (e) {
fileInput.addEventListener('file-input-change', async (e) => {
const {allFiles} = e.detail;
if (allFiles && allFiles.length > 0) {
canvasesContainer.style.display = 'block'; // set for visual purposes
let file = allFiles[0];
renderPageFromFile(file);
}
});
});
cropForm.addEventListener('submit', function (e) {
if (xInput.value == '' && yInput.value == '' && widthInput.value == '' && heightInput.value == '') {
// Ορίστε συντεταγμένες για ολόκληρη την επιφάνεια του PDF
xInput.value = 0;
yInput.value = 0;
widthInput.value = containerRect.width;
heightInput.value = containerRect.height;
}
});
overlayCanvas.addEventListener('mousedown', function (e) {
// Clear previously drawn rectangle on the main canvas
context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
renderPage(currentPage); // Re-render the PDF
// Clear the overlay canvas to ensure old drawings are removed
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
startX = e.offsetX;
startY = e.offsetY;
isDrawing = true;
});
overlayCanvas.addEventListener('mousemove', function (e) {
if (!isDrawing) return;
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear previous rectangle
rectWidth = e.offsetX - startX;
rectHeight = e.offsetY - startY;
overlayContext.strokeStyle = 'red';
overlayContext.strokeRect(startX, startY, rectWidth, rectHeight);
});
overlayCanvas.addEventListener('mouseup', function (e) {
isDrawing = false;
rectWidth = e.offsetX - startX;
rectHeight = e.offsetY - startY;
let flippedY = pdfCanvas.height - e.offsetY;
xInput.value = startX / pageScale;
yInput.value = flippedY / pageScale;
widthInput.value = rectWidth / pageScale;
heightInput.value = rectHeight / pageScale;
// Draw the final rectangle on the main canvas
context.strokeStyle = 'red';
context.strokeRect(startX, startY, rectWidth, rectHeight);
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear the overlay
});
function renderPage(pageNumber) {
pdfDoc.getPage(pageNumber).then(function (page) {
let canvasesContainer = document.getElementById('canvasesContainer');
let containerRect = canvasesContainer.getBoundingClientRect();
pageScale = containerRect.width / page.getViewport({scale: 1}).width; // The new scale
let viewport = page.getViewport({scale: containerRect.width / page.getViewport({scale: 1}).width});
canvasesContainer.width = viewport.width;
canvasesContainer.height = viewport.height;
pdfCanvas.width = viewport.width;
pdfCanvas.height = viewport.height;
overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas
overlayCanvas.height = viewport.height;
let renderContext = {canvasContext: context, viewport: viewport};
page.render(renderContext);
pdfCanvas.classList.add('shadow-canvas');
});
}

View File

@@ -1,138 +0,0 @@
let pdfCanvas = document.getElementById('cropPdfCanvas');
let overlayCanvas = document.getElementById('overlayCanvas');
let canvasesContainer = document.getElementById('canvasesContainer');
canvasesContainer.style.display = 'none';
// let paginationBtnContainer = ;
let context = pdfCanvas.getContext('2d');
let overlayContext = overlayCanvas.getContext('2d');
let btn1Object = document.getElementById('previous-page-btn');
let btn2Object = document.getElementById('next-page-btn');
overlayCanvas.width = pdfCanvas.width;
overlayCanvas.height = pdfCanvas.height;
let fileInput = document.getElementById('fileInput-input');
let file;
let pdfDoc = null;
let pageId = document.getElementById('pageId');
let currentPage = 1;
let totalPages = 0;
let startX = 0;
let startY = 0;
let rectWidth = 0;
let rectHeight = 0;
let timeId = null; // timeout id for resizing canvases event
btn1Object.addEventListener('click', function (e) {
if (currentPage !== 1) {
currentPage = currentPage - 1;
pageId.value = currentPage;
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function (ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
});
};
reader.readAsArrayBuffer(file);
}
}
});
btn2Object.addEventListener('click', function (e) {
if (currentPage !== totalPages) {
currentPage = currentPage + 1;
pageId.value = currentPage;
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function (ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
});
};
reader.readAsArrayBuffer(file);
}
}
});
function renderPageFromFile(file) {
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function (ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
});
pageId.value = currentPage;
};
reader.readAsArrayBuffer(file);
document.getElementById('pagination-button-container').style.display = 'flex';
document.getElementById('instruction-text').style.display = 'block';
}
}
window.addEventListener('resize', function () {
clearTimeout(timeId);
timeId = setTimeout(function () {
if (fileInput.files.length == 0) return;
let canvasesContainer = document.getElementById('canvasesContainer');
let containerRect = canvasesContainer.getBoundingClientRect();
context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
pdfCanvas.width = containerRect.width;
pdfCanvas.height = containerRect.height;
overlayCanvas.width = containerRect.width;
overlayCanvas.height = containerRect.height;
let file = fileInput.files[0];
renderPageFromFile(file);
}, 1000);
});
fileInput.addEventListener('change', function (e) {
fileInput.addEventListener('file-input-change', async (e) => {
const {allFiles} = e.detail;
if (allFiles && allFiles.length > 0) {
canvasesContainer.style.display = 'block'; // set for visual purposes
file = e.target.files[0];
renderPageFromFile(file);
}
});
});
function renderPage(pageNumber) {
pdfDoc.getPage(pageNumber).then(function (page) {
let viewport = page.getViewport({scale: 1.0});
pdfCanvas.width = viewport.width;
pdfCanvas.height = viewport.height;
overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas
overlayCanvas.height = viewport.height;
let renderContext = {canvasContext: context, viewport: viewport};
page.render(renderContext);
pdfCanvas.classList.add('shadow-canvas');
});
}

View File

@@ -1,213 +0,0 @@
window.toggleSignatureView = toggleSignatureView;
window.previewSignature = previewSignature;
window.addSignatureFromPreview = addSignatureFromPreview;
window.addDraggableFromPad = addDraggableFromPad;
window.addDraggableFromText = addDraggableFromText;
window.goToFirstOrLastPage = goToFirstOrLastPage;
let currentPreviewSrc = null;
function toggleSignatureView() {
const gridView = document.getElementById('gridView');
const listView = document.getElementById('listView');
const gridText = document.querySelector('.grid-view-text');
const listText = document.querySelector('.list-view-text');
if (gridView.style.display !== 'none') {
gridView.style.display = 'none';
listView.style.display = 'block';
gridText.style.display = 'none';
listText.style.display = 'inline';
} else {
gridView.style.display = 'block';
listView.style.display = 'none';
gridText.style.display = 'inline';
listText.style.display = 'none';
}
}
function previewSignature(element) {
const src = element.dataset.src;
currentPreviewSrc = src;
const filename = element.querySelector('.signature-list-name').textContent;
const previewImage = document.getElementById('previewImage');
const previewFileName = document.getElementById('previewFileName');
previewImage.src = src;
previewFileName.textContent = filename;
const modal = new bootstrap.Modal(document.getElementById('signaturePreview'));
modal.show();
}
function addSignatureFromPreview() {
if (currentPreviewSrc) {
DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc);
bootstrap.Modal.getInstance(document.getElementById('signaturePreview')).hide();
}
}
let originalFileName = '';
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
const fileInput = event.target;
fileInput.addEventListener('file-input-change', async (e) => {
const {allFiles} = e.detail;
if (allFiles && allFiles.length > 0) {
const file = allFiles[0];
originalFileName = file.name.replace(/\.[^/.]+$/, '');
const pdfData = await file.arrayBuffer();
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
const pdfDoc = await pdfjsLib.getDocument({data: pdfData}).promise;
await DraggableUtils.renderPage(pdfDoc, 0);
document.querySelectorAll('.show-on-file-selected').forEach((el) => {
el.style.cssText = '';
});
}
});
});
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.show-on-file-selected').forEach((el) => {
el.style.cssText = 'display:none !important';
});
});
const imageUpload = document.querySelector('input[name=image-upload]');
imageUpload.addEventListener('change', (e) => {
if (!e.target.files) return;
for (const imageFile of e.target.files) {
var reader = new FileReader();
reader.readAsDataURL(imageFile);
reader.onloadend = function (e) {
DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
};
}
});
const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
const signaturePad = new SignaturePad(signaturePadCanvas, {
minWidth: 1,
maxWidth: 2,
penColor: 'black',
});
function addDraggableFromPad() {
if (signaturePad.isEmpty()) return;
const startTime = Date.now();
const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas);
console.log(Date.now() - startTime);
DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
}
function getCroppedCanvasDataUrl(canvas) {
let originalCtx = canvas.getContext('2d');
let originalWidth = canvas.width;
let originalHeight = canvas.height;
let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
let minX = originalWidth + 1,
maxX = -1,
minY = originalHeight + 1,
maxY = -1,
x = 0,
y = 0,
currentPixelColorValueIndex;
for (y = 0; y < originalHeight; y++) {
for (x = 0; x < originalWidth; x++) {
currentPixelColorValueIndex = (y * originalWidth + x) * 4;
let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
if (currentPixelAlphaValue > 0) {
if (minX > x) minX = x;
if (maxX < x) maxX = x;
if (minY > y) minY = y;
if (maxY < y) maxY = y;
}
}
}
let croppedWidth = maxX - minX;
let croppedHeight = maxY - minY;
if (croppedWidth < 0 || croppedHeight < 0) return null;
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
let croppedCanvas = document.createElement('canvas'),
croppedCtx = croppedCanvas.getContext('2d');
croppedCanvas.width = croppedWidth;
croppedCanvas.height = croppedHeight;
croppedCtx.putImageData(cuttedImageData, 0, 0);
return croppedCanvas.toDataURL();
}
function resizeCanvas() {
var ratio = Math.max(window.devicePixelRatio || 1, 1);
var additionalFactor = 10;
signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
signaturePadCanvas.getContext('2d').scale(ratio * additionalFactor, ratio * additionalFactor);
signaturePad.clear();
}
new IntersectionObserver((entries, observer) => {
if (entries.some((entry) => entry.intersectionRatio > 0)) {
resizeCanvas();
}
}).observe(signaturePadCanvas);
new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
function addDraggableFromText() {
const sigText = document.getElementById('sigText').value;
const font = document.querySelector('select[name=font]').value;
const fontSize = 100;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.font = `${fontSize}px ${font}`;
const textWidth = ctx.measureText(sigText).width;
const textHeight = fontSize;
let paragraphs = sigText.split(/\r?\n/);
canvas.width = textWidth;
canvas.height = paragraphs.length * textHeight * 1.35; // for tails
ctx.font = `${fontSize}px ${font}`;
ctx.textBaseline = 'top';
let y = 0;
paragraphs.forEach((paragraph) => {
ctx.fillText(paragraph, 0, y);
y += fontSize;
});
const dataURL = canvas.toDataURL();
DraggableUtils.createDraggableCanvasFromUrl(dataURL);
}
async function goToFirstOrLastPage(page) {
if (page) {
const lastPage = DraggableUtils.pdfDoc.numPages;
await DraggableUtils.goToPage(lastPage - 1);
} else {
await DraggableUtils.goToPage(0);
}
}
document.getElementById('download-pdf').addEventListener('click', async () => {
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
const modifiedPdfBytes = await modifiedPdf.save();
const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = originalFileName + '_signed.pdf';
link.click();
});

View File

@@ -196,7 +196,7 @@
/*<![CDATA[*/ /*<![CDATA[*/
const urlGetApiKey = /*[[@{/api/v1/user/get-api-key}]]*/ "/api/v1/user/get-api-key"; const urlGetApiKey = /*[[@{/api/v1/user/get-api-key}]]*/ "/api/v1/user/get-api-key";
/*]]>*/ /*]]>*/
let response = await window.fetchWithCsrf(urlGetApiKey, { method: 'POST' }); let response = await fetch(urlGetApiKey, { method: 'POST' });
if (response.status === 200) { if (response.status === 200) {
let apiKey = await response.text(); let apiKey = await response.text();
manageUIState(apiKey); manageUIState(apiKey);
@@ -213,7 +213,7 @@
/*<![CDATA[*/ /*<![CDATA[*/
const urlUpdateApiKey = /*[[@{/api/v1/user/update-api-key}]]*/ "/api/v1/user/update-api-key"; const urlUpdateApiKey = /*[[@{/api/v1/user/update-api-key}]]*/ "/api/v1/user/update-api-key";
/*]]>*/ /*]]>*/
let response = await window.fetchWithCsrf(urlUpdateApiKey, { method: 'POST' }); let response = await fetch(urlUpdateApiKey, { method: 'POST' });
if (response.status === 200) { if (response.status === 200) {
let apiKey = await response.text(); let apiKey = await response.text();
manageUIState(apiKey); manageUIState(apiKey);

View File

@@ -34,8 +34,140 @@
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2; width: 100%"></canvas> <canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2; width: 100%"></canvas>
</div> </div>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script> <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script> <script>
<script type="module" th:src="@{'/js/pages/pdf-to-csv.js'}"> let pdfCanvas = document.getElementById('cropPdfCanvas');
let overlayCanvas = document.getElementById('overlayCanvas');
let canvasesContainer = document.getElementById('canvasesContainer');
canvasesContainer.style.display = "none";
// let paginationBtnContainer = ;
let context = pdfCanvas.getContext('2d');
let overlayContext = overlayCanvas.getContext('2d');
let btn1Object = document.getElementById('previous-page-btn');
let btn2Object = document.getElementById('next-page-btn');
overlayCanvas.width = pdfCanvas.width;
overlayCanvas.height = pdfCanvas.height;
let fileInput = document.getElementById('fileInput-input');
let file;
let pdfDoc = null;
let pageId = document.getElementById('pageId');
let currentPage = 1;
let totalPages = 0;
let startX = 0;
let startY = 0;
let rectWidth = 0;
let rectHeight = 0;
let timeId = null; // timeout id for resizing canvases event
btn1Object.addEventListener('click',function (e){
if (currentPage !== 1) {
currentPage = currentPage - 1;
pageId.value = currentPage;
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function (ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
});
};
reader.readAsArrayBuffer(file);
}
}
});
btn2Object.addEventListener('click',function (e){
if (currentPage !== totalPages){
currentPage=currentPage+1;
pageId.value = currentPage;
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function(ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
});
};
reader.readAsArrayBuffer(file);
}
}
});
function renderPageFromFile(file) {
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function (ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
});
pageId.value = currentPage;
};
reader.readAsArrayBuffer(file);
document.getElementById("pagination-button-container").style.display = "flex";
document.getElementById("instruction-text").style.display = "block";
}
}
window.addEventListener("resize", function() {
clearTimeout(timeId);
timeId = setTimeout(function () {
if (fileInput.files.length == 0) return;
let canvasesContainer = document.getElementById('canvasesContainer');
let containerRect = canvasesContainer.getBoundingClientRect();
context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
pdfCanvas.width = containerRect.width;
pdfCanvas.height = containerRect.height;
overlayCanvas.width = containerRect.width;
overlayCanvas.height = containerRect.height;
let file = fileInput.files[0];
renderPageFromFile(file);
}, 1000);
});
fileInput.addEventListener('change', function(e) {
canvasesContainer.style.display = "block"; // set for visual purposes
file = e.target.files[0];
renderPageFromFile(file);
});
function renderPage(pageNumber) {
pdfDoc.getPage(pageNumber).then(function(page) {
let viewport = page.getViewport({ scale: 1.0 });
pdfCanvas.width = viewport.width;
pdfCanvas.height = viewport.height;
overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas
overlayCanvas.height = viewport.height;
let renderContext = { canvasContext: context, viewport: viewport };
page.render(renderContext);
pdfCanvas.classList.add("shadow-canvas");
});
}
</script> </script>
</div> </div>
</div> </div>

View File

@@ -32,7 +32,163 @@
</div> </div>
</div> </div>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script> <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script type="module" th:src="@{'/js/pages/crop.js'}"></script> <script>
let pdfCanvas = document.getElementById('cropPdfCanvas');
let overlayCanvas = document.getElementById('overlayCanvas');
let canvasesContainer = document.getElementById('canvasesContainer');
canvasesContainer.style.display = "none";
let containerRect = canvasesContainer.getBoundingClientRect();
let context = pdfCanvas.getContext('2d');
let overlayContext = overlayCanvas.getContext('2d');
overlayCanvas.width = pdfCanvas.width;
overlayCanvas.height = pdfCanvas.height;
let isDrawing = false; // New flag to check if drawing is ongoing
let cropForm = document.getElementById('cropForm');
let fileInput = document.getElementById('fileInput-input');
let xInput = document.getElementById('x');
let yInput = document.getElementById('y');
let widthInput = document.getElementById('width');
let heightInput = document.getElementById('height');
let pdfDoc = null;
let currentPage = 1;
let totalPages = 0;
let startX = 0;
let startY = 0;
let rectWidth = 0;
let rectHeight = 0;
let pageScale = 1; // The scale which the pdf page renders
let timeId = null; // timeout id for resizing canvases event
function renderPageFromFile(file) {
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function(ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
});
};
reader.readAsArrayBuffer(file);
}
}
window.addEventListener("resize", function() {
clearTimeout(timeId);
timeId = setTimeout(function () {
if (fileInput.files.length == 0) return;
let canvasesContainer = document.getElementById('canvasesContainer');
let containerRect = canvasesContainer.getBoundingClientRect();
context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
pdfCanvas.width = containerRect.width;
pdfCanvas.height = containerRect.height;
overlayCanvas.width = containerRect.width;
overlayCanvas.height = containerRect.height;
let file = fileInput.files[0];
renderPageFromFile(file);
}, 1000);
});
fileInput.addEventListener('change', function(e) {
canvasesContainer.style.display = "block"; // set for visual purposes
let file = e.target.files[0];
renderPageFromFile(file);
});
cropForm.addEventListener('submit', function(e) {
if (xInput.value == "" && yInput.value == "" && widthInput.value == "" && heightInput.value == "") {
// Ορίστε συντεταγμένες για ολόκληρη την επιφάνεια του PDF
xInput.value = 0;
yInput.value = 0;
widthInput.value = containerRect.width;
heightInput.value = containerRect.height;
}
});
overlayCanvas.addEventListener('mousedown', function(e) {
// Clear previously drawn rectangle on the main canvas
context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
renderPage(currentPage); // Re-render the PDF
// Clear the overlay canvas to ensure old drawings are removed
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
startX = e.offsetX;
startY = e.offsetY;
isDrawing = true;
});
overlayCanvas.addEventListener('mousemove', function(e) {
if (!isDrawing) return;
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear previous rectangle
rectWidth = e.offsetX - startX;
rectHeight = e.offsetY - startY;
overlayContext.strokeStyle = 'red';
overlayContext.strokeRect(startX, startY, rectWidth, rectHeight);
});
overlayCanvas.addEventListener('mouseup', function(e) {
isDrawing = false;
rectWidth = e.offsetX - startX;
rectHeight = e.offsetY - startY;
let flippedY = pdfCanvas.height - e.offsetY;
xInput.value = startX / pageScale;
yInput.value = flippedY / pageScale;
widthInput.value = rectWidth / pageScale;
heightInput.value = rectHeight /pageScale;
// Draw the final rectangle on the main canvas
context.strokeStyle = 'red';
context.strokeRect(startX, startY, rectWidth, rectHeight);
overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear the overlay
});
function renderPage(pageNumber) {
pdfDoc.getPage(pageNumber).then(function(page) {
let canvasesContainer = document.getElementById('canvasesContainer');
let containerRect = canvasesContainer.getBoundingClientRect();
pageScale = containerRect.width / page.getViewport({ scale: 1 }).width; // The new scale
let viewport = page.getViewport({ scale: containerRect.width / page.getViewport({ scale: 1 }).width });
canvasesContainer.width =viewport.width;
canvasesContainer.height = viewport.height;
pdfCanvas.width = viewport.width;
pdfCanvas.height = viewport.height;
overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas
overlayCanvas.height = viewport.height;
let renderContext = { canvasContext: context, viewport: viewport };
page.render(renderContext);
pdfCanvas.classList.add("shadow-canvas");
});
}
</script>
</div> </div>
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>

View File

@@ -19,7 +19,7 @@
<p th:text="#{error.contactTip}"></p> <p th:text="#{error.contactTip}"></p>
<div id="button-group"> <div id="button-group">
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" class="btn btn-primary" target="_blank" th:text="#{error.github}"></a> <a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" class="btn btn-primary" target="_blank" th:text="#{error.github}"></a>
<a href="https://discord.gg/HYmhKj45pU" id="discord-button" class="btn btn-primary" target="_blank" th:text="#{joinDiscord}"></a> <a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" class="btn btn-primary" target="_blank" th:text="#{joinDiscord}"></a>
</div> </div>
<a th:href="@{'/'}" id="home-button" class="home-button btn btn-primary" th:text="#{goHomepage}"></a> <a th:href="@{'/'}" id="home-button" class="home-button btn btn-primary" th:text="#{goHomepage}"></a>
</div> </div>

Some files were not shown because too many files have changed in this diff Show More