Compare commits
147 Commits
v0.37.1
...
migrate-en
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
505c4bd2a7 | ||
|
|
11a5b2e79f | ||
|
|
896258f011 | ||
|
|
69c6544877 | ||
|
|
e145f25ba4 | ||
|
|
696e5ff9ca | ||
|
|
f9cf75e247 | ||
|
|
a67fd82c42 | ||
|
|
e2dd8a3d91 | ||
|
|
554c112a94 | ||
|
|
d7e6cae313 | ||
|
|
a3affe63f9 | ||
|
|
d34c44ed7b | ||
|
|
68e8a0174c | ||
|
|
4b40a0460e | ||
|
|
27fc5e9a9e | ||
|
|
3d7eb040ab | ||
|
|
82b1ab4263 | ||
|
|
c1d7217242 | ||
|
|
708ede5e26 | ||
|
|
af74f9d6c6 | ||
|
|
27ec4d5a8f | ||
|
|
779191c02b | ||
|
|
2742e0b051 | ||
|
|
ea46204c6e | ||
|
|
41b41996c5 | ||
|
|
2abf48a57b | ||
|
|
5f81ff88e8 | ||
|
|
bc3340f3ba | ||
|
|
f9963a2316 | ||
|
|
0aeb22e7c8 | ||
|
|
dbb9009cf6 | ||
|
|
21839d07e8 | ||
|
|
8004251460 | ||
|
|
1ced941bee | ||
|
|
cb4a5778ec | ||
|
|
46c6a2b599 | ||
|
|
e5627af63d | ||
|
|
6a3064f7f2 | ||
|
|
158708b696 | ||
|
|
0233086487 | ||
|
|
242aa5eae1 | ||
|
|
bf65c456d1 | ||
|
|
26720c5018 | ||
|
|
2ed07e3fcb | ||
|
|
e382d254ee | ||
|
|
507d21772d | ||
|
|
5bf050d77f | ||
|
|
976caeb79d | ||
|
|
2d3611fd00 | ||
|
|
118de1789a | ||
|
|
b56d54a35a | ||
|
|
b9bfcd59cd | ||
|
|
f8adc0f101 | ||
|
|
69d4b52b06 | ||
|
|
5e3612a9b0 | ||
|
|
b3a4597ad1 | ||
|
|
dc46172deb | ||
|
|
04696dc2aa | ||
|
|
b37457b41d | ||
|
|
46c53a9c88 | ||
|
|
5f610da671 | ||
|
|
bacc502f07 | ||
|
|
883281e4df | ||
|
|
9e8c16f313 | ||
|
|
4a7df3fd3f | ||
|
|
f725f92e1a | ||
|
|
9d2c23f612 | ||
|
|
637446fab8 | ||
|
|
196c0f4a39 | ||
|
|
f9b92eec71 | ||
|
|
3be6b0659e | ||
|
|
00e1f74f48 | ||
|
|
6ae2fddd48 | ||
|
|
c5cffdcacb | ||
|
|
d9eda14521 | ||
|
|
b865f4379f | ||
|
|
4294dc54b1 | ||
|
|
e3adb38a06 | ||
|
|
1eb40be3b6 | ||
|
|
906bfa7ab1 | ||
|
|
382f5603a8 | ||
|
|
3dd8b53f85 | ||
|
|
6a874be8e3 | ||
|
|
ff08cf41b5 | ||
|
|
107112e728 | ||
|
|
60cc613c63 | ||
|
|
67569a8f6a | ||
|
|
7130143809 | ||
|
|
16233595d8 | ||
|
|
f59e024802 | ||
|
|
a97a27afd3 | ||
|
|
94ea723326 | ||
|
|
8348caf3f0 | ||
|
|
b2dfb4f863 | ||
|
|
565208edbf | ||
|
|
8e743e2d67 | ||
|
|
3220ad2045 | ||
|
|
e690b09ed2 | ||
|
|
595da7acd9 | ||
|
|
78f1d2b843 | ||
|
|
d2fe4364c2 | ||
|
|
cb114c29de | ||
|
|
7eb7774979 | ||
|
|
10158fa408 | ||
|
|
dab6613f1b | ||
|
|
1d016df92e | ||
|
|
0631e3071c | ||
|
|
ba1b4b502c | ||
|
|
7865bf720f | ||
|
|
06abc82fbc | ||
|
|
3afacf2405 | ||
|
|
ba259a2d8d | ||
|
|
45895cd6bf | ||
|
|
66d0ad5071 | ||
|
|
c5ae576541 | ||
|
|
2bcdd8cce6 | ||
|
|
c96765f962 | ||
|
|
3830c7d1f3 | ||
|
|
05add001fb | ||
|
|
abc3ff3529 | ||
|
|
737be6c458 | ||
|
|
2e8abb7bb2 | ||
|
|
e2d75ead27 | ||
|
|
28e89a373f | ||
|
|
b4451da2f4 | ||
|
|
8353c399d2 | ||
|
|
af1b156ba6 | ||
|
|
3654743d95 | ||
|
|
58bd9b36cd | ||
|
|
d31b379b5c | ||
|
|
35c85bfeb8 | ||
|
|
64469061fd | ||
|
|
57c343910f | ||
|
|
319ba24be0 | ||
|
|
ec12470188 | ||
|
|
ec88e893c8 | ||
|
|
4ef5a0688b | ||
|
|
86438d7ad3 | ||
|
|
3b15a166c5 | ||
|
|
907a0fb4ff | ||
|
|
8b28bf66fa | ||
|
|
6bd7eadd85 | ||
|
|
a46a570c8a | ||
|
|
a64acb3126 | ||
|
|
2229f38602 | ||
|
|
901bf50d54 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1,2 +1,2 @@
|
||||
# All PRs to V1 must be approved by Frooodle
|
||||
* @Frooodle @reecebrowne @Ludy87 @DarioGii
|
||||
* @Frooodle @reecebrowne @Ludy87 @DarioGii @ConnorYoh
|
||||
|
||||
29
.github/pull_request_template.md
vendored
29
.github/pull_request_template.md
vendored
@@ -1,15 +1,34 @@
|
||||
# Description
|
||||
# Description of Changes
|
||||
|
||||
Please provide a summary of the changes, including relevant motivation and context.
|
||||
Please provide a summary of the changes, including:
|
||||
|
||||
- What was changed
|
||||
- Why the change was made
|
||||
- Any challenges encountered
|
||||
|
||||
Closes #(issue_number)
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
### General
|
||||
|
||||
- [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
|
||||
- [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable)
|
||||
- [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable)
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have attached images of the change if it is UI based
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] If my code has heavily changed functionality I have updated relevant docs on [Stirling-PDFs doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
|
||||
- [ ] My changes generate no new warnings
|
||||
|
||||
### Documentation
|
||||
|
||||
- [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed)
|
||||
- [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only)
|
||||
|
||||
### UI Changes (if applicable)
|
||||
|
||||
- [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR)
|
||||
|
||||
### Testing (if applicable)
|
||||
|
||||
- [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
|
||||
|
||||
6
.github/release.yml
vendored
6
.github/release.yml
vendored
@@ -1,10 +1,4 @@
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- Documentation
|
||||
- Test
|
||||
- Github
|
||||
|
||||
categories:
|
||||
- title: Bug Fixes
|
||||
labels:
|
||||
|
||||
51
.github/scripts/check_duplicates.py
vendored
51
.github/scripts/check_duplicates.py
vendored
@@ -1,51 +0,0 @@
|
||||
import sys
|
||||
|
||||
|
||||
def find_duplicate_keys(file_path):
|
||||
"""
|
||||
Finds duplicate keys in a properties file and returns their occurrences.
|
||||
|
||||
This function reads a properties file, identifies any keys that occur more than
|
||||
once, and returns a dictionary with these keys and the line numbers of their occurrences.
|
||||
|
||||
Parameters:
|
||||
file_path (str): The path to the properties file to be checked.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary where each key is a duplicated key in the file, and the value is a list
|
||||
of line numbers where the key occurs.
|
||||
"""
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
lines = file.readlines()
|
||||
|
||||
keys = {}
|
||||
duplicates = {}
|
||||
|
||||
for line_number, line in enumerate(lines, start=1):
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
key = line.split("=", 1)[0].strip()
|
||||
if key in keys:
|
||||
# If the key already exists, add the current line number
|
||||
duplicates.setdefault(key, []).append(line_number)
|
||||
# Also add the first instance of the key if not already done
|
||||
if keys[key] not in duplicates[key]:
|
||||
duplicates[key].insert(0, keys[key])
|
||||
else:
|
||||
# Store the line number of the first instance of the key
|
||||
keys[key] = line_number
|
||||
|
||||
return duplicates
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
failed = False
|
||||
for ar in sys.argv[1:]:
|
||||
duplicates = find_duplicate_keys(ar)
|
||||
if duplicates:
|
||||
for key, lines in duplicates.items():
|
||||
lines_str = ", ".join(map(str, lines))
|
||||
print(f"{key} duplicated in {ar} on lines {lines_str}")
|
||||
failed = True
|
||||
if failed:
|
||||
sys.exit(1)
|
||||
78
.github/scripts/check_language_properties.py
vendored
78
.github/scripts/check_language_properties.py
vendored
@@ -21,25 +21,60 @@ import argparse
|
||||
import re
|
||||
|
||||
|
||||
def find_duplicate_keys(file_path):
|
||||
"""
|
||||
Identifies duplicate keys in a .properties file.
|
||||
:param file_path: Path to the .properties file.
|
||||
:return: List of tuples (key, first_occurrence_line, duplicate_line).
|
||||
"""
|
||||
keys = {}
|
||||
duplicates = []
|
||||
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
for line_number, line in enumerate(file, start=1):
|
||||
stripped_line = line.strip()
|
||||
|
||||
# Skip empty lines and comments
|
||||
if not stripped_line or stripped_line.startswith("#"):
|
||||
continue
|
||||
|
||||
# Split the line into key and value
|
||||
if "=" in stripped_line:
|
||||
key, _ = stripped_line.split("=", 1)
|
||||
key = key.strip()
|
||||
|
||||
# Check if the key already exists
|
||||
if key in keys:
|
||||
duplicates.append((key, keys[key], line_number))
|
||||
else:
|
||||
keys[key] = line_number
|
||||
|
||||
return duplicates
|
||||
|
||||
|
||||
# Maximum size for properties files (e.g., 200 KB)
|
||||
MAX_FILE_SIZE = 200 * 1024
|
||||
|
||||
|
||||
def parse_properties_file(file_path):
|
||||
"""Parses a .properties file and returns a list of objects (including comments, empty lines, and line numbers)."""
|
||||
"""
|
||||
Parses a .properties file and returns a structured list of its contents.
|
||||
:param file_path: Path to the .properties file.
|
||||
:return: List of dictionaries representing each line in the file.
|
||||
"""
|
||||
properties_list = []
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
for line_number, line in enumerate(file, start=1):
|
||||
stripped_line = line.strip()
|
||||
|
||||
# Empty lines
|
||||
# Handle empty lines
|
||||
if not stripped_line:
|
||||
properties_list.append(
|
||||
{"line_number": line_number, "type": "empty", "content": ""}
|
||||
)
|
||||
continue
|
||||
|
||||
# Comments
|
||||
# Handle comments
|
||||
if stripped_line.startswith("#"):
|
||||
properties_list.append(
|
||||
{
|
||||
@@ -50,7 +85,7 @@ def parse_properties_file(file_path):
|
||||
)
|
||||
continue
|
||||
|
||||
# Key-value pairs
|
||||
# Handle key-value pairs
|
||||
match = re.match(r"^([^=]+)=(.*)$", line)
|
||||
if match:
|
||||
key, value = match.groups()
|
||||
@@ -67,9 +102,14 @@ def parse_properties_file(file_path):
|
||||
|
||||
|
||||
def write_json_file(file_path, updated_properties):
|
||||
"""
|
||||
Writes updated properties back to the file in their original format.
|
||||
:param file_path: Path to the .properties file.
|
||||
:param updated_properties: List of updated properties to write.
|
||||
"""
|
||||
updated_lines = {entry["line_number"]: entry for entry in updated_properties}
|
||||
|
||||
# Sort by line numbers and retain comments and empty lines
|
||||
# Sort lines by their numbers and retain comments and empty lines
|
||||
all_lines = sorted(set(updated_lines.keys()))
|
||||
|
||||
original_format = []
|
||||
@@ -88,8 +128,8 @@ def write_json_file(file_path, updated_properties):
|
||||
# Replace entries with those from the current JSON
|
||||
original_format.append(entry)
|
||||
|
||||
# Write back in the original format
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
# Write the updated content back to the file
|
||||
with open(file_path, "w", encoding="utf-8", newline="\n") as file:
|
||||
for entry in original_format:
|
||||
if entry["type"] == "comment":
|
||||
file.write(f"{entry['content']}\n")
|
||||
@@ -100,6 +140,12 @@ def write_json_file(file_path, updated_properties):
|
||||
|
||||
|
||||
def update_missing_keys(reference_file, file_list, branch=""):
|
||||
"""
|
||||
Updates missing keys in the translation files based on the reference file.
|
||||
:param reference_file: Path to the reference .properties file.
|
||||
:param file_list: List of translation files to update.
|
||||
:param branch: Branch where the files are located.
|
||||
"""
|
||||
reference_properties = parse_properties_file(reference_file)
|
||||
for file_path in file_list:
|
||||
basename_current_file = os.path.basename(os.path.join(branch, file_path))
|
||||
@@ -245,6 +291,24 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
||||
)
|
||||
else:
|
||||
report.append("2. **Test Status:** ✅ **_Passed_**")
|
||||
|
||||
if find_duplicate_keys(os.path.join(branch, file_path)):
|
||||
has_differences = True
|
||||
output = "\n".join(
|
||||
[
|
||||
f" - `{key}`: first at line {first}, duplicate at `line {duplicate}`"
|
||||
for key, first, duplicate in find_duplicate_keys(
|
||||
os.path.join(branch, file_path)
|
||||
)
|
||||
]
|
||||
)
|
||||
report.append("3. **Test Status:** ❌ **_Failed_**")
|
||||
report.append(" - **Issue:**")
|
||||
report.append(" - duplicate entries were found:")
|
||||
report.append(output)
|
||||
else:
|
||||
report.append("3. **Test Status:** ✅ **_Passed_**")
|
||||
|
||||
report.append("")
|
||||
report.append("---")
|
||||
report.append("")
|
||||
|
||||
85
.github/scripts/check_tabulator.py
vendored
85
.github/scripts/check_tabulator.py
vendored
@@ -1,85 +0,0 @@
|
||||
"""check_tabulator.py"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
def check_tabs(file_path):
|
||||
"""
|
||||
Checks for tabs in the specified file.
|
||||
|
||||
Args:
|
||||
file_path (str): The path to the file to be checked.
|
||||
|
||||
Returns:
|
||||
bool: True if tabs are found, False otherwise.
|
||||
"""
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
content = file.read()
|
||||
|
||||
if "\t" in content:
|
||||
print(f"Tab found in {file_path}")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def replace_tabs_with_spaces(file_path, replace_with=" "):
|
||||
"""
|
||||
Replaces tabs with a specified number of spaces in the file.
|
||||
|
||||
Args:
|
||||
file_path (str): The path to the file where tabs will be replaced.
|
||||
replace_with (str): The character(s) to replace tabs with. Defaults to two spaces.
|
||||
"""
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
content = file.read()
|
||||
|
||||
updated_content = content.replace("\t", replace_with)
|
||||
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
file.write(updated_content)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function to replace tabs with spaces in the provided files.
|
||||
The replacement character and files to check are taken from command line arguments.
|
||||
"""
|
||||
# Create ArgumentParser instance
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Replace tabs in files with specified characters."
|
||||
)
|
||||
|
||||
# Define optional argument `--replace_with`
|
||||
parser.add_argument(
|
||||
"--replace_with",
|
||||
default=" ",
|
||||
help="Character(s) to replace tabs with. Default is two spaces.",
|
||||
)
|
||||
|
||||
# Define argument for file paths
|
||||
parser.add_argument("files", metavar="FILE", nargs="+", help="Files to process.")
|
||||
|
||||
# Parse arguments
|
||||
args = parser.parse_args()
|
||||
|
||||
# Extract replacement characters and files from the parsed arguments
|
||||
replace_with = args.replace_with
|
||||
files_checked = args.files
|
||||
|
||||
error = False
|
||||
|
||||
for file_path in files_checked:
|
||||
if check_tabs(file_path):
|
||||
replace_tabs_with_spaces(file_path, replace_with)
|
||||
error = True
|
||||
|
||||
if error:
|
||||
print("Error: Originally found tabs in HTML files, now replaced.")
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
14
.github/workflows/PR-Demo-Comment.yml
vendored
14
.github/workflows/PR-Demo-Comment.yml
vendored
@@ -27,7 +27,8 @@ jobs:
|
||||
github.event.comment.user.login == 'LaserKaspar' ||
|
||||
github.event.comment.user.login == 'sbplat' ||
|
||||
github.event.comment.user.login == 'reecebrowne' ||
|
||||
github.event.comment.user.login == 'DarioGii'
|
||||
github.event.comment.user.login == 'DarioGii' ||
|
||||
github.event.comment.user.login == 'ConnorYoh'
|
||||
)
|
||||
outputs:
|
||||
pr_number: ${{ steps.get-pr.outputs.pr_number }}
|
||||
@@ -36,7 +37,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -81,7 +82,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -93,7 +94,7 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
@@ -102,9 +103,10 @@ jobs:
|
||||
run: ./gradlew clean build
|
||||
env:
|
||||
DOCKER_ENABLE_SECURITY: false
|
||||
STIRLING_PDF_DESKTOP_UI: false
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
||||
|
||||
- name: Get version number
|
||||
id: versionNumber
|
||||
@@ -119,7 +121,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
|
||||
- name: Build and push PR-specific image
|
||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
|
||||
30
.github/workflows/PR-Demo-cleanup.yml
vendored
30
.github/workflows/PR-Demo-cleanup.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Cleanup PR deployment
|
||||
id: cleanup
|
||||
run: |
|
||||
CLEANUP_STATUS=$(ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
|
||||
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
|
||||
if [ -d "/stirling/PR-${{ github.event.pull_request.number }}" ]; then
|
||||
echo "Found PR directory, proceeding with cleanup..."
|
||||
|
||||
@@ -57,29 +57,3 @@ jobs:
|
||||
echo "NO_CLEANUP_NEEDED"
|
||||
fi
|
||||
ENDSSH
|
||||
)
|
||||
|
||||
if [[ $CLEANUP_STATUS == *"PERFORMED_CLEANUP"* ]]; then
|
||||
echo "cleanup_performed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "cleanup_performed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Post cleanup notice to PR
|
||||
if: steps.cleanup.outputs.cleanup_performed == 'true'
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const { GITHUB_REPOSITORY } = process.env;
|
||||
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
|
||||
const prNumber = context.issue.number;
|
||||
|
||||
const commentBody = `## 🧹 Deployment Cleanup\n\n` +
|
||||
`The test deployment for this PR has been cleaned up.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: repoOwner,
|
||||
repo: repoName,
|
||||
issue_number: prNumber,
|
||||
body: commentBody
|
||||
});
|
||||
|
||||
2
.github/workflows/auto-labeler.yml
vendored
2
.github/workflows/auto-labeler.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
51
.github/workflows/build.yml
vendored
51
.github/workflows/build.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK ${{ matrix.jdk-version }}
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: ${{ matrix.jdk-version }}
|
||||
distribution: "temurin"
|
||||
@@ -58,6 +58,35 @@ jobs:
|
||||
build/reports/problems/
|
||||
retention-days: 3
|
||||
|
||||
check-licence:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "adopt"
|
||||
|
||||
- name: check the licenses for compatibility
|
||||
run: ./gradlew clean checkLicense
|
||||
|
||||
- name: FAILED - check the licenses for compatibility
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: dependencies-without-allowed-license.json
|
||||
path: |
|
||||
build/reports/dependency-license/dependencies-without-allowed-license.json
|
||||
retention-days: 3
|
||||
|
||||
docker-compose-tests:
|
||||
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
|
||||
# (github.event_name == 'pull_request' &&
|
||||
@@ -77,7 +106,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -85,31 +114,31 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Java 17
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "adopt"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
||||
|
||||
- name: Install Docker Compose
|
||||
run: |
|
||||
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.32.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.32.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: 'pip' # caching pip dependencies
|
||||
|
||||
- name: Pip requirements
|
||||
run: |
|
||||
pip install --require-hashes -r ./cucumber/requirements.txt
|
||||
pip install --require-hashes -r ./testing/cucumber/requirements.txt
|
||||
|
||||
- name: Run Docker Compose Tests
|
||||
run: |
|
||||
chmod +x ./cucumber/test_webpages.sh
|
||||
chmod +x ./test.sh
|
||||
./test.sh
|
||||
chmod +x ./testing/test_webpages.sh
|
||||
chmod +x ./testing/test.sh
|
||||
./testing/test.sh
|
||||
|
||||
8
.github/workflows/check_properties.yml
vendored
8
.github/workflows/check_properties.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
run: |
|
||||
echo "Fetching PR changed files..."
|
||||
echo "Getting list of changed files from PR..."
|
||||
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]+\.properties$' > changed_files.txt # Filter only matching property files
|
||||
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$' > changed_files.txt # Filter only matching property files
|
||||
|
||||
- name: Determine reference file test
|
||||
id: determine-file
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
// Filter for relevant files based on the PR changes
|
||||
const changedFiles = files
|
||||
.map(file => file.filename)
|
||||
.filter(file => /^src\/main\/resources\/messages_[a-zA-Z_]+\.properties$/.test(file));
|
||||
.filter(file => /^src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file));
|
||||
|
||||
console.log("Changed files:", changedFiles);
|
||||
|
||||
|
||||
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
23
.github/workflows/licenses-update.yml
vendored
23
.github/workflows/licenses-update.yml
vendored
@@ -18,30 +18,39 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Generate GitHub App Token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||
uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "adopt"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
||||
|
||||
- name: Run Gradle Command
|
||||
run: ./gradlew clean generateLicenseReport
|
||||
- name: check the licenses for compatibility
|
||||
run: ./gradlew clean checkLicense
|
||||
|
||||
- name: FAILED - check the licenses for compatibility
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: dependencies-without-allowed-license.json
|
||||
path: |
|
||||
build/reports/dependency-license/dependencies-without-allowed-license.json
|
||||
retention-days: 3
|
||||
|
||||
- name: Move and Rename License File
|
||||
run: |
|
||||
|
||||
4
.github/workflows/manage-label.yml
vendored
4
.github/workflows/manage-label.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Run Labeler
|
||||
uses: crazy-max/ghaction-github-labeler@b54af0c25861143e7c8813d7cbbf46d2c341680c # v5.1.0
|
||||
uses: crazy-max/ghaction-github-labeler@31674a3852a9074f2086abcf1c53839d466a47e7 # v5.2.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
yaml-file: .github/labels.yml
|
||||
|
||||
31
.github/workflows/multiOSReleases.yml
vendored
31
.github/workflows/multiOSReleases.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -51,19 +51,19 @@ jobs:
|
||||
file_suffix: ""
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
||||
with:
|
||||
gradle-version: 8.12
|
||||
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
file_suffix: ""
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -130,8 +130,8 @@ jobs:
|
||||
include:
|
||||
- os: windows-latest
|
||||
platform: win-
|
||||
# - os: macos-latest
|
||||
# platform: mac-
|
||||
- os: macos-latest
|
||||
platform: mac-
|
||||
# - os: ubuntu-latest
|
||||
# platform: linux-
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -139,19 +139,19 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
||||
with:
|
||||
gradle-version: 8.12
|
||||
|
||||
@@ -168,6 +168,7 @@ jobs:
|
||||
env:
|
||||
DOCKER_ENABLE_SECURITY: false
|
||||
STIRLING_PDF_DESKTOP_UI: true
|
||||
BROWSER_OPEN: true
|
||||
|
||||
# Rename and collect artifacts based on OS
|
||||
- name: Prepare artifacts
|
||||
@@ -202,14 +203,14 @@ jobs:
|
||||
include:
|
||||
- os: windows-latest
|
||||
platform: win-
|
||||
# - os: macos-latest
|
||||
# platform: mac-
|
||||
- os: macos-latest
|
||||
platform: mac-
|
||||
# - os: ubuntu-latest
|
||||
# platform: linux-
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -223,7 +224,7 @@ jobs:
|
||||
|
||||
- name: Install Cosign
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
||||
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3.8.0
|
||||
|
||||
- name: Generate key pair
|
||||
if: matrix.os == 'windows-latest'
|
||||
@@ -270,7 +271,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
44
.github/workflows/pre_commit.yml
vendored
44
.github/workflows/pre_commit.yml
vendored
@@ -2,29 +2,47 @@ name: Pre-commit
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * 1"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Generate GitHub App Token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Get GitHub App User ID
|
||||
id: get-user-id
|
||||
run: echo "user-id=$(gh api "/users/${{ steps.generate-token.outputs.app-slug }}[bot]" --jq .id)" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- id: committer
|
||||
run: |
|
||||
echo "string=${{ steps.generate-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com>" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: 3.12
|
||||
cache: 'pip' # caching pip dependencies
|
||||
@@ -35,25 +53,25 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: Set up git config
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name ${{ steps.generate-token.outputs.app-slug }}[bot]
|
||||
git config --global user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com"
|
||||
- name: git add
|
||||
run: |
|
||||
git add .
|
||||
git diff --staged --quiet || git commit -m ":file_folder: pre-commit
|
||||
> Made via .github/workflows/pre_commit.yml" || echo "pre-commit: no changes"
|
||||
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
||||
- name: Create Pull Request
|
||||
if: env.CHANGES_DETECTED == 'true'
|
||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "ci: 🤖 format everything with pre-commit"
|
||||
committer: GitHub Action <action@github.com>
|
||||
author: GitHub Action <action@github.com>
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
commit-message: ":file_folder: pre-commit"
|
||||
committer: ${{ steps.committer.outputs.string }}
|
||||
author: ${{ steps.committer.outputs.string }}
|
||||
signoff: true
|
||||
branch: pre-commit
|
||||
title: "🤖 format everything with pre-commit by <github-actions[bot]>"
|
||||
title: "🤖 format everything with pre-commit by <${{ steps.generate-token.outputs.app-slug }}>"
|
||||
body: |
|
||||
Auto-generated by [create-pull-request][1]
|
||||
Auto-generated by [create-pull-request][1] with **${{ steps.generate-token.outputs.app-slug }}**
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
draft: false
|
||||
|
||||
19
.github/workflows/push-docker.yml
vendored
19
.github/workflows/push-docker.yml
vendored
@@ -18,19 +18,19 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
||||
with:
|
||||
gradle-version: 8.12
|
||||
|
||||
@@ -38,16 +38,17 @@ jobs:
|
||||
run: ./gradlew clean build
|
||||
env:
|
||||
DOCKER_ENABLE_SECURITY: false
|
||||
STIRLING_PDF_DESKTOP_UI: false
|
||||
|
||||
- name: Install cosign
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
||||
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3.8.0
|
||||
with:
|
||||
cosign-release: "v2.4.1"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
||||
|
||||
- name: Get version number
|
||||
id: versionNumber
|
||||
@@ -67,7 +68,7 @@ jobs:
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
|
||||
uses: docker/setup-qemu-action@4574d27a4764455b42196d70a065bc6853246a25 # v3.4.0
|
||||
|
||||
- name: Convert repository owner to lowercase
|
||||
id: repoowner
|
||||
@@ -89,7 +90,7 @@ jobs:
|
||||
|
||||
- name: Build and push main Dockerfile
|
||||
id: build-push-regular
|
||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
@@ -134,7 +135,7 @@ jobs:
|
||||
|
||||
- name: Build and push Dockerfile-ultra-lite
|
||||
id: build-push-lite
|
||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
||||
if: github.ref != 'refs/heads/main'
|
||||
with:
|
||||
context: .
|
||||
@@ -165,7 +166,7 @@ jobs:
|
||||
|
||||
- name: Build and push main Dockerfile fat
|
||||
id: build-push-fat
|
||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
||||
if: github.ref != 'refs/heads/main'
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
|
||||
12
.github/workflows/releaseArtifacts.yml
vendored
12
.github/workflows/releaseArtifacts.yml
vendored
@@ -23,19 +23,19 @@ jobs:
|
||||
version: ${{ steps.versionNumber.outputs.versionNumber }}
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
||||
with:
|
||||
gradle-version: 8.12
|
||||
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
file_suffix: ""
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
run: ls -R
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
||||
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3.8.0
|
||||
|
||||
- name: Generate key pair
|
||||
run: cosign generate-key-pair
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
file_suffix: ""
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
4
.github/workflows/scorecards.yml
vendored
4
.github/workflows/scorecards.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -74,6 +74,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||
uses: github/codeql-action/upload-sarif@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
63
.github/workflows/sonarqube.yml
vendored
Normal file
63
.github/workflows/sonarqube.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Run Sonarqube
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
sonarqube:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
||||
|
||||
- name: Build and analyze with Gradle
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
DOCKER_ENABLE_SECURITY: true
|
||||
STIRLING_PDF_DESKTOP_UI: true
|
||||
run: |
|
||||
./gradlew clean build sonar \
|
||||
-Dsonar.projectKey=Stirling-Tools_Stirling-PDF \
|
||||
-Dsonar.organization=stirling-tools \
|
||||
-Dsonar.host.url=https://sonarcloud.io \
|
||||
-Dsonar.login=${SONAR_TOKEN} \
|
||||
-Dsonar.log.level=DEBUG \
|
||||
--info
|
||||
|
||||
- name: Upload Problems Report on Failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: gradle-problems-report
|
||||
path: build/reports/problems/problems-report.html
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload Sonar Logs on Failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: sonar-logs
|
||||
path: |
|
||||
.scannerwork/report-task.txt
|
||||
build/sonar/
|
||||
retention-days: 7
|
||||
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -16,12 +16,12 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: 30 days stale issues
|
||||
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
|
||||
uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 30
|
||||
|
||||
6
.github/workflows/swagger.yml
vendored
6
.github/workflows/swagger.yml
vendored
@@ -14,19 +14,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
||||
|
||||
- name: Generate Swagger documentation
|
||||
run: ./gradlew generateOpenApiDocs
|
||||
|
||||
124
.github/workflows/sync_files.yml
vendored
124
.github/workflows/sync_files.yml
vendored
@@ -1,63 +1,145 @@
|
||||
name: Sync Files
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "build.gradle"
|
||||
- "README.md"
|
||||
- "src/main/resources/messages_*.properties"
|
||||
- "src/main/resources/static/3rdPartyLicenses.json"
|
||||
- "scripts/ignore_translation.toml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync-readme:
|
||||
read_bot_entries:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
outputs:
|
||||
userName: ${{ steps.get-user-id.outputs.user_name }}
|
||||
userEmail: ${{ steps.get-user-id.outputs.user_email }}
|
||||
committer: ${{ steps.committer.outputs.committer }}
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Generate GitHub App Token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Get GitHub App User ID
|
||||
id: get-user-id
|
||||
run: |
|
||||
USER_NAME="${{ steps.generate-token.outputs.app-slug }}[bot]"
|
||||
USER_ID=$(gh api "/users/$USER_NAME" --jq .id)
|
||||
USER_EMAIL="$USER_ID+$USER_NAME@users.noreply.github.com"
|
||||
echo "user_name=$USER_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "user_email=$USER_EMAIL" >> "$GITHUB_OUTPUT"
|
||||
echo "user-id=$USER_ID" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- id: committer
|
||||
run: |
|
||||
COMMITTER="${{ steps.get-user-id.outputs.user_name }} <${{ steps.get-user-id.outputs.user_email }}>"
|
||||
echo "committer=$COMMITTER" >> "$GITHUB_OUTPUT"
|
||||
|
||||
sync-files:
|
||||
needs: ["read_bot_entries"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Generate GitHub App Token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
|
||||
with:
|
||||
app-id: ${{ vars.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: 'pip' # caching pip dependencies
|
||||
- name: Install dependencies
|
||||
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
|
||||
- name: Sync README
|
||||
run: python scripts/counter_translation.py
|
||||
|
||||
- name: Sync translation property files
|
||||
run: |
|
||||
python .github/scripts/check_language_properties.py --reference-file "src/main/resources/messages_en_GB.properties" --branch main
|
||||
|
||||
- name: Set up git config
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name ${{ needs.read_bot_entries.outputs.userName }}
|
||||
git config --global user.email ${{ needs.read_bot_entries.outputs.userEmail }}
|
||||
|
||||
- name: Run git add
|
||||
run: |
|
||||
git add .
|
||||
git diff --staged --quiet || git commit -m ":memo: Sync README
|
||||
> Made via sync_files.yml" || echo "no changes"
|
||||
git add src/main/resources/messages_*.properties
|
||||
git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "no changes"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
|
||||
|
||||
- name: Sync README.md
|
||||
run: |
|
||||
python scripts/counter_translation.py
|
||||
|
||||
- name: Run git add
|
||||
run: |
|
||||
git add README.md
|
||||
git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "no changes"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
commit-message: Update files
|
||||
committer: GitHub Action <action@github.com>
|
||||
author: GitHub Action <action@github.com>
|
||||
committer: ${{ needs.read_bot_entries.outputs.committer }}
|
||||
author: ${{ needs.read_bot_entries.outputs.committer }}
|
||||
signoff: true
|
||||
branch: sync_readme
|
||||
title: ":memo: Update README: Translation Progress Table"
|
||||
title: ":globe_with_meridians: Sync Translations + Update README Progress Table"
|
||||
body: |
|
||||
Auto-generated by [create-pull-request][1]
|
||||
### Description of Changes
|
||||
|
||||
This Pull Request was automatically generated to synchronize updates to translation files and documentation. Below are the details of the changes made:
|
||||
|
||||
#### **1. Synchronization of Translation Files**
|
||||
- Updated translation files (`messages_*.properties`) to reflect changes in the reference file `messages_en_GB.properties`.
|
||||
- Ensured consistency and synchronization across all supported language files.
|
||||
- Highlighted any missing or incomplete translations.
|
||||
|
||||
#### **2. Update README.md**
|
||||
- Generated the translation progress table in `README.md`.
|
||||
- Added a summary of the current translation status for all supported languages.
|
||||
- Included up-to-date statistics on translation coverage.
|
||||
|
||||
#### **Why these changes are necessary**
|
||||
- Keeps translation files aligned with the latest reference updates.
|
||||
- Ensures the documentation reflects the current translation progress.
|
||||
|
||||
---
|
||||
|
||||
Auto-generated by [create-pull-request][1].
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
draft: false
|
||||
delete-branch: true
|
||||
labels: Documentation,Translation,github-actions
|
||||
labels: github-actions
|
||||
sign-commits: true
|
||||
add-paths: |
|
||||
README.md
|
||||
src/main/resources/messages_*.properties
|
||||
|
||||
16
.github/workflows/testdriver.yml
vendored
16
.github/workflows/testdriver.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
DOCKER_ENABLE_SECURITY: false
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
||||
|
||||
- name: Get version number
|
||||
id: versionNumber
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
|
||||
- name: Build and push test image
|
||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
@@ -105,14 +105,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Run TestDriver.ai
|
||||
uses: testdriverai/action@47e87c5d50beeeb3da624b2d9b5c1391269d6d22 #1.0.0
|
||||
uses: testdriverai/action@f0d0f45fdd684db628baa843fe9313f3ca3a8aa8 #1.1.3
|
||||
with:
|
||||
key: ${{secrets.TESTDRIVER_API_KEY}}
|
||||
prerun: |
|
||||
@@ -122,7 +122,7 @@ jobs:
|
||||
Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--load-extension=$(pwd)/node_modules/dashcam-chrome/build", "http://${{ secrets.VPS_HOST }}:1337"
|
||||
Start-Sleep -Seconds 20
|
||||
prompt: |
|
||||
1. /run testdriver/test.yml
|
||||
1. /run testing/testdriver/test.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
FORCE_COLOR: "3"
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
73
.github/workflows/update-translations.yml
vendored
73
.github/workflows/update-translations.yml
vendored
@@ -1,73 +0,0 @@
|
||||
name: Update Translations
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- "src/main/resources/messages_en_GB.properties"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update-translations-main:
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Run Python script to check files
|
||||
id: run-check
|
||||
run: |
|
||||
echo "Running Python script to check files..."
|
||||
python .github/scripts/check_language_properties.py \
|
||||
--reference-file src/main/resources/messages_en_GB.properties \
|
||||
--branch main
|
||||
|
||||
- name: Set up git config
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Add translation keys
|
||||
run: |
|
||||
git add src/main/resources/messages_*.properties
|
||||
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
||||
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
if: env.CHANGES_DETECTED == 'true'
|
||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "Update translation files"
|
||||
committer: GitHub Action <action@github.com>
|
||||
author: GitHub Action <action@github.com>
|
||||
signoff: true
|
||||
branch: update_translation_files
|
||||
title: "Update translation files"
|
||||
add-paths: |
|
||||
src/main/resources/messages_*.properties
|
||||
body: |
|
||||
Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
draft: false
|
||||
delete-branch: true
|
||||
labels: Translation,github-actions
|
||||
sign-commits: true
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,9 +21,11 @@ pipeline/finishedFolders/
|
||||
customFiles/
|
||||
configs/
|
||||
watchedFolders/
|
||||
clientWebUI/
|
||||
!cucumber/
|
||||
!cucumber/exampleFiles/
|
||||
!cucumber/exampleFiles/example_html.zip
|
||||
exampleYmlFiles/stirling/
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
@@ -138,6 +140,7 @@ venv.bak/
|
||||
# VS Code
|
||||
/.vscode/**/*
|
||||
!/.vscode/settings.json
|
||||
!/.vscode/extensions.json
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea/
|
||||
|
||||
@@ -6,10 +6,10 @@ repos:
|
||||
args:
|
||||
- --fix
|
||||
- --line-length=127
|
||||
files: ^((.github/scripts|scripts)/.+)?[^/]+\.py$
|
||||
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
|
||||
exclude: (split_photos.py)
|
||||
- id: ruff-format
|
||||
files: ^((.github/scripts|scripts)/.+)?[^/]+\.py$
|
||||
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
|
||||
exclude: (split_photos.py)
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.3.0
|
||||
@@ -19,39 +19,18 @@ repos:
|
||||
- --ignore-words-list=
|
||||
- --skip="./.*,*.csv,*.json,*.ambr"
|
||||
- --quiet-level=2
|
||||
files: \.(properties|html|css|js|py|md)$
|
||||
files: \.(html|css|js|py|md)$
|
||||
exclude: (.vscode|.devcontainer|src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.22.0
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
||||
rev: 3.0.0
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
files: ^.*(\.bash|\.sh|\.ksh|\.zsh)$
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
files: ^.*(\.js|\.java|\.py|\.yml)$
|
||||
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js$)
|
||||
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js|\.github/workflows/.*$)
|
||||
- id: trailing-whitespace
|
||||
files: ^.*(\.js|\.java|\.py|\.yml)$
|
||||
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js$)
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: check-duplicate-properties-keys
|
||||
name: Check Duplicate Properties Keys
|
||||
entry: python .github/scripts/check_duplicates.py
|
||||
language: python
|
||||
files: ^(src)/.+\.properties$
|
||||
- id: check-html-tabs
|
||||
name: Check HTML for tabs
|
||||
description: Ensures HTML/CSS/JS files do not contain tab characters
|
||||
# args: ["--replace_with= "]
|
||||
entry: python .github/scripts/check_tabulator.py
|
||||
language: python
|
||||
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js$)
|
||||
files: ^.*(\.html|\.css|\.js)$
|
||||
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js|\.github/workflows/.*$)
|
||||
|
||||
23
.vscode/extensions.json
vendored
Normal file
23
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"elagil.pre-commit-helper", // Support for pre-commit hooks to enforce code quality
|
||||
"josevseb.google-java-format-for-vs-code", // Google Java code formatter to follow the Google Java Style Guide
|
||||
"ms-python.black-formatter", // Python code formatter using Black
|
||||
"ms-python.flake8", // Flake8 linter for Python to enforce code quality
|
||||
"ms-python.python", // Official Microsoft Python extension with IntelliSense, debugging, and Jupyter support
|
||||
// "ms-vscode-remote.remote-containers", // Support for remote development with containers (Docker, Dev Containers)
|
||||
// "ms-vscode-remote.vscode-remote-extensionpack", // Remote Development Pack for SSH, WSL, and Containers
|
||||
"Oracle.oracle-java", // Oracle Java extension with additional features for Java development
|
||||
"redhat.java", // Java support by Red Hat with IntelliSense, debugging, and code navigation
|
||||
"streetsidesoftware.code-spell-checker", // Spell checker for code to avoid typos
|
||||
"vmware.vscode-boot-dev-pack", // Developer tools for Spring Boot by VMware
|
||||
"vmware.vscode-spring-boot", // Spring Boot tools by VMware for enhanced Spring development
|
||||
"vscjava.vscode-gradle", // Gradle extension for build and automation support
|
||||
"vscjava.vscode-java-debug", // Debugging support for Java projects
|
||||
"vscjava.vscode-java-dependency", // Java dependency management within VS Code
|
||||
"vscjava.vscode-java-pack", // Java Extension Pack with essential Java tools for VS Code
|
||||
"vscjava.vscode-java-test", // Java test framework for running and debugging tests in VS Code
|
||||
"vscjava.vscode-spring-boot-dashboard", // Spring Boot dashboard for managing and visualizing Spring Boot applications
|
||||
"vscjava.vscode-spring-initializr" // Support for Spring Initializr to create new Spring projects
|
||||
]
|
||||
}
|
||||
121
.vscode/settings.json
vendored
121
.vscode/settings.json
vendored
@@ -2,54 +2,147 @@
|
||||
"java.compile.nullAnalysis.mode": "automatic",
|
||||
"files.eol": "auto",
|
||||
"java.configuration.updateBuildConfiguration": "interactive",
|
||||
"black-formatter.args": ["--line-length", "127"],
|
||||
"flake8.args": ["--max-line-length", "127"],
|
||||
"pylint.args": ["max-line-length", "127"],
|
||||
"black-formatter.args": [
|
||||
"--line-length",
|
||||
"127"
|
||||
],
|
||||
"flake8.args": [
|
||||
"--max-line-length",
|
||||
"127"
|
||||
],
|
||||
"[java]": {
|
||||
"editor.tabSize": 4,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.rulers": [127]
|
||||
"editor.rulers": [
|
||||
127
|
||||
],
|
||||
"editor.defaultFormatter": "josevseb.google-java-format-for-vs-code"
|
||||
},
|
||||
"[python]": {
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.rulers": [127]
|
||||
"editor.rulers": [
|
||||
127
|
||||
]
|
||||
},
|
||||
"[gradle-build]": {
|
||||
"editor.tabSize": 4,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.rulers": [127]
|
||||
"editor.rulers": [
|
||||
127
|
||||
]
|
||||
},
|
||||
"[gradle]": {
|
||||
"editor.tabSize": 4,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.rulers": [127]
|
||||
"editor.rulers": [
|
||||
127
|
||||
]
|
||||
},
|
||||
"[html]": {
|
||||
"editor.tabSize": 2,
|
||||
"editor.rulers": [127],
|
||||
"editor.rulers": [
|
||||
127
|
||||
],
|
||||
"files.trimFinalNewlines": false,
|
||||
"files.insertFinalNewline": false
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.tabSize": 2,
|
||||
"editor.rulers": [127]
|
||||
"editor.rulers": [
|
||||
127
|
||||
]
|
||||
},
|
||||
"[yaml]": {
|
||||
"files.trimFinalNewlines": false,
|
||||
"files.insertFinalNewline": false
|
||||
},
|
||||
"diffEditor.maxComputationTime": 0,
|
||||
"editor.wordSegmenterLocales": null,
|
||||
"editor.guides.bracketPairs": "active",
|
||||
"editor.guides.bracketPairsHorizontal": "active",
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimFinalNewlines": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.autoSave": "onFocusChange",
|
||||
"files.autoSaveWhenNoErrors": true,
|
||||
"diffEditor.maxComputationTime": 0,
|
||||
"editor.wordSegmenterLocales": "",
|
||||
"editor.guides.bracketPairs": "active",
|
||||
"editor.guides.bracketPairsHorizontal": "active",
|
||||
"editor.indentSize": "tabSize",
|
||||
"editor.stickyScroll.enabled": false,
|
||||
"editor.minimap.enabled": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.insertSpaces": true,
|
||||
"java.format.enabled": true,
|
||||
"java.format.settings.profile": "GoogleStyle",
|
||||
"java.format.settings.google.version": "1.25.2",
|
||||
"java.format.settings.google.mode": "jar-file",
|
||||
"java.format.settings.google.extra": "--aosp --skip-sorting-imports"
|
||||
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting",
|
||||
// (DE) Aktiviert Kommentare im Java-Format.
|
||||
// (EN) Enables comments in Java formatting.
|
||||
// "java.format.comments.enabled": true,
|
||||
// (DE) Generiert automatisch Kommentare im Code.
|
||||
// (EN) Automatically generates comments in code.
|
||||
// "java.codeGeneration.generateComments": true,
|
||||
// https://github.com/redhat-developer/vscode-java/blob/master/document/_java.learnMoreAboutCleanUps.md#java-clean-ups
|
||||
"java.saveActions.cleanup": true,
|
||||
"java.cleanup.actions": [
|
||||
"invertEquals", // Inverts calls to Object.equals(Object) and String.equalsIgnoreCase(String) to avoid useless null pointer exception.
|
||||
"instanceofPatternMatch" // Replaces instanceof checks with pattern matching.
|
||||
],
|
||||
// (DE) Aktiviert die Code-Vervollständigung für Java.
|
||||
// (EN) Enables code completion for Java.
|
||||
"java.completion.engine": "dom",
|
||||
"java.completion.enabled": true,
|
||||
"java.completion.importOrder": [
|
||||
"java",
|
||||
"javax",
|
||||
"org",
|
||||
"com",
|
||||
"net",
|
||||
"io",
|
||||
"jakarta",
|
||||
"lombok",
|
||||
"me",
|
||||
"stirling",
|
||||
],
|
||||
"java.project.resourceFilters": [
|
||||
".devcontainer/",
|
||||
".git/",
|
||||
".github/",
|
||||
".gradle/",
|
||||
".venv/",
|
||||
".venv*/",
|
||||
".vscode/",
|
||||
"bin/",
|
||||
"build/",
|
||||
"configs/",
|
||||
"customFiles/",
|
||||
"docs/",
|
||||
"exampleYmlFiles",
|
||||
"gradle/",
|
||||
"images/",
|
||||
"logs/",
|
||||
"pipeline/",
|
||||
"scripts/",
|
||||
"testings/",
|
||||
".git-blame-ignore-revs",
|
||||
".gitattributes",
|
||||
".gitignore",
|
||||
".pre-commit-config.yaml",
|
||||
],
|
||||
// Enables signature help in Java.
|
||||
"java.signatureHelp.enabled": true,
|
||||
// Enables detailed signature help descriptions.
|
||||
"java.signatureHelp.description.enabled": true,
|
||||
// Downloads sources for Maven dependencies.
|
||||
"java.maven.downloadSources": true,
|
||||
// Enables Gradle project import.
|
||||
"java.import.gradle.enabled": true,
|
||||
// Downloads sources for Eclipse projects.
|
||||
"java.eclipse.downloadSources": true,
|
||||
// Enables import of the Gradle wrapper.
|
||||
"java.import.gradle.wrapper.enabled": true,
|
||||
"spring.initializr.defaultLanguage": "Java",
|
||||
"spring.initializr.defaultGroupId": "stirling.software.SPDF",
|
||||
"spring.initializr.defaultArtifactId": "SPDF",
|
||||
"cSpell.enabled": false,
|
||||
}
|
||||
|
||||
@@ -39,6 +39,16 @@ Stirling-PDF is built using:
|
||||
2. Install Docker and JDK17 if not already installed.
|
||||
|
||||
3. Install a recommended Java IDE such as Eclipse, IntelliJ, or VSCode
|
||||
1. Only VSCode
|
||||
1. Open VS Code.
|
||||
2. When prompted, install the recommended extensions.
|
||||
3. Alternatively, open the command palette (`Ctrl + Shift + P` or `Cmd + Shift + P` on macOS) and run:
|
||||
|
||||
```sh
|
||||
Extensions: Show Recommended Extensions
|
||||
```
|
||||
|
||||
4. Install the required extensions from the list.
|
||||
|
||||
4. Lombok Setup
|
||||
Stirling-PDF uses Lombok to reduce boilerplate code. Some IDEs, like Eclipse, don't support Lombok out of the box. To set up Lombok in your development environment:
|
||||
@@ -575,42 +585,3 @@ In your Thymeleaf templates, use the `#{key}` syntax to reference the new transl
|
||||
```
|
||||
|
||||
Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization.
|
||||
|
||||
|
||||
## Managing Dependencies
|
||||
|
||||
When adding new dependencies or updating existing ones in Stirling-PDF, follow these steps to ensure proper verification and security:
|
||||
|
||||
1. Update the dependency in `build.gradle`:
|
||||
```groovy
|
||||
dependencies {
|
||||
// Add or update your dependency
|
||||
implementation "com.example:new-library:1.2.3"
|
||||
}
|
||||
```
|
||||
|
||||
2. Generate new verification metadata and keys:
|
||||
```bash
|
||||
# Generate verification metadata with signatures and checksums
|
||||
./gradlew clean dependencies buildEnvironment --write-verification-metadata sha256,pgp
|
||||
|
||||
# Export the .keys file
|
||||
./gradlew --export-keys
|
||||
```
|
||||
|
||||
3. Files to commit:
|
||||
- `build.gradle` - Your dependency changes
|
||||
- `gradle/verification-metadata.xml` - Contains verification rules and checksums
|
||||
- `gradle/verification-keyring.keys` - Contains PGP keys in text format
|
||||
|
||||
4. Verify the build works with the new verification:
|
||||
```bash
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
5. Before committing, check:
|
||||
- Verify any new BOM files are properly handled in verification metadata
|
||||
- Review the changes in `verification-metadata.xml` to ensure they match your dependency updates
|
||||
|
||||
This ensures dependencies are properly verified and secure while maintaining transparency in the repository.
|
||||
|
||||
|
||||
79
Dockerfile
79
Dockerfile
@@ -1,5 +1,5 @@
|
||||
# Main stage
|
||||
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
|
||||
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
|
||||
|
||||
# Copy necessary files
|
||||
COPY scripts /scripts
|
||||
@@ -25,49 +25,66 @@ LABEL org.opencontainers.image.keywords="PDF, manipulation, merge, split, conver
|
||||
# Set Environment Variables
|
||||
ENV DOCKER_ENABLE_SECURITY=false \
|
||||
VERSION_TAG=$VERSION_TAG \
|
||||
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
||||
JAVA_TOOL_OPTIONS="-XX:+UnlockExperimentalVMOptions \
|
||||
-XX:MaxRAMPercentage=75 \
|
||||
-XX:InitiatingHeapOccupancyPercent=20 \
|
||||
-XX:+G1PeriodicGCInvokesConcurrent \
|
||||
-XX:G1PeriodicGCInterval=10000 \
|
||||
-XX:+UseStringDeduplication \
|
||||
-XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||
HOME=/home/stirlingpdfuser \
|
||||
PUID=1000 \
|
||||
PGID=1000 \
|
||||
UMASK=022
|
||||
UMASK=022 \
|
||||
PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
|
||||
UNO_PATH=/usr/lib/libreoffice/program \
|
||||
URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc
|
||||
|
||||
|
||||
# JDK for app
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||
apk upgrade --no-cache -a && \
|
||||
apk add --no-cache \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
tini \
|
||||
bash \
|
||||
curl \
|
||||
qpdf \
|
||||
shadow \
|
||||
su-exec \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
openjdk21-jre \
|
||||
# Doc conversion
|
||||
libreoffice \
|
||||
# pdftohtml
|
||||
poppler-utils \
|
||||
# OCR MY PDF (unpaper for descew and other advanced features)
|
||||
tesseract-ocr-data-eng \
|
||||
# CV
|
||||
py3-opencv \
|
||||
# python3/pip
|
||||
python3 \
|
||||
py3-pip && \
|
||||
# uno unoconv and HTML
|
||||
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
tini \
|
||||
bash \
|
||||
curl \
|
||||
qpdf \
|
||||
shadow \
|
||||
su-exec \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
openjdk21-jre \
|
||||
# Doc conversion
|
||||
gcompat \
|
||||
libc6-compat \
|
||||
libreoffice \
|
||||
# pdftohtml
|
||||
poppler-utils \
|
||||
# OCR MY PDF (unpaper for descew and other advanced features)
|
||||
tesseract-ocr-data-eng \
|
||||
# CV
|
||||
py3-opencv \
|
||||
python3 \
|
||||
py3-pip \
|
||||
py3-pillow@testing \
|
||||
py3-pdf2image@testing && \
|
||||
python3 -m venv /opt/venv && \
|
||||
export PATH="/opt/venv/bin:$PATH" && \
|
||||
pip install --upgrade pip && \
|
||||
pip install --no-cache-dir --upgrade unoserver weasyprint && \
|
||||
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||
ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
|
||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
||||
fc-cache -f -v && \
|
||||
chmod +x /scripts/* && \
|
||||
chmod +x /scripts/init.sh && \
|
||||
# User permissions
|
||||
# User permissions
|
||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
@@ -76,4 +93,4 @@ EXPOSE 8080/tcp
|
||||
|
||||
# Set user and run command
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 0.0.0.0"]
|
||||
@@ -9,10 +9,11 @@ COPY . .
|
||||
|
||||
# Build the application with DOCKER_ENABLE_SECURITY=false
|
||||
RUN DOCKER_ENABLE_SECURITY=true \
|
||||
STIRLING_PDF_DESKTOP_UI=false \
|
||||
./gradlew clean build
|
||||
|
||||
# Main stage
|
||||
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
|
||||
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
|
||||
|
||||
# Copy necessary files
|
||||
COPY scripts /scripts
|
||||
@@ -25,18 +26,27 @@ ARG VERSION_TAG
|
||||
# Set Environment Variables
|
||||
ENV DOCKER_ENABLE_SECURITY=false \
|
||||
VERSION_TAG=$VERSION_TAG \
|
||||
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
||||
JAVA_TOOL_OPTIONS="-XX:+UnlockExperimentalVMOptions \
|
||||
-XX:MaxRAMPercentage=75 \
|
||||
-XX:InitiatingHeapOccupancyPercent=20 \
|
||||
-XX:+G1PeriodicGCInvokesConcurrent \
|
||||
-XX:G1PeriodicGCInterval=10000 \
|
||||
-XX:+UseStringDeduplication \
|
||||
-XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||
HOME=/home/stirlingpdfuser \
|
||||
PUID=1000 \
|
||||
PGID=1000 \
|
||||
UMASK=022 \
|
||||
FAT_DOCKER=true \
|
||||
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
|
||||
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
|
||||
PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
|
||||
UNO_PATH=/usr/lib/libreoffice/program \
|
||||
URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc
|
||||
|
||||
|
||||
# JDK for app
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||
apk upgrade --no-cache -a && \
|
||||
apk add --no-cache \
|
||||
@@ -51,20 +61,29 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
||||
openssl-dev \
|
||||
openjdk21-jre \
|
||||
# Doc conversion
|
||||
gcompat \
|
||||
libc6-compat \
|
||||
libreoffice \
|
||||
# pdftohtml
|
||||
poppler-utils \
|
||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||
qpdf \
|
||||
tesseract-ocr-data-eng \
|
||||
|
||||
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
|
||||
# CV
|
||||
py3-opencv \
|
||||
# python3/pip
|
||||
python3 \
|
||||
py3-pip && \
|
||||
# uno unoconv and HTML
|
||||
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
|
||||
py3-opencv \
|
||||
python3 \
|
||||
py3-pip \
|
||||
py3-pillow@testing \
|
||||
py3-pdf2image@testing && \
|
||||
python3 -m venv /opt/venv && \
|
||||
export PATH="/opt/venv/bin:$PATH" && \
|
||||
pip install --upgrade pip && \
|
||||
pip install --no-cache-dir --upgrade unoserver weasyprint && \
|
||||
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||
ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
|
||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
||||
fc-cache -f -v && \
|
||||
@@ -76,7 +95,6 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
|
||||
EXPOSE 8080/tcp
|
||||
|
||||
# Set user and run command
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 0.0.0.0"]
|
||||
@@ -1,5 +1,5 @@
|
||||
# use alpine
|
||||
FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
|
||||
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
||||
@@ -7,7 +7,13 @@ ARG VERSION_TAG
|
||||
ENV DOCKER_ENABLE_SECURITY=false \
|
||||
HOME=/home/stirlingpdfuser \
|
||||
VERSION_TAG=$VERSION_TAG \
|
||||
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
||||
JAVA_TOOL_OPTIONS="-XX:+UnlockExperimentalVMOptions \
|
||||
-XX:MaxRAMPercentage=75 \
|
||||
-XX:InitiatingHeapOccupancyPercent=20 \
|
||||
-XX:+G1PeriodicGCInvokesConcurrent \
|
||||
-XX:G1PeriodicGCInterval=10000 \
|
||||
-XX:+UseStringDeduplication \
|
||||
-XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||
PUID=1000 \
|
||||
PGID=1000 \
|
||||
UMASK=022
|
||||
|
||||
@@ -18,9 +18,7 @@ Any SVG flags are fine; most of the current ones were sourced from [here](https:
|
||||
For example, to add Polish, you would add:
|
||||
|
||||
```html
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL">
|
||||
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
|
||||
</a>
|
||||
<a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'pl_PL')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL"> <img th:src="@{'/images/flags/pl.svg'}" alt="icon" width="20" height="15"> Polski</a>
|
||||
```
|
||||
|
||||
The `data-bs-language-code` is the code used to reference the file in the next step.
|
||||
|
||||
70
README.md
70
README.md
@@ -3,7 +3,6 @@
|
||||
|
||||
[](https://hub.docker.com/r/frooodle/s-pdf)
|
||||
[](https://discord.gg/HYmhKj45pU)
|
||||
[](https://github.com/Stirling-Tools/Stirling-PDF/)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/Stirling-Tools/Stirling-PDF)
|
||||
[](https://github.com/Stirling-Tools/stirling-pdf)
|
||||
|
||||
@@ -113,49 +112,50 @@ Visit our comprehensive documentation at [docs.stirlingpdf.com](https://docs.sti
|
||||
|
||||
## Supported Languages
|
||||
|
||||
Stirling-PDF currently supports 38 languages!
|
||||
Stirling-PDF currently supports 39 languages!
|
||||
|
||||
| Language | Progress |
|
||||
| -------------------------------------------- | -------------------------------------- |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| English (English) (en_GB) |  |
|
||||
| English (US) (en_US) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Persian (فارسی) (fa_IR) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Persian (فارسی) (fa_IR) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Slovenian (Slovenščina) (sl_SI) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
|
||||
|
||||
## Stirling PDF Enterprise
|
||||
|
||||
164
allowed-licenses.json
Normal file
164
allowed-licenses.json
Normal file
@@ -0,0 +1,164 @@
|
||||
{
|
||||
"allowedLicenses": [
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "BSD License"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "The BSD License"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "BSD-2-Clause"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "BSD 2-Clause License"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "The 2-Clause BSD License"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "BSD-3-Clause"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "The BSD 3-Clause License (BSD3)"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "BSD-4 License"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "MIT License"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "The MIT License"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.github.jai-imageio:jai-imageio-core",
|
||||
"moduleLicense": "LICENSE.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.github.jai-imageio:jai-imageio-jpeg2000",
|
||||
"moduleLicense": "LICENSE-JJ2000.txt, LICENSE-Sun.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Apache 2"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Apache 2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Apache-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Apache-2.0 License"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Apache License 2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Apache License Version 2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Apache License, Version 2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "The Apache License, Version 2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.nimbusds:oauth2-oidc-sdk",
|
||||
"moduleLicense": "\"Apache License, version 2.0\";link=\"https://www.apache.org/licenses/LICENSE-2.0.html\""
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "MPL 2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "UnboundID SCIM2 SDK Free Use License"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "GPL2 w/ CPE"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "GPLv2+CE"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "GNU GENERAL PUBLIC LICENSE, Version 2 + Classpath Exception"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.martiansoftware:jsap",
|
||||
"moduleLicense": "LGPL"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.hibernate.orm:hibernate-core",
|
||||
"moduleLicense": "GNU Library General Public License v2.1 or later"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Eclipse Public License - v 1.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Eclipse Public License v. 2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Eclipse Public License - v 2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Eclipse Public License - Version 2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Eclipse Public License, Version 2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Ubuntu Font Licence 1.0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Bouncy Castle Licence"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "Public Domain, per Creative Commons CC0"
|
||||
},
|
||||
{
|
||||
"moduleName": ".*",
|
||||
"moduleLicense": "The W3C License"
|
||||
}
|
||||
]
|
||||
}
|
||||
141
build.gradle
141
build.gradle
@@ -5,28 +5,27 @@ plugins {
|
||||
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
||||
id "io.swagger.swaggerhub" version "1.3.2"
|
||||
id "edu.sc.seis.launch4j" version "3.0.6"
|
||||
id "com.diffplug.spotless" version "7.0.1"
|
||||
id "com.diffplug.spotless" version "7.0.2"
|
||||
id "com.github.jk1.dependency-license-report" version "2.9"
|
||||
//id "nebula.lint" version "19.0.3"
|
||||
id("org.panteleyev.jpackageplugin") version "1.6.0"
|
||||
//id "nebula.lint" version "19.0.3"
|
||||
id("org.panteleyev.jpackageplugin") version "1.6.1"
|
||||
id "org.sonarqube" version "6.0.1.5171"
|
||||
}
|
||||
|
||||
import com.github.jk1.license.render.*
|
||||
|
||||
ext {
|
||||
springBootVersion = "3.4.1"
|
||||
pdfboxVersion = "3.0.3"
|
||||
logbackVersion = "1.5.7"
|
||||
springBootVersion = "3.4.2"
|
||||
pdfboxVersion = "3.0.4"
|
||||
imageioVersion = "3.12.0"
|
||||
lombokVersion = "1.18.36"
|
||||
bouncycastleVersion = "1.79"
|
||||
bouncycastleVersion = "1.80"
|
||||
springSecuritySamlVersion = "6.4.2"
|
||||
openSamlVersion = "4.3.2"
|
||||
}
|
||||
|
||||
group = "stirling.software"
|
||||
version = "0.37.1"
|
||||
|
||||
version = "0.42.0"
|
||||
|
||||
java {
|
||||
// 17 is lowest but we support and recommend 21
|
||||
@@ -35,14 +34,13 @@ java {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
maven { url "https://build.shibboleth.net/maven/releases" }
|
||||
maven { url "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
|
||||
|
||||
maven { url = "https://build.shibboleth.net/maven/releases" }
|
||||
maven { url = "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
|
||||
}
|
||||
|
||||
licenseReport {
|
||||
renderers = [new JsonReportRenderer()]
|
||||
allowedLicensesFile = new File("$projectDir/allowed-licenses.json")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -66,7 +64,7 @@ sourceSets {
|
||||
}
|
||||
|
||||
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
|
||||
exclude "stirling/software/SPDF/UI/impl/**"
|
||||
exclude "stirling/software/SPDF/UI/impl/**"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -111,18 +109,15 @@ def getMacVersion(String version) {
|
||||
|
||||
jpackage {
|
||||
input = "build/libs"
|
||||
|
||||
destination = "${projectDir}/build/jpackage"
|
||||
mainJar = "Stirling-PDF-${project.version}.jar"
|
||||
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"
|
||||
|
||||
appDescription = "Stirling PDF - Your Local PDF Editor"
|
||||
icon = "src/main/resources/static/favicon.ico"
|
||||
|
||||
|
||||
verbose = true
|
||||
// mainClass = "org.springframework.boot.loader.launch.JarLauncher"
|
||||
|
||||
// JVM Options
|
||||
javaOptions = [
|
||||
@@ -136,20 +131,15 @@ jpackage {
|
||||
"--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED",
|
||||
"--add-opens=java.desktop/sun.awt.windows=ALL-UNNAMED",
|
||||
"--add-opens=java.desktop/sun.lwawt=ALL-UNNAMED",
|
||||
"--add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED"
|
||||
"--add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED",
|
||||
]
|
||||
|
||||
|
||||
verbose = true
|
||||
|
||||
destination = "${projectDir}/build/jpackage"
|
||||
|
||||
// Windows-specific configuration
|
||||
windows {
|
||||
launcherAsService = false
|
||||
appVersion = project.version
|
||||
launcherAsService = false
|
||||
appVersion = project.version
|
||||
|
||||
winConsole = false
|
||||
winConsole = false
|
||||
winMenu = true // Creates start menu entry
|
||||
winShortcut = true // Creates desktop shortcut
|
||||
winShortcutPrompt = true // Lets user choose whether to create shortcuts
|
||||
@@ -165,7 +155,7 @@ jpackage {
|
||||
|
||||
// macOS-specific configuration
|
||||
mac {
|
||||
appVersion = getMacVersion(project.version.toString())
|
||||
appVersion = getMacVersion(project.version.toString())
|
||||
icon = "src/main/resources/static/favicon.icns"
|
||||
type = "dmg"
|
||||
macPackageIdentifier = "com.stirling.software.pdf"
|
||||
@@ -189,7 +179,7 @@ jpackage {
|
||||
|
||||
// Linux-specific configuration
|
||||
linux {
|
||||
appVersion = project.version
|
||||
appVersion = project.version
|
||||
icon = "src/main/resources/static/favicon.png"
|
||||
type = "deb" // Can also use "rpm" for Red Hat-based systems
|
||||
|
||||
@@ -237,9 +227,9 @@ launch4j {
|
||||
outfile="Stirling-PDF.exe"
|
||||
|
||||
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
||||
headerType = "gui"
|
||||
headerType = "gui"
|
||||
} else {
|
||||
headerType = "console"
|
||||
headerType = "console"
|
||||
}
|
||||
jarTask = tasks.bootJar
|
||||
|
||||
@@ -247,13 +237,11 @@ launch4j {
|
||||
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
||||
|
||||
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
||||
variables=["BROWSER_OPEN=true", "STIRLING_PDF_DESKTOP_UI=true"]
|
||||
variables=["BROWSER_OPEN=true", "STIRLING_PDF_DESKTOP_UI=true"]
|
||||
} else {
|
||||
variables=["BROWSER_OPEN=true"]
|
||||
variables=["BROWSER_OPEN=true"]
|
||||
}
|
||||
|
||||
|
||||
|
||||
jreMinVersion="17"
|
||||
|
||||
mutexName="Stirling-PDF"
|
||||
@@ -272,14 +260,25 @@ spotless {
|
||||
|
||||
googleJavaFormat("1.25.2").aosp().reorderImports(false)
|
||||
|
||||
importOrder("java", "javax", "org", "com", "net", "io")
|
||||
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
|
||||
toggleOffOn()
|
||||
trimTrailingWhitespace()
|
||||
indentWithSpaces()
|
||||
leadingTabsToSpaces()
|
||||
endWithNewline()
|
||||
}
|
||||
}
|
||||
|
||||
sonar {
|
||||
properties {
|
||||
property "sonar.projectKey", "Stirling-Tools_Stirling-PDF"
|
||||
property "sonar.organization", "stirling-tools"
|
||||
|
||||
property "sonar.exclusions", "**/build-wrapper-dump.json, src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
|
||||
property "sonar.coverage.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
|
||||
property "sonar.cpd.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
|
||||
}
|
||||
}
|
||||
|
||||
//gradleLint {
|
||||
// rules=['unused-dependency']
|
||||
// }
|
||||
@@ -294,26 +293,36 @@ configurations.all {
|
||||
}
|
||||
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"
|
||||
//tmp for security bumps
|
||||
implementation 'ch.qos.logback:logback-core:1.5.15'
|
||||
implementation 'ch.qos.logback:logback-classic:1.5.15'
|
||||
|
||||
|
||||
// Exclude vulnerable BouncyCastle version used in tableau
|
||||
configurations.all {
|
||||
exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15on'
|
||||
exclude group: 'org.bouncycastle', module: 'bcutil-jdk15on'
|
||||
exclude group: 'org.bouncycastle', module: 'bcmail-jdk15on'
|
||||
}
|
||||
|
||||
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
|
||||
implementation 'org.apache.commons:commons-compress:1.26.0'
|
||||
implementation "me.friwi:jcefmaven:127.3.1"
|
||||
implementation "org.openjfx:javafx-controls:21"
|
||||
implementation "org.openjfx:javafx-swing:21"
|
||||
}
|
||||
|
||||
//security updates
|
||||
implementation "org.springframework:spring-webmvc:6.2.1"
|
||||
implementation "org.springframework:spring-webmvc:6.2.3"
|
||||
|
||||
implementation("io.github.pixee:java-security-toolkit:1.2.1")
|
||||
|
||||
// implementation "org.yaml:snakeyaml:2.2"
|
||||
implementation 'com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4'
|
||||
|
||||
// Exclude Tomcat and include Jetty
|
||||
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
|
||||
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
|
||||
|
||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
||||
implementation 'com.posthog.java:posthog:1.1.1'
|
||||
implementation 'com.posthog.java:posthog:1.2.0'
|
||||
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
||||
|
||||
|
||||
@@ -323,20 +332,20 @@ dependencies {
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
||||
|
||||
implementation "org.springframework.session:spring-session-core:$springBootVersion"
|
||||
implementation "org.springframework:spring-jdbc:6.2.1"
|
||||
implementation "org.springframework.session:spring-session-core:3.4.1"
|
||||
implementation "org.springframework:spring-jdbc:6.2.3"
|
||||
|
||||
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
|
||||
runtimeOnly "com.h2database:h2:2.3.232"
|
||||
runtimeOnly "org.postgresql:postgresql:42.7.4"
|
||||
runtimeOnly "org.postgresql:postgresql:42.7.5"
|
||||
constraints {
|
||||
implementation "org.opensaml:opensaml-core:$openSamlVersion"
|
||||
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
|
||||
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
|
||||
}
|
||||
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
|
||||
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
|
||||
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
|
||||
implementation 'com.coveo:saml-client:5.0.0'
|
||||
|
||||
|
||||
@@ -380,6 +389,8 @@ dependencies {
|
||||
implementation ("org.apache.pdfbox:pdfbox:$pdfboxVersion") {
|
||||
exclude group: "commons-logging", module: "commons-logging"
|
||||
}
|
||||
implementation "org.apache.pdfbox:preflight:$pdfboxVersion"
|
||||
|
||||
|
||||
implementation ("org.apache.pdfbox:xmpbox:$pdfboxVersion") {
|
||||
exclude group: "commons-logging", module: "commons-logging"
|
||||
@@ -397,7 +408,7 @@ dependencies {
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
||||
implementation "io.micrometer:micrometer-core:1.14.3"
|
||||
implementation "io.micrometer:micrometer-core:1.14.4"
|
||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||
implementation "org.commonmark:commonmark:0.24.0"
|
||||
@@ -406,8 +417,8 @@ dependencies {
|
||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
||||
implementation "com.fathzer:javaluator:3.0.5"
|
||||
|
||||
implementation 'org.jsoup:jsoup:1.18.3'
|
||||
|
||||
implementation 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
|
||||
|
||||
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||
@@ -431,13 +442,13 @@ task writeVersion {
|
||||
}
|
||||
|
||||
swaggerhubUpload {
|
||||
//dependsOn generateOpenApiDocs // Depends on your task generating Swagger docs
|
||||
api "Stirling-PDF" // The name of your API on SwaggerHub
|
||||
owner "Frooodle" // Your SwaggerHub username (or organization name)
|
||||
version project.version // The version of your API
|
||||
inputFile "./SwaggerDoc.json" // The path to your Swagger docs
|
||||
token "${System.getenv("SWAGGERHUB_API_KEY")}" // Your SwaggerHub API key, passed as an environment variable
|
||||
oas "3.0.0" // The version of the OpenAPI Specification you"re using
|
||||
// dependsOn = generateOpenApiDocs // Depends on your task generating Swagger docs
|
||||
api = "Stirling-PDF" // The name of your API on SwaggerHub
|
||||
owner = "Frooodle" // Your SwaggerHub username (or organization name)
|
||||
version = project.version // The version of your API
|
||||
inputFile = "./SwaggerDoc.json" // The path to your Swagger docs
|
||||
token = "${System.getenv("SWAGGERHUB_API_KEY")}" // Your SwaggerHub API key, passed as an environment variable
|
||||
oas = "3.0.0" // The version of the OpenAPI Specification you"re using
|
||||
}
|
||||
|
||||
jar {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security-Fat
|
||||
container_name: Stirling-PDF-Security-Fat-with-login
|
||||
image: stirlingtools/stirling-pdf:latest-fat
|
||||
deploy:
|
||||
resources:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,477 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
|
||||
<configuration>
|
||||
<verify-metadata>true</verify-metadata>
|
||||
<verify-signatures>true</verify-signatures>
|
||||
<keyring-format>armored</keyring-format>
|
||||
<trusted-artifacts>
|
||||
<trust group="io.dropwizard.metrics" name="metrics-bom" reason="BOM file, safe to trust"/>
|
||||
<trust group="io.dropwizard.metrics" name="metrics-parent" reason="BOM parent, https://github.com/gradle/gradle/issues/20194"/>
|
||||
<trust group="org.springframework" name="spring-framework-bom" reason="Spring BOM file, safe to trust"/>
|
||||
</trusted-artifacts>
|
||||
<trusted-keys>
|
||||
<trusted-key id="015479E1055341431B4545AB72475FD306B9CAB7" group="com.googlecode.javaewah" name="JavaEWAH" version="1.2.3"/>
|
||||
<trusted-key id="042B29E928995B9DB963C636C7CA19B7B620D787" group="com.github.stephenc.jcip" name="jcip-annotations" version="1.0-1"/>
|
||||
<trusted-key id="04543577D6A9CC626239C50C7ECBD740FF06AEB5">
|
||||
<trusting group="org.glassfish.jaxb"/>
|
||||
<trusting group="^com[.]sun($|([.].*))" regex="true"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="050A37A2E0577F4BAA095B52602EC18D20C4661C">
|
||||
<trusting group="com.thoughtworks.xstream"/>
|
||||
<trusting group="io.github.x-stream"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="077E8893A6DCC33DD4A4D5B256E73BA9A0B592D0" group="^org[.]apache[.]logging($|([.].*))" regex="true"/>
|
||||
<trusted-key id="0785B3EFF60B1B1BEA94E0BB7C25280EAE63EBE5" group="^org[.]apache[.]httpcomponents($|([.].*))" regex="true"/>
|
||||
<trusted-key id="07E20F0103D9DFC697C490D0368557390486F2C5">
|
||||
<trusting group="io.rest-assured"/>
|
||||
<trusting group="org.awaitility"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="08F0AAB4D0C1A4BDDE340765B341DDB020FCB6AB" group="org.bouncycastle"/>
|
||||
<trusted-key id="0A60B3F1FCB211175300EC206E50BB68CC1699A6" group="com.github.jai-imageio"/>
|
||||
<trusted-key id="0B1B71E813C226033B16D8C5F0D228D8FF31B515" group="^io[.]zipkin($|([.].*))" regex="true"/>
|
||||
<trusted-key id="0B743A794876D3C78AB542A118D239B1CBCD2236" group="org.glassfish.jersey" name="jersey-bom"/>
|
||||
<trusted-key id="0CC641C3A62453AB390066C4A41F13C999945293" group="commons-collections" name="commons-collections" version="3.2.2"/>
|
||||
<trusted-key id="0CDE80149711EB46DFF17AE421A24B3F8B0F594A" group="org.apache" name="apache" version="16"/>
|
||||
<trusted-key id="0CFA413799E2464C7D7E26220A4B343F2A55FDAE" group="com.h2database" name="h2" version="2.3.232"/>
|
||||
<trusted-key id="0D35D3F60078655126908E8AF3D1600878E85A3D" group="io.netty" name="netty-bom"/>
|
||||
<trusted-key id="0E0CA56D354132B5E646C25F49A1796B9B494CB8" group="org.opensaml"/>
|
||||
<trusted-key id="0E9BD9062B021BBA50F41EEB9549F6CB1E679A56" group="org.locationtech.jts"/>
|
||||
<trusted-key id="10F3C7A02ECA55E502BADCF3991EFB94DB91127D" group="org.ow2" name="ow2" version="1.5.1"/>
|
||||
<trusted-key id="1452F35849B50750F6A3BBB4B54011358B352F85" group="org.hibernate.orm" name="hibernate-core" version="6.6.4.Final"/>
|
||||
<trusted-key id="147B691A19097624902F4EA9689CBE64F4BC997F" group="org.mockito"/>
|
||||
<trusted-key id="190D5A957FF22273E601F7A7C92C5FEC70161C62" group="org.apache" name="apache" version="18"/>
|
||||
<trusted-key id="19BEAB2D799C020F17C69126B16698A4ADF4D638" group="org.checkerframework" name="checker-qual"/>
|
||||
<trusted-key id="1AA8CF92D409A73393D0B736BFF2EE42C8282E76" group="org.apache.activemq" name="activemq-bom" version="6.1.4"/>
|
||||
<trusted-key id="1D04A424F505394DBED15D451D0690E353BE126D" group="net.minidev"/>
|
||||
<trusted-key id="1D2C7EF8ADA0F794B58C7C63436902AF59EDF60E">
|
||||
<trusting group="dev.equo.ide" name="solstice" version="1.7.5"/>
|
||||
<trusting group="dev.equo.ide" name="solstice" version="1.8.0"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="20FC6EC5F628F0EB66F157B8DC97B815CAC4E847" group="io.github.pixee" name="java-security-toolkit" version="1.2.1"/>
|
||||
<trusted-key id="2518174F4111F02779592A6F9757D7E7E06DD2AC" group="io.prometheus"/>
|
||||
<trusted-key id="2655176F748FD83725B4805FF2A01147D830C125" group="org.testcontainers" name="testcontainers-bom"/>
|
||||
<trusted-key id="28118C070CB22A0175A2E8D43D12CA2AC19F3181" group="^com[.]fasterxml($|([.].*))" regex="true"/>
|
||||
<trusted-key id="28417C95E8906D108392822354A43F3254868410" group="org.apache.activemq"/>
|
||||
<trusted-key id="2B1DD4CE9223D4E19C73531E5657B51F13E59DBE" group="com.unboundid.product.scim2"/>
|
||||
<trusted-key id="2B34821418CF19CF1F2A8352953E02E4F573B46F" group="jakarta.platform"/>
|
||||
<trusted-key id="2BCBDD0F23EA1CAFCC11D4860374CF2E8DD1BDFD" group="net.java"/>
|
||||
<trusted-key id="2DB4F1EF0FA761ECC4EA935C86FDC7E2A11262CB">
|
||||
<trusting group="commons-beanutils" name="commons-beanutils" version="1.10.0"/>
|
||||
<trusting group="commons-codec"/>
|
||||
<trusting group="commons-io"/>
|
||||
<trusting group="commons-logging"/>
|
||||
<trusting group="org.apache.commons"/>
|
||||
<trusting group="xml-apis"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="2DC48CBB4352B4953AF6F803D433B437192A0FD1" group="com.datastax.oss" name="java-driver-bom" version="4.15.0"/>
|
||||
<trusted-key id="2E3A1AFFE42B5F53AF19F780BCF4173966770193" group="org.jetbrains" name="annotations" version="13.0"/>
|
||||
<trusted-key id="2FC53E6B1F681184F4CCD637F5C81DE10A0B8ECC" group="org.yaml" name="snakeyaml" version="2.3"/>
|
||||
<trusted-key id="3262A061C42FC4C7BBB5C25C1CF0293FA53CA458" group="org.apache.tomcat.embed"/>
|
||||
<trusted-key id="34441E504A937F43EB0DAEF96A65176A0FB1CD0B" group="org.apache.groovy" name="groovy-bom"/>
|
||||
<trusted-key id="3690C240CE51B4670D30AD1C38EE757D69184620" group="org.tukaani" name="xz" version="1.9"/>
|
||||
<trusted-key id="3750777B9C4B7D233B9D0C40307A96FBA0292109" group="org.postgresql" name="postgresql" version="42.7.4"/>
|
||||
<trusted-key id="38319E05F62674572CDF886170B2EBE96C112CC9" group="org.cryptacular" name="cryptacular" version="1.2.5"/>
|
||||
<trusted-key id="3E61D8C230332482009D7F0EDB901B24CAD38BC4" group="io.swagger.core.v3"/>
|
||||
<trusted-key id="3F05DDA9F317301E927136D417A27CE7A60FF5F0" group="io.opentelemetry" name="opentelemetry-bom"/>
|
||||
<trusted-key id="4021EEEAFF5DE8404DCD0A270AA3E5C3D232E79B">
|
||||
<trusting group="jakarta.enterprise"/>
|
||||
<trusting group="jakarta.inject"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="41D266DB4427983A1A4AFB0C3684155E9365C30E" group="com.jayway.jsonpath" name="json-path" version="2.9.0"/>
|
||||
<trusted-key id="44FBDBBC1A00FE414F1C1873586654072EAD6677" group="org.sonatype.oss" name="oss-parent" version="9"/>
|
||||
<trusted-key id="453EA31328DE7D8AAA55AD4ED56C721C1CFF1424" group="^com[.]twelvemonkeys($|([.].*))" regex="true"/>
|
||||
<trusted-key id="475F3B8E59E6E63AA78067482C7B12F2A511E325" group="org.slf4j"/>
|
||||
<trusted-key id="477E62A656AD5475A1882855C809CA3C41BA6E96" group="jakarta.validation" name="jakarta.validation-api" version="3.0.2"/>
|
||||
<trusted-key id="4797B4F5DCC46CEA61059071A1AE06236CA2BA62" group="^com[.]diffplug($|([.].*))" regex="true"/>
|
||||
<trusted-key id="47EF0EC60C210BC6DFAA5819B7AE15C15C321C44" group="jakarta.transaction" name="jakarta.transaction-api" version="2.0.1"/>
|
||||
<trusted-key id="47FF105DF431FF5416B821FEAECDB81D38EA9C89" group="org.commonmark"/>
|
||||
<trusted-key id="482C52BC305FB31063CD19D67BEFA2F0A9C24E7D" group="net.sf.launch4j" name="launch4j" version="3.50"/>
|
||||
<trusted-key id="48B086A7D843CFA258E83286928FBF39003C0425">
|
||||
<trusting group="io.micrometer"/>
|
||||
<trusting group="io.projectreactor"/>
|
||||
<trusting group="io.spring.gradle"/>
|
||||
<trusting group="^org[.]springframework($|([.].*))" regex="true"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="498AAC354AA5CB36FAAB7608B6E83A2D2E447E56" group="org.apache.cassandra" name="java-driver-bom" version="4.18.1"/>
|
||||
<trusted-key id="4F7E32D440EF90A83011A8FC6425559C47CC79C4">
|
||||
<trusting group="com.sun.activation"/>
|
||||
<trusting group="javax.activation"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="53C935821AA6A755BD337DB53595395EB3D8E1BA" group="org.apache.logging.log4j" name="log4j-bom" version="2.20.0"/>
|
||||
<trusted-key id="5719E50EAC5A4B1DD390B72C2A742740E08E7F8D" group="org.antlr"/>
|
||||
<trusted-key id="57312C37B064EE0FDAB0130490D5CE79E1DE6A2C" group="com.querydsl" name="querydsl-bom"/>
|
||||
<trusted-key id="5989BAF76217B843D66BE55B2D0E1FB8FE4B68B4" group="^org[.]eclipse[.]jetty($|([.].*))" regex="true"/>
|
||||
<trusted-key id="59A8E169739301FD48139CA00E325BECB6962A24" group="jakarta.annotation" name="jakarta.annotation-api" version="2.1.1"/>
|
||||
<trusted-key id="5C9A30FF22B2C02F30261C305B93F1DF7CDB6DEA" group="org.apache.xmlgraphics"/>
|
||||
<trusted-key id="60200AC4AE761F1614D6C46766D68DAA073BE985">
|
||||
<trusting group="ch.qos.logback"/>
|
||||
<trusting group="org.slf4j"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="694621A7227D8D5289699830ABE9F3126BB741C1" group="com.google.guava" name="guava-parent" version="26.0-android"/>
|
||||
<trusted-key id="6DD3B8C64EF75253BEB2C53AD908A43FB7EC07AC" group="jakarta.activation" name="jakarta.activation-api" version="2.1.3"/>
|
||||
<trusted-key id="6E13156C0EE653F0B984663AB95BBD3FA43C4492" group="org.apache" name="apache" version="3"/>
|
||||
<trusted-key id="6F538074CCEBF35F28AF9B066A0975F8B1127B83" group="org.jetbrains.kotlin"/>
|
||||
<trusted-key id="70CD19BFD9F6C330027D6F260315BFB7970A144F" group="javax.xml.bind"/>
|
||||
<trusted-key id="71EBF1CA4125B10AAB1E17CDB7DC526C17E3608B" group="jakarta.persistence" name="jakarta.persistence-api" version="3.1.0"/>
|
||||
<trusted-key id="7464550A61C90BA385FC97A76D9567281201E5E3" group="jakarta.servlet" name="jakarta.servlet-api" version="6.0.0"/>
|
||||
<trusted-key id="7616EB882DAF57A11477AAF559A252FB1199D873" group="com.google.code.findbugs" name="jsr305" version="3.0.2"/>
|
||||
<trusted-key id="798E2DA37E70DAE0EA9E498CA388C395AAFB80F8" group="io.dropwizard.metrics"/>
|
||||
<trusted-key id="7A1D848E7C2AF85EEBA69C99E7BF252CF360097E" group="org.latencyutils" name="LatencyUtils" version="2.0.3"/>
|
||||
<trusted-key id="7B121B76A7ED6CE6E60AD51784E913A8E3A748C0" group="org.bouncycastle"/>
|
||||
<trusted-key id="7C669810892CBD3148FA92995B05CCDE140C2876" group="org.eclipse.jgit"/>
|
||||
<trusted-key id="808D78B17A5A2D7C3668E31FBFFC9B54721244AD" group="org.apache.commons" name="commons-parent" version="39"/>
|
||||
<trusted-key id="80F6D6B0D90C6747753344CAB5A9E81B565E89E0" group="org.tomlj" name="tomlj" version="1.0.0"/>
|
||||
<trusted-key id="81BE0C38ACE8AEDC7735A05F4C2AFF633F3A7223" group="org.seleniumhq.selenium" name="selenium-bom"/>
|
||||
<trusted-key id="81CCDC71C7D61C179B27002D6A9FBE152D4C64D1" group="org.openjfx"/>
|
||||
<trusted-key id="82F0964816AD7319CB0CCCF93EFD9D223D715E9A" group="com.nimbusds"/>
|
||||
<trusted-key id="82F94BBDF95C247BBD21396B9A0B94DEC0FFA7EE" group="org.webjars" name="swagger-ui" version="5.2.0"/>
|
||||
<trusted-key id="839323A4780D5BF9A6978970152888E10EF880B3">
|
||||
<trusting group="org.attoparser"/>
|
||||
<trusting group="org.unbescape"/>
|
||||
<trusting group="^org[.]thymeleaf($|([.].*))" regex="true"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="84789D24DF77A32433CE1F079EB80E92EB2135B1" group="org.apache" name="apache"/>
|
||||
<trusted-key id="8756C4F765C9AC3CB6B85D62379CE192D401AB61" group="com.diffplug.durian"/>
|
||||
<trusted-key id="894F14D98D7F20D5E82645E3DFE102108BF9381F" group="org.hibernate.search" name="hibernate-search-bom" version="7.1.2.Final"/>
|
||||
<trusted-key id="94976E17E18DD3201447286954963C3E875A56AE" group="io.smallrye"/>
|
||||
<trusted-key id="9579802DC3E15DE9C389239FC0D48A119CE7EE7B" group="com.zaxxer" name="HikariCP" version="5.1.0"/>
|
||||
<trusted-key id="9790B1EC52577244529621F38C77ED250E495230" group="com.bucket4j" name="bucket4j_jdk17-core" version="8.14.0"/>
|
||||
<trusted-key id="982C26A0C156D986CC2AD19E3FBA8E8E719022D7" group="org.jboss" name="jboss-parent" version="39"/>
|
||||
<trusted-key id="9B32CBC0F3F6BA4C13D611FC21871D2A9AB66A31" group="io.rsocket" name="rsocket-bom" version="1.1.3"/>
|
||||
<trusted-key id="9E3044071B758EBCB7E45673700E4F39BC05364B">
|
||||
<trusting group="org.eclipse.platform" name="org.eclipse.osgi" version="3.18.300"/>
|
||||
<trusting group="org.eclipse.platform" name="org.eclipse.osgi" version="3.18.500"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="A41A5960555F8CBBC7D8B2D7787F3A057B828D36" group="org.springdoc"/>
|
||||
<trusted-key id="A5BD02B93E7A40482EB1D66A5F69AD087600B22C" group="org.ow2.asm"/>
|
||||
<trusted-key id="A602970FE1BF5C9C8A9491B97A3C9FE21DFDBF44" group="org.apache.pdfbox"/>
|
||||
<trusted-key id="A654E2E6D97BE4219A4909415B15A33991BEA5A8" group="me.friwi"/>
|
||||
<trusted-key id="A6D6C97108B8585F91B158748671A8DF71296252" group="com.squareup.okhttp3" name="okhttp-bom" version="4.10.0"/>
|
||||
<trusted-key id="A7892505CF1A58076453E52D7999BEFBA1039E8B" group="net.bytebuddy"/>
|
||||
<trusted-key id="A9789342F598AD5B1175EF357EB97D110DFADD60" group="com.googlecode.concurrent-trees" name="concurrent-trees" version="2.6.1"/>
|
||||
<trusted-key id="AA70C7C433D501636392EC02153E7A3C2B4E5118" group="org.eclipse.ee4j" name="project"/>
|
||||
<trusted-key id="AB1DC33940689C44669107094989E0E939C2999B">
|
||||
<trusting group="com.opencsv" name="opencsv" version="5.10"/>
|
||||
<trusting group="com.opencsv" name="opencsv" version="5.9"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="B1F250C1F371EBF0E31E86E30E31BBB30C940D01" group="com.posthog.java" name="posthog" version="1.1.1"/>
|
||||
<trusted-key id="B6E73D84EA4FCC47166087253FAAD2CD5ECBB314">
|
||||
<trusting group="commons-beanutils"/>
|
||||
<trusting group="org.apache.commons"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="BA926F64CA647B6D853A38672E2010F8A7FF4A41" group="org.apache" name="apache" version="7"/>
|
||||
<trusted-key id="BB785E0400E71390977E4D1ADF3CC7C64D56297B" group="jakarta.interceptor" name="jakarta.interceptor-api" version="2.1.0"/>
|
||||
<trusted-key id="BCA1F17506AF088F3A964A9C0459A2B383ED8C11" group="org.eclipse.angus"/>
|
||||
<trusted-key id="BDB5FA4FE719D787FB3D3197F6D4A1D411E9D1AE" group="^com[.]google($|([.].*))" regex="true"/>
|
||||
<trusted-key id="BE685132AFD2740D9095F9040CC0B712FEE75827" group="org.assertj"/>
|
||||
<trusted-key id="C1D1ADA83198AA7FEAD102483FFE64C7506FCCC9" group="com.coveo" name="saml-client" version="5.0.0"/>
|
||||
<trusted-key id="C663D2F64DA2CA09DB28D9ABD3FA67D522C55256" group="org.apache.pulsar" name="pulsar-bom" version="3.3.3"/>
|
||||
<trusted-key id="C7BE5BCC9FEC15518CFDA882B0F3710FA64900E7" group="com.google.code.gson"/>
|
||||
<trusted-key id="C89074FC8BE681B7C7EAAB6E4C5EED3C53B75933" group="org.skyscreamer" name="jsonassert" version="1.5.3"/>
|
||||
<trusted-key id="CA62ED130E4053944406DF640181B45EA58677BC" group="org.apache.logging" name="logging-parent" version="7"/>
|
||||
<trusted-key id="CC57399D74CD7E4768ED6FA4CA62973FBF0451C0" group="com.vaadin.external.google" name="android-json" version="0.0.20131108.vaadin1"/>
|
||||
<trusted-key id="CD5464315F0B98C77E6E8ECD9DAADC1C9FCC82D0" group="commons-cli" name="commons-cli" version="1.4"/>
|
||||
<trusted-key id="CE3285F320685193D11FEA01F6CE9695C9318406" group="com.google.zxing"/>
|
||||
<trusted-key id="CE4439C1BEF3DA83B1832F9DBEFEEF227A98B809" group="org.apache.velocity"/>
|
||||
<trusted-key id="CE8075A251547BEE249BC151A2115AE15F6B8B72">
|
||||
<trusting group="org.apache.commons"/>
|
||||
<trusting group="org.xmlunit"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="D421D1DF4570BFB13E485D0BF95ADD0A28D2F139" group="org.projectlombok" name="lombok" version="1.18.36"/>
|
||||
<trusted-key id="D54A395B5CF3F86EB45F6E426B1B008864323B92" group="org.antlr"/>
|
||||
<trusted-key id="DB45ECD19B97514F727105AE67BF80B10AD53983" group="org.apache.santuario" name="xmlsec" version="2.3.4"/>
|
||||
<trusted-key id="DBD744ACE7ADE6AA50DD591F66B50994442D2D40" group="^com[.]squareup($|([.].*))" regex="true"/>
|
||||
<trusted-key id="DBFBFF8DA2F1571ACC6F63AB905CF8FC70CC1444" group="org.aspectj" name="aspectjweaver" version="1.9.22.1"/>
|
||||
<trusted-key id="DCAA15007BED9DE690CD9523378B845402277962">
|
||||
<trusting group="org.opensaml"/>
|
||||
<trusting group="^net[.]shibboleth($|([.].*))" regex="true"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="E01AAB301618D23B39DBD41002DE09238A0E4D34" group="com.drewnoakes" name="metadata-extractor" version="2.19.0"/>
|
||||
<trusted-key id="E113159331A1F87BFC2A93D0960D2E8635A91268" group="org.hdrhistogram" name="HdrHistogram" version="2.2.2"/>
|
||||
<trusted-key id="E2ACB037933CDEAAB7BF77D49A2C7A98E457C53D">
|
||||
<trusting group="io.projectreactor"/>
|
||||
<trusting group="^org[.]springframework($|([.].*))" regex="true"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="E3A9F95079E84CE201F7CF60BEDE11EAF1164480" group="org.hamcrest" name="hamcrest"/>
|
||||
<trusted-key id="E7DC75FC24FB3C8DFE8086AD3D5839A2262CBBFB" group="org.jetbrains.kotlinx"/>
|
||||
<trusted-key id="E85AED155021AF8A6C6B7A4A7C7D8456294423BA" group="org.objenesis"/>
|
||||
<trusted-key id="EE0CA873074092F806F59B65D364ABAA39A47320">
|
||||
<trusting group="com.google.errorprone"/>
|
||||
<trusting group="com.google.googlejavaformat"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="EED29BAB8D4FD882D62308CD72D1B04BC7E6AA04" group="me.friwi"/>
|
||||
<trusted-key id="EF5214AD654CD05F0DA91609ECEAC3B11AD0E0A0" group="com.adobe.xmp" name="xmpcore" version="6.1.11"/>
|
||||
<trusted-key id="F046369B06B761AC86D9849F71B329993BFFCFDD" group="com.oracle.database.jdbc" name="ojdbc-bom" version="21.9.0.0"/>
|
||||
<trusted-key id="F0E31196852A34F8855710BD4A6CE7EBC7F4F54B" group="io.prometheus"/>
|
||||
<trusted-key id="F1232CDCD94176E7FBA9CFE289A2C76A5EE16E57" group="technology.tabula" name="tabula" version="1.0.5"/>
|
||||
<trusted-key id="F3184BCD55F4D016E30D4C9BF42E87F9665015C9" group="org.jsoup" name="jsoup" version="1.18.3"/>
|
||||
<trusted-key id="F55EF5BB19F52A250FEDC0DF39450183608E49D4" group="com.googlecode.owasp-java-html-sanitizer"/>
|
||||
<trusted-key id="F5FEBA84EB26C56457B2CF819E31AB27445478DB" group="org.infinispan"/>
|
||||
<trusted-key id="F60649A7F36F9FBEE21D9AA08AC0378EC753063D" group="com.fathzer"/>
|
||||
<trusted-key id="F674EBA7B6EC777BDB58942DE0E92C40A43A012A" group="jakarta.websocket"/>
|
||||
<trusted-key id="FA77DCFEF2EE6EB2DEBEDD2C012579464D01C06A" group="org.apache" name="apache"/>
|
||||
<trusted-key id="FA7929F83AD44C4590F6CC6815C71C0A4E0B8EDD" group="net.java.dev.jna"/>
|
||||
<trusted-key id="FC411CD3CB7DCB0ABC9801058118B3BCDB1A5000" group="jakarta.xml.bind"/>
|
||||
<trusted-key id="FF6E2C001948C5F2F38B0CC385911F425EC61B51">
|
||||
<trusting group="org.apiguardian"/>
|
||||
<trusting group="org.opentest4j"/>
|
||||
<trusting group="^org[.]junit($|([.].*))" regex="true"/>
|
||||
</trusted-key>
|
||||
</trusted-keys>
|
||||
</configuration>
|
||||
<components>
|
||||
<component group="com.diffplug.spotless" name="com.diffplug.spotless.gradle.plugin" version="6.25.0">
|
||||
<artifact name="com.diffplug.spotless.gradle.plugin-6.25.0.pom">
|
||||
<sha256 value="f45c82b12faacd85acd474eba699322fa5dea88408b247d0e4bde9412908223a" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.diffplug.spotless" name="com.diffplug.spotless.gradle.plugin" version="7.0.1">
|
||||
<artifact name="com.diffplug.spotless.gradle.plugin-7.0.1.pom">
|
||||
<sha256 value="d967a0f74c203ddcc5700947aab40f4be2a5a9f7b8d32aab7fc412b2030e7dfc" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.Carleslc.Simple-YAML" name="Simple-Configuration" version="1.8.4">
|
||||
<artifact name="Simple-Configuration-1.8.4.jar">
|
||||
<sha256 value="2b960f4840ac68bb1815d937ca2d58eb9b04c05e6a9b769a4e870c52a4728156" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="Simple-Configuration-1.8.4.pom">
|
||||
<sha256 value="698e378e816a220edfcb754fd4c4f7d9a8fd38716b9081f63f9878d4bbf3cdd5" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.Carleslc.Simple-YAML" name="Simple-YAML-Parent" version="1.8.4">
|
||||
<artifact name="Simple-YAML-Parent-1.8.4.pom">
|
||||
<sha256 value="b9298b875185bd13b4e301187eeb234d3a1a4b1a871dd4a7461f2e7775121357" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.Carleslc.Simple-YAML" name="Simple-Yaml" version="1.8.4">
|
||||
<artifact name="Simple-Yaml-1.8.4.jar">
|
||||
<sha256 value="d558ca57927d4bc393e9522aac0cf60cc632a9f6f60cd6724aa94b7005e1fd18" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="Simple-Yaml-1.8.4.pom">
|
||||
<sha256 value="47f1003cd91eb6c11b2c941bf89e72428aed92e6bfef327b18935dda28eb4072" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.jk1" name="gradle-license-report" version="2.9">
|
||||
<artifact name="gradle-license-report-2.9.jar">
|
||||
<sha256 value="ebfd6da851654c53216eea9eda1485c12e0cd6de5a9919bf5da9735a021f32af" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="gradle-license-report-2.9.module">
|
||||
<sha256 value="4139a508481c369ae0f2627fa8387f1e20e58600f2037cdc1cdaa164e056f235" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.jk1.dependency-license-report" name="com.github.jk1.dependency-license-report.gradle.plugin" version="2.9">
|
||||
<artifact name="com.github.jk1.dependency-license-report.gradle.plugin-2.9.pom">
|
||||
<sha256 value="a79ca4dfe069d737faf075c8f4b6c6471c2e5cea8e1546946ae333d747fddf02" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.psxpaul" name="gradle-execfork-plugin" version="0.2.0">
|
||||
<artifact name="gradle-execfork-plugin-0.2.0.jar">
|
||||
<sha256 value="eb4f73df13ee24fb1952e0a9054c5618ef07f0d62386bfad1a04990df0cb3a65" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="gradle-execfork-plugin-0.2.0.module">
|
||||
<sha256 value="7b239eb029b2e4cab00dddf1df204ef4bbf88e78a43619c26fbb1e49bc53c642" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.google.guava" name="guava-parent" version="33.3.1-jre">
|
||||
<artifact name="guava-parent-33.3.1-jre.pom">
|
||||
<sha256 value="55441db27e8869dfefe053059bdf478bdc7e95585642bf391f0023345fd56287" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.martiansoftware" name="jsap" version="2.1">
|
||||
<artifact name="jsap-2.1.jar">
|
||||
<sha256 value="331746fa62cfbc3368260c5a2e660936ad11be612308c120a044e120361d474e" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="jsap-2.1.pom">
|
||||
<sha256 value="9acf56a8579c05bedd819d99232363e2bf327e8f73c67598dbd9885a845a3c69" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="commons-logging" name="commons-logging" version="1.0.4">
|
||||
<artifact name="commons-logging-1.0.4.pom">
|
||||
<sha256 value="65d310509352b5425118225ee600a01f83ba72142d035014b5d164bc04b2d284" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="edu.sc.seis.launch4j" name="edu.sc.seis.launch4j.gradle.plugin" version="3.0.6">
|
||||
<artifact name="edu.sc.seis.launch4j.gradle.plugin-3.0.6.pom">
|
||||
<sha256 value="62a4f6752190b9ebf30869e092e4154e41a2c5cd96048ae98a01916f2684465a" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="edu.sc.seis.launch4j" name="launch4j" version="3.0.6">
|
||||
<artifact name="launch4j-3.0.6.jar">
|
||||
<sha256 value="6a8f000c6fda2eb17406b516ec0be28cdac900cbba03319e57bd3c2f1b1afa02" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="launch4j-3.0.6.module">
|
||||
<sha256 value="0a38e1daab79a32b56790db458088148c97be021764e2d2dce259b9a87fec048" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.dropwizard.metrics" name="metrics-bom" version="4.2.25">
|
||||
<artifact name="metrics-bom-4.2.25.pom">
|
||||
<sha256 value="825ad37b8380f992b515050bbd95452f85466feae7b856d5c150d4e5f716a8e9" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.dropwizard.metrics" name="metrics-parent" version="4.2.25">
|
||||
<artifact name="metrics-parent-4.2.25.pom">
|
||||
<sha256 value="df7b6371f9b15698e123d9861f2099ca32c9ec966d9f0c60755a2a34ccbfabc2" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.spring.dependency-management" name="io.spring.dependency-management.gradle.plugin" version="1.1.7">
|
||||
<artifact name="io.spring.dependency-management.gradle.plugin-1.1.7.pom">
|
||||
<sha256 value="19bb16ab5d6359bff88ce95c80b01e7e3445157faa1d74ae5cf03a467cea1e04" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.swagger" name="swaggerhub" version="1.3.2">
|
||||
<artifact name="swaggerhub-1.3.2.jar">
|
||||
<sha256 value="703a61e96b23af81b2ceeba4a081bb3212ec00211ae300748b3e7ccb1f33bd32" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="swaggerhub-1.3.2.module">
|
||||
<sha256 value="bd5ccd6e48224cab88bbd79e880ff011ee4fa711490f410f7810b75a2cb9c3c0" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.swagger.swaggerhub" name="io.swagger.swaggerhub.gradle.plugin" version="1.3.2">
|
||||
<artifact name="io.swagger.swaggerhub.gradle.plugin-1.3.2.pom">
|
||||
<sha256 value="69069eee12440c521662057334ac4acaea0ce6534ca4fd8b1bc264de930ad2d0" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="net.java" name="jvnet-parent" version="5">
|
||||
<artifact name="jvnet-parent-5.pom">
|
||||
<sha256 value="1af699f8d9ddab67f9a0d202fbd7915eb0362a5a6dfd5ffc54cafa3465c9cb0a" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="net.shibboleth" name="parent" version="11.3.5">
|
||||
<artifact name="parent-11.3.5.pom">
|
||||
<pgp value="0E0CA56D354132B5E646C25F49A1796B9B494CB8"/>
|
||||
<sha256 value="7a24e2700485eea087370f1dca6fe0291d7893d38c11aabfe977784fd93b808c" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.apache" name="apache" version="27">
|
||||
<artifact name="apache-27.pom">
|
||||
<sha256 value="b2b0fc69e22a650c3892f1c366d77076f29575c6738df4c7a70a44844484cdf9" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.apache" name="apache" version="33">
|
||||
<artifact name="apache-33.pom">
|
||||
<sha256 value="d78bd8524c5f8380a190a6525686629a95dfe512df21111383a6d8c0923a4415" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.apache.commons" name="commons-parent" version="78">
|
||||
<artifact name="commons-parent-78.pom">
|
||||
<sha256 value="022d202e655edd04f2a10ecbe453d92977924d38380a4ca8c359f1817a80320e" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.hibernate.common" name="hibernate-commons-annotations" version="7.0.3.Final">
|
||||
<artifact name="hibernate-commons-annotations-7.0.3.Final.jar">
|
||||
<sha256 value="0db2fd57d5e43688ac6ed5cdf36deaf05d84340dcc24c2dd2a2114de38e5175d" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="hibernate-commons-annotations-7.0.3.Final.module">
|
||||
<sha256 value="b1aa7202fc3f67d22066903d3e1eb7052ee10f47322a1cb925fa2f449f25aee3" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="hibernate-commons-annotations-7.0.3.Final.pom">
|
||||
<sha256 value="66f6e607b30740e391989825a5ae076a6c877a99b78eb054a8146650aaff72eb" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jboss" name="jboss-parent" version="42">
|
||||
<artifact name="jboss-parent-42.pom">
|
||||
<sha256 value="e41276efe3509054cba4197b3d6360c51dd57bc640dde48cf37dafaa45a09c3b" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jboss" name="jboss-parent" version="43">
|
||||
<artifact name="jboss-parent-43.pom">
|
||||
<sha256 value="3c3ade76fb883acdb9a8a03355d1df4066ffb9c8c78f09b219e9c0fc2a3f4317" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jboss.logging" name="jboss-logging" version="3.6.1.Final">
|
||||
<artifact name="jboss-logging-3.6.1.Final.jar">
|
||||
<sha256 value="5e08a4b092dc85b337f0910a740571d8720cfa565fabd880a8caf94a657ca416" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="jboss-logging-3.6.1.Final.pom">
|
||||
<sha256 value="27cd88ab8e5946b8a7aa92644eb3732e35be281439ab07af71f898453ee7540d" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jboss.logging" name="logging-parent" version="1.0.3.Final">
|
||||
<artifact name="logging-parent-1.0.3.Final.pom">
|
||||
<sha256 value="9972c894749cda355766217d43ded7009b1eeb26e0301c30914a2db253dd685b" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.junit" name="junit-bom" version="5.11.2">
|
||||
<artifact name="junit-bom-5.11.2.pom">
|
||||
<sha256 value="f48e88538aac145eb3ae0345a9ebd055b28f329a35dce8d1e9281325ca9b0ea2" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.opensaml" name="opensaml-bom" version="4.3.0">
|
||||
<artifact name="opensaml-bom-4.3.0.pom">
|
||||
<sha256 value="4dfcc7cd96a2645c6e28df9f166f0e5b2b1a44aa109b3100cdb0ee17e01e02d2" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.opensaml" name="opensaml-parent" version="4.3.0">
|
||||
<artifact name="opensaml-parent-4.3.0.pom">
|
||||
<sha256 value="5e9db2f2dc3938835a76f5334997d79c8781511c8b68c1f6df6b384306900319" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.ow2" name="ow2" version="1.5.1">
|
||||
<artifact name="ow2-1.5.1.pom">
|
||||
<sha256 value="321ddbb7ee6fe4f53dea6b4cd6db74154d6bfa42391c1f763b361b9f485acf05" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.panteleyev" name="jpackage-gradle-plugin" version="1.6.0">
|
||||
<artifact name="jpackage-gradle-plugin-1.6.0.jar">
|
||||
<sha256 value="a8a588ff44a62db1aee62d3da117d2632a7f9a107709ca201da2a59dcb500175" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="jpackage-gradle-plugin-1.6.0.module">
|
||||
<sha256 value="a572bc67a0bcce5eb8c50a0ae2659fba850dae5b0188f53045635c9276545179" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.panteleyev.jpackageplugin" name="org.panteleyev.jpackageplugin.gradle.plugin" version="1.6.0">
|
||||
<artifact name="org.panteleyev.jpackageplugin.gradle.plugin-1.6.0.pom">
|
||||
<sha256 value="82bff05e70c9f7f5c3d4a8c3958b3842dea970ac1378e306051e66cf98a8f340" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.sonatype.oss" name="oss-parent" version="5">
|
||||
<artifact name="oss-parent-5.pom">
|
||||
<pgp value="2BCBDD0F23EA1CAFCC11D4860374CF2E8DD1BDFD"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.sonatype.oss" name="oss-parent" version="7">
|
||||
<artifact name="oss-parent-7.pom">
|
||||
<sha256 value="b51f8867c92b6a722499557fc3a1fdea77bdf9ef574722fe90ce436a29559454" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.springdoc" name="springdoc-openapi-gradle-plugin" version="1.8.0">
|
||||
<artifact name="springdoc-openapi-gradle-plugin-1.8.0.jar">
|
||||
<sha256 value="94075aa01757a0c1d573ade9145c098963f084feefd31dc95d65606c503585f4" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="springdoc-openapi-gradle-plugin-1.8.0.module">
|
||||
<sha256 value="6f2d828807961169293f4f5b897f7c3ee76da06dc6bb9d94acb3d5f2418e998c" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.springdoc.openapi-gradle-plugin" name="org.springdoc.openapi-gradle-plugin.gradle.plugin" version="1.8.0">
|
||||
<artifact name="org.springdoc.openapi-gradle-plugin.gradle.plugin-1.8.0.pom">
|
||||
<sha256 value="2d117343231e29be22e489bc1f4825e1d4bbef545905e793244e95221957154f" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.springframework" name="spring-framework-bom" version="5.3.34">
|
||||
<artifact name="spring-framework-bom-5.3.34.pom">
|
||||
<sha256 value="6d0616e2544d7115dc249817dd758a34dfa677329182b42e17542e133e55732d" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.springframework.boot" name="org.springframework.boot.gradle.plugin" version="3.4.1">
|
||||
<artifact name="org.springframework.boot.gradle.plugin-3.4.1.pom">
|
||||
<sha256 value="f4d1acf98aa55a44b1a23b1c2b5c75aaa84c4408bea955bd81efb38acd68f38d" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="xml-apis" name="xml-apis-ext" version="1.3.04">
|
||||
<artifact name="xml-apis-ext-1.3.04.jar">
|
||||
<sha256 value="d0b4887dc34d57de49074a58affad439a013d0baffa1a8034f8ef2a5ea191646" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="xml-apis-ext-1.3.04.pom">
|
||||
<sha256 value="1b5939a9310a59c0df0c03726721d5fc9521e87d6c203bfa7220bae82a30d9e8" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="xmlpull" name="xmlpull" version="1.1.3.1">
|
||||
<artifact name="xmlpull-1.1.3.1.jar">
|
||||
<sha256 value="34e08ee62116071cbb69c0ed70d15a7a5b208d62798c59f2120bb8929324cb63" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="xmlpull-1.1.3.1.pom">
|
||||
<sha256 value="8f10ffd8df0d3e9819c8cc8402709c6b248bc53a954ef6e45470d9ae3a5735fb" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
</components>
|
||||
</verification-metadata>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 169 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 118 KiB |
@@ -75,7 +75,7 @@ def write_readme(progress_list: list[tuple[str, int]]) -> None:
|
||||
f"",
|
||||
)
|
||||
|
||||
with open("README.md", "w", encoding="utf-8") as file:
|
||||
with open("README.md", "w", encoding="utf-8", newline="\n") as file:
|
||||
file.writelines(content)
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ def compare_files(
|
||||
)
|
||||
)
|
||||
ignore_translation = convert_to_multiline(sort_ignore_translation)
|
||||
with open(ignore_translation_file, "w", encoding="utf-8") as file:
|
||||
with open(ignore_translation_file, "w", encoding="utf-8", newline="\n") as file:
|
||||
file.write(tomlkit.dumps(ignore_translation))
|
||||
|
||||
unique_data = list(set(result_list))
|
||||
|
||||
@@ -24,7 +24,6 @@ ignore = [
|
||||
[cs_CZ]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
'pipeline.header',
|
||||
'text',
|
||||
]
|
||||
|
||||
@@ -50,13 +49,13 @@ ignore = [
|
||||
'pipeline.title',
|
||||
'pipelineOptions.pipelineHeader',
|
||||
'pro',
|
||||
'redact.zoom',
|
||||
'sponsor',
|
||||
'text',
|
||||
'validateSignature.cert.bits',
|
||||
'validateSignature.cert.version',
|
||||
'validateSignature.status',
|
||||
'watermark.type.1',
|
||||
'redact.zoom',
|
||||
]
|
||||
|
||||
[el_GR]
|
||||
@@ -211,6 +210,11 @@ ignore = [
|
||||
'watermark.type.1',
|
||||
]
|
||||
|
||||
[sl_SI]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[sr_LATN_RS]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
|
||||
@@ -25,7 +25,7 @@ public class EEAppConfig {
|
||||
|
||||
@Bean(name = "runningEE")
|
||||
public boolean runningEnterpriseEdition() {
|
||||
return licenseKeyChecker.getEnterpriseEnabledResult();
|
||||
return licenseKeyChecker.getEnterpriseEnabledResult();
|
||||
}
|
||||
|
||||
@Bean(name = "SSOAutoLogin")
|
||||
|
||||
@@ -41,6 +41,7 @@ import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
|
||||
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
|
||||
import stirling.software.SPDF.UI.WebBrowser;
|
||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||
import stirling.software.SPDF.utils.UIScaling;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@@ -215,7 +216,7 @@ public class DesktopBrowser implements WebBrowser {
|
||||
}
|
||||
});
|
||||
|
||||
frame.setSize(1280, 768);
|
||||
frame.setSize(UIScaling.scaleWidth(1280), UIScaling.scaleHeight(800));
|
||||
frame.setLocationRelativeTo(null);
|
||||
|
||||
loadIcon();
|
||||
@@ -264,7 +265,9 @@ public class DesktopBrowser implements WebBrowser {
|
||||
frame.setOpacity(1.0f);
|
||||
frame.setUndecorated(false);
|
||||
frame.pack();
|
||||
frame.setSize(1280, 800);
|
||||
frame.setSize(
|
||||
UIScaling.scaleWidth(1280),
|
||||
UIScaling.scaleHeight(800));
|
||||
frame.setLocationRelativeTo(null);
|
||||
log.debug("Frame reconfigured");
|
||||
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
package stirling.software.SPDF.UI.impl;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
|
||||
import io.github.pixee.security.BoundedLineReader;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.utils.UIScaling;
|
||||
|
||||
@Slf4j
|
||||
public class LoadingWindow extends JDialog {
|
||||
@@ -16,6 +24,13 @@ public class LoadingWindow extends JDialog {
|
||||
private final JLabel brandLabel;
|
||||
private long startTime;
|
||||
|
||||
private Timer stuckTimer;
|
||||
private long stuckThreshold = 4000;
|
||||
private long timeAt90Percent = -1;
|
||||
private volatile Process explorerProcess;
|
||||
private static final boolean IS_WINDOWS =
|
||||
System.getProperty("os.name").toLowerCase().contains("win");
|
||||
|
||||
public LoadingWindow(Frame parent, String initialUrl) {
|
||||
super(parent, "Initializing Stirling-PDF", true);
|
||||
startTime = System.currentTimeMillis();
|
||||
@@ -41,12 +56,12 @@ public class LoadingWindow extends JDialog {
|
||||
if (is != null) {
|
||||
Image img = ImageIO.read(is);
|
||||
if (img != null) {
|
||||
Image scaledImg = img.getScaledInstance(48, 48, Image.SCALE_SMOOTH);
|
||||
Image scaledImg = UIScaling.scaleIcon(img, 48, 48);
|
||||
JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));
|
||||
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
gbc.gridy = 0;
|
||||
mainPanel.add(iconLabel, gbc);
|
||||
log.debug("Icon loaded and scaled successfully");
|
||||
log.info("Icon loaded and scaled successfully");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +98,8 @@ public class LoadingWindow extends JDialog {
|
||||
setUndecorated(false);
|
||||
|
||||
// Set size and position
|
||||
setSize(400, 200);
|
||||
setSize(UIScaling.scaleWidth(400), UIScaling.scaleHeight(200));
|
||||
|
||||
setLocationRelativeTo(parent);
|
||||
setAlwaysOnTop(true);
|
||||
setProgress(0);
|
||||
@@ -94,6 +110,163 @@ public class LoadingWindow extends JDialog {
|
||||
System.currentTimeMillis() - startTime);
|
||||
}
|
||||
|
||||
private void checkAndRefreshExplorer() {
|
||||
if (!IS_WINDOWS) {
|
||||
return;
|
||||
}
|
||||
if (timeAt90Percent == -1) {
|
||||
timeAt90Percent = System.currentTimeMillis();
|
||||
stuckTimer =
|
||||
new Timer(
|
||||
1000,
|
||||
e -> {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (currentTime - timeAt90Percent > stuckThreshold) {
|
||||
try {
|
||||
log.debug(
|
||||
"Attempting Windows explorer refresh due to 90% stuck state");
|
||||
String currentDir = System.getProperty("user.dir");
|
||||
|
||||
// Store current explorer PIDs before we start new one
|
||||
Set<String> existingPids = new HashSet<>();
|
||||
ProcessBuilder listExplorer =
|
||||
new ProcessBuilder(
|
||||
"cmd",
|
||||
"/c",
|
||||
"wmic",
|
||||
"process",
|
||||
"where",
|
||||
"name='explorer.exe'",
|
||||
"get",
|
||||
"ProcessId",
|
||||
"/format:csv");
|
||||
Process process = listExplorer.start();
|
||||
BufferedReader reader =
|
||||
new BufferedReader(
|
||||
new InputStreamReader(
|
||||
process.getInputStream()));
|
||||
String line;
|
||||
while ((line =
|
||||
BoundedLineReader.readLine(
|
||||
reader, 5_000_000))
|
||||
!= null) {
|
||||
if (line.matches(".*\\d+.*")) { // Contains numbers
|
||||
String[] parts = line.trim().split(",");
|
||||
if (parts.length >= 2) {
|
||||
existingPids.add(
|
||||
parts[parts.length - 1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
process.waitFor(2, TimeUnit.SECONDS);
|
||||
|
||||
// Start new explorer
|
||||
ProcessBuilder pb =
|
||||
new ProcessBuilder(
|
||||
"cmd",
|
||||
"/c",
|
||||
"start",
|
||||
"/min",
|
||||
"/b",
|
||||
"explorer.exe",
|
||||
currentDir);
|
||||
pb.redirectErrorStream(true);
|
||||
explorerProcess = pb.start();
|
||||
|
||||
// Schedule cleanup
|
||||
Timer cleanupTimer =
|
||||
new Timer(
|
||||
2000,
|
||||
cleanup -> {
|
||||
try {
|
||||
// Find new explorer processes
|
||||
ProcessBuilder findNewExplorer =
|
||||
new ProcessBuilder(
|
||||
"cmd",
|
||||
"/c",
|
||||
"wmic",
|
||||
"process",
|
||||
"where",
|
||||
"name='explorer.exe'",
|
||||
"get",
|
||||
"ProcessId",
|
||||
"/format:csv");
|
||||
Process newProcess =
|
||||
findNewExplorer.start();
|
||||
BufferedReader newReader =
|
||||
new BufferedReader(
|
||||
new InputStreamReader(
|
||||
newProcess
|
||||
.getInputStream()));
|
||||
String newLine;
|
||||
while ((newLine =
|
||||
BoundedLineReader
|
||||
.readLine(
|
||||
newReader,
|
||||
5_000_000))
|
||||
!= null) {
|
||||
if (newLine.matches(
|
||||
".*\\d+.*")) {
|
||||
String[] parts =
|
||||
newLine.trim()
|
||||
.split(",");
|
||||
if (parts.length >= 2) {
|
||||
String pid =
|
||||
parts[
|
||||
parts.length
|
||||
- 1]
|
||||
.trim();
|
||||
if (!existingPids
|
||||
.contains(
|
||||
pid)) {
|
||||
log.debug(
|
||||
"Found new explorer.exe with PID: "
|
||||
+ pid);
|
||||
ProcessBuilder
|
||||
killProcess =
|
||||
new ProcessBuilder(
|
||||
"taskkill",
|
||||
"/PID",
|
||||
pid,
|
||||
"/F");
|
||||
killProcess
|
||||
.redirectErrorStream(
|
||||
true);
|
||||
Process killResult =
|
||||
killProcess
|
||||
.start();
|
||||
killResult.waitFor(
|
||||
2,
|
||||
TimeUnit
|
||||
.SECONDS);
|
||||
log.debug(
|
||||
"Explorer process terminated: "
|
||||
+ pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
newProcess.waitFor(
|
||||
2, TimeUnit.SECONDS);
|
||||
} catch (Exception ex) {
|
||||
log.error(
|
||||
"Error cleaning up Windows explorer process",
|
||||
ex);
|
||||
}
|
||||
});
|
||||
cleanupTimer.setRepeats(false);
|
||||
cleanupTimer.start();
|
||||
stuckTimer.stop();
|
||||
} catch (Exception ex) {
|
||||
log.error("Error refreshing Windows explorer", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
stuckTimer.setRepeats(true);
|
||||
stuckTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(final int progress) {
|
||||
SwingUtilities.invokeLater(
|
||||
() -> {
|
||||
@@ -115,11 +288,23 @@ public class LoadingWindow extends JDialog {
|
||||
|
||||
// Add thread state logging
|
||||
Thread currentThread = Thread.currentThread();
|
||||
log.debug(
|
||||
log.info(
|
||||
"Current thread state - Name: {}, State: {}, Priority: {}",
|
||||
currentThread.getName(),
|
||||
currentThread.getState(),
|
||||
currentThread.getPriority());
|
||||
|
||||
if (validProgress >= 90 && validProgress < 95) {
|
||||
checkAndRefreshExplorer();
|
||||
} else {
|
||||
// Reset the timer if we move past 95%
|
||||
if (validProgress >= 95) {
|
||||
if (stuckTimer != null) {
|
||||
stuckTimer.stop();
|
||||
}
|
||||
timeAt90Percent = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progressBar.setValue(validProgress);
|
||||
@@ -145,7 +330,7 @@ public class LoadingWindow extends JDialog {
|
||||
statusLabel.setText(validStatus);
|
||||
|
||||
// Log UI state when status changes
|
||||
log.debug(
|
||||
log.info(
|
||||
"UI State - Window visible: {}, Progress: {}%, Status: {}",
|
||||
isVisible(), progressBar.getValue(), validStatus);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -74,6 +75,11 @@ public class AppConfig {
|
||||
: "null";
|
||||
}
|
||||
|
||||
@Bean(name = "languages")
|
||||
public List<String> languages() {
|
||||
return applicationProperties.getUi().getLanguages();
|
||||
}
|
||||
|
||||
@Bean(name = "navBarText")
|
||||
public String navBarText() {
|
||||
String defaultNavBar =
|
||||
|
||||
@@ -9,135 +9,200 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.simpleyaml.configuration.comments.CommentType;
|
||||
import org.simpleyaml.configuration.file.YamlFile;
|
||||
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
||||
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
||||
import java.util.*;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* A naive, line-based approach to merging "settings.yml" with "settings.yml.template" while
|
||||
* preserving exact whitespace, blank lines, and inline comments -- but we only rewrite the file if
|
||||
* the merged content actually differs.
|
||||
*/
|
||||
@Slf4j
|
||||
public class ConfigInitializer {
|
||||
|
||||
public void ensureConfigExists() throws IOException, URISyntaxException {
|
||||
// Define the path to the external config directory
|
||||
// 1) If settings file doesn't exist, create from template
|
||||
Path destPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
||||
|
||||
// Check if the file already exists
|
||||
if (Files.notExists(destPath)) {
|
||||
// Ensure the destination directory exists
|
||||
Files.createDirectories(destPath.getParent());
|
||||
|
||||
// Copy the resource from classpath to the external directory
|
||||
try (InputStream in =
|
||||
getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
|
||||
if (in != null) {
|
||||
Files.copy(in, destPath);
|
||||
} else {
|
||||
if (in == null) {
|
||||
throw new FileNotFoundException(
|
||||
"Resource file not found: settings.yml.template");
|
||||
}
|
||||
Files.copy(in, destPath);
|
||||
}
|
||||
log.info("Created settings file from template");
|
||||
} else {
|
||||
|
||||
// Define the path to the config settings file
|
||||
// 2) Merge existing file with the template
|
||||
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
||||
// Load the template resource
|
||||
URL settingsTemplateResource =
|
||||
getClass().getClassLoader().getResource("settings.yml.template");
|
||||
if (settingsTemplateResource == null) {
|
||||
URL templateResource = getClass().getClassLoader().getResource("settings.yml.template");
|
||||
if (templateResource == null) {
|
||||
throw new IOException("Resource not found: settings.yml.template");
|
||||
}
|
||||
|
||||
// Create a temporary file to copy the resource content
|
||||
// Copy template to a temp location so we can read lines
|
||||
Path tempTemplatePath = Files.createTempFile("settings.yml", ".template");
|
||||
|
||||
try (InputStream in = settingsTemplateResource.openStream()) {
|
||||
try (InputStream in = templateResource.openStream()) {
|
||||
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
final YamlFile settingsTemplateFile = new YamlFile(tempTemplatePath.toFile());
|
||||
DumperOptions yamlOptionsSettingsTemplateFile =
|
||||
((SimpleYamlImplementation) settingsTemplateFile.getImplementation())
|
||||
.getDumperOptions();
|
||||
yamlOptionsSettingsTemplateFile.setSplitLines(false);
|
||||
settingsTemplateFile.loadWithComments();
|
||||
// 2a) Read lines from both files
|
||||
List<String> templateLines = Files.readAllLines(tempTemplatePath);
|
||||
List<String> mainLines = Files.readAllLines(settingsPath);
|
||||
|
||||
final YamlFile settingsFile = new YamlFile(settingsPath.toFile());
|
||||
DumperOptions yamlOptionsSettingsFile =
|
||||
((SimpleYamlImplementation) settingsFile.getImplementation())
|
||||
.getDumperOptions();
|
||||
yamlOptionsSettingsFile.setSplitLines(false);
|
||||
settingsFile.loadWithComments();
|
||||
// 2b) Merge lines
|
||||
List<String> mergedLines = mergeYamlLinesWithTemplate(templateLines, mainLines);
|
||||
|
||||
// Load headers and comments
|
||||
String header = settingsTemplateFile.getHeader();
|
||||
|
||||
// Create a new file for temporary settings
|
||||
final YamlFile tempSettingFile = new YamlFile(settingsPath.toFile());
|
||||
DumperOptions yamlOptionsTempSettingFile =
|
||||
((SimpleYamlImplementation) tempSettingFile.getImplementation())
|
||||
.getDumperOptions();
|
||||
yamlOptionsTempSettingFile.setSplitLines(false);
|
||||
tempSettingFile.createNewFile(true);
|
||||
tempSettingFile.setHeader(header);
|
||||
|
||||
// Get all keys from the template
|
||||
List<String> keys =
|
||||
Arrays.asList(settingsTemplateFile.getKeys(true).toArray(new String[0]));
|
||||
|
||||
for (String key : keys) {
|
||||
if (!key.contains(".")) {
|
||||
// Add blank lines and comments to specific sections
|
||||
tempSettingFile
|
||||
.path(key)
|
||||
.comment(settingsTemplateFile.getComment(key))
|
||||
.blankLine();
|
||||
continue;
|
||||
}
|
||||
// Copy settings from the template to the settings.yml file
|
||||
changeConfigItemFromCommentToKeyValue(
|
||||
settingsTemplateFile, settingsFile, tempSettingFile, key);
|
||||
// 2c) Only write if there's an actual difference
|
||||
if (!mergedLines.equals(mainLines)) {
|
||||
Files.write(settingsPath, mergedLines);
|
||||
log.info("Settings file updated based on template changes.");
|
||||
} else {
|
||||
log.info("No changes detected; settings file left as-is.");
|
||||
}
|
||||
|
||||
// Save the settings.yml file
|
||||
tempSettingFile.save();
|
||||
Files.deleteIfExists(tempTemplatePath);
|
||||
}
|
||||
|
||||
// Create custom settings file if it doesn't exist
|
||||
// 3) Ensure custom settings file exists
|
||||
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());
|
||||
if (!Files.exists(customSettingsPath)) {
|
||||
Files.createFile(customSettingsPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void changeConfigItemFromCommentToKeyValue(
|
||||
final YamlFile settingsTemplateFile,
|
||||
final YamlFile settingsFile,
|
||||
final YamlFile tempSettingFile,
|
||||
String path) {
|
||||
if (settingsFile.get(path) == null && settingsTemplateFile.get(path) != null) {
|
||||
// If the key is only in the template, add it to the temporary settings with comments
|
||||
tempSettingFile
|
||||
.path(path)
|
||||
.set(settingsTemplateFile.get(path))
|
||||
.comment(settingsTemplateFile.getComment(path, CommentType.BLOCK))
|
||||
.commentSide(settingsTemplateFile.getComment(path, CommentType.SIDE));
|
||||
} else if (settingsFile.get(path) != null && settingsTemplateFile.get(path) != null) {
|
||||
// If the key is in both, update the temporary settings with the main settings' value
|
||||
// and comments
|
||||
tempSettingFile
|
||||
.path(path)
|
||||
.set(settingsFile.get(path))
|
||||
.comment(settingsTemplateFile.getComment(path, CommentType.BLOCK))
|
||||
.commentSide(settingsTemplateFile.getComment(path, CommentType.SIDE));
|
||||
} else {
|
||||
// Log if the key is not found in both YAML files
|
||||
log.info("Key not found in both YAML files: " + path);
|
||||
/**
|
||||
* Merge logic that: - Reads the template lines block-by-block (where a "block" = a key and all
|
||||
* the lines that belong to it), - If the main file has that key, we keep the main file's block
|
||||
* (preserving whitespace + inline comments). - Otherwise, we insert the template's block. - We
|
||||
* also remove keys from main that no longer exist in the template.
|
||||
*
|
||||
* @param templateLines lines from settings.yml.template
|
||||
* @param mainLines lines from the existing settings.yml
|
||||
* @return merged lines
|
||||
*/
|
||||
private List<String> mergeYamlLinesWithTemplate(
|
||||
List<String> templateLines, List<String> mainLines) {
|
||||
|
||||
// 1) Parse template lines into an ordered map: path -> Block
|
||||
LinkedHashMap<String, Block> templateBlocks = parseYamlBlocks(templateLines);
|
||||
|
||||
// 2) Parse main lines into a map: path -> Block
|
||||
LinkedHashMap<String, Block> mainBlocks = parseYamlBlocks(mainLines);
|
||||
|
||||
// 3) Build the final list by iterating template blocks in order
|
||||
List<String> merged = new ArrayList<>();
|
||||
for (Map.Entry<String, Block> entry : templateBlocks.entrySet()) {
|
||||
String path = entry.getKey();
|
||||
Block templateBlock = entry.getValue();
|
||||
|
||||
if (mainBlocks.containsKey(path)) {
|
||||
// If main has the same block, prefer main's lines
|
||||
merged.addAll(mainBlocks.get(path).lines);
|
||||
} else {
|
||||
// Otherwise, add the template block
|
||||
merged.addAll(templateBlock.lines);
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a list of lines into a map of "path -> Block" where "Block" is all lines that belong to
|
||||
* that key (including subsequent indented lines). Very naive approach that may not work with
|
||||
* advanced YAML.
|
||||
*/
|
||||
private LinkedHashMap<String, Block> parseYamlBlocks(List<String> lines) {
|
||||
LinkedHashMap<String, Block> blocks = new LinkedHashMap<>();
|
||||
|
||||
Block currentBlock = null;
|
||||
String currentPath = null;
|
||||
|
||||
for (String line : lines) {
|
||||
if (isLikelyKeyLine(line)) {
|
||||
// Found a new "key: ..." line
|
||||
if (currentBlock != null && currentPath != null) {
|
||||
blocks.put(currentPath, currentBlock);
|
||||
}
|
||||
currentBlock = new Block();
|
||||
currentBlock.lines.add(line);
|
||||
currentPath = computePathForLine(line);
|
||||
} else {
|
||||
// Continuation of current block (comments, blank lines, sub-lines)
|
||||
if (currentBlock == null) {
|
||||
// If file starts with comments/blank lines, treat as "header block" with path
|
||||
// ""
|
||||
currentBlock = new Block();
|
||||
currentPath = "";
|
||||
}
|
||||
currentBlock.lines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentBlock != null && currentPath != null) {
|
||||
blocks.put(currentPath, currentBlock);
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the line is likely "key:" or "key: value", ignoring comments/blank. Skips lines
|
||||
* starting with "-" or "#".
|
||||
*/
|
||||
private boolean isLikelyKeyLine(String line) {
|
||||
String trimmed = line.trim();
|
||||
if (trimmed.isEmpty() || trimmed.startsWith("#") || trimmed.startsWith("-")) {
|
||||
return false;
|
||||
}
|
||||
int colonIdx = trimmed.indexOf(':');
|
||||
return (colonIdx > 0); // someKey:
|
||||
}
|
||||
|
||||
// For a line like "security: ", returns "security" or "security.enableLogin"
|
||||
// by looking at indentation. Very naive.
|
||||
private static final Deque<String> pathStack = new ArrayDeque<>();
|
||||
private static int currentIndentLevel = 0;
|
||||
|
||||
private String computePathForLine(String line) {
|
||||
// count leading spaces
|
||||
int leadingSpaces = 0;
|
||||
for (char c : line.toCharArray()) {
|
||||
if (c == ' ') leadingSpaces++;
|
||||
else break;
|
||||
}
|
||||
// assume 2 spaces = 1 indent
|
||||
int indentLevel = leadingSpaces / 2;
|
||||
|
||||
String trimmed = line.trim();
|
||||
int colonIdx = trimmed.indexOf(':');
|
||||
String keyName = trimmed.substring(0, colonIdx).trim();
|
||||
|
||||
// pop stack until we match the new indent level
|
||||
while (currentIndentLevel >= indentLevel && !pathStack.isEmpty()) {
|
||||
pathStack.pop();
|
||||
currentIndentLevel--;
|
||||
}
|
||||
|
||||
// push the new key
|
||||
pathStack.push(keyName);
|
||||
currentIndentLevel = indentLevel;
|
||||
|
||||
// build path by reversing the stack
|
||||
String[] arr = pathStack.toArray(new String[0]);
|
||||
List<String> reversed = Arrays.asList(arr);
|
||||
Collections.reverse(reversed);
|
||||
return String.join(".", reversed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple holder for the lines that comprise a "block" (i.e. a key and its subsequent lines).
|
||||
*/
|
||||
private static class Block {
|
||||
List<String> lines = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Convert", "url-to-pdf");
|
||||
addEndpointToGroup("Convert", "markdown-to-pdf");
|
||||
addEndpointToGroup("Convert", "pdf-to-csv");
|
||||
addEndpointToGroup("Convert", "pdf-to-markdown");
|
||||
|
||||
// Adding endpoints to "Security" group
|
||||
addEndpointToGroup("Security", "add-password");
|
||||
@@ -243,6 +244,7 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Java", REMOVE_BLANKS);
|
||||
addEndpointToGroup("Java", "pdf-to-text");
|
||||
addEndpointToGroup("Java", "remove-image-pdf");
|
||||
addEndpointToGroup("Java", "pdf-to-markdown");
|
||||
|
||||
// Javascript
|
||||
addEndpointToGroup("Javascript", "pdf-organizer");
|
||||
@@ -258,12 +260,11 @@ public class EndpointConfiguration {
|
||||
// Weasyprint dependent endpoints
|
||||
addEndpointToGroup("Weasyprint", "html-to-pdf");
|
||||
addEndpointToGroup("Weasyprint", "url-to-pdf");
|
||||
addEndpointToGroup("Weasyprint", "markdown-to-pdf");
|
||||
|
||||
// Pdftohtml dependent endpoints
|
||||
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
||||
|
||||
// disabled for now while we resolve issues
|
||||
disableEndpoint("pdf-to-pdfa");
|
||||
addEndpointToGroup("Pdftohtml", "pdf-to-markdown");
|
||||
}
|
||||
|
||||
private void processEnvironmentConfigs() {
|
||||
|
||||
@@ -21,9 +21,9 @@ public class ExternalAppDepConfig {
|
||||
|
||||
{
|
||||
put("soffice", List.of("LibreOffice"));
|
||||
put("weasyprint", List.of("Weasyprint"));
|
||||
put("/opt/venv/bin/weasyprint", List.of("Weasyprint"));
|
||||
put("pdftohtml", List.of("Pdftohtml"));
|
||||
put("unoconv", List.of("Unoconv"));
|
||||
put("/opt/venv/bin/unoconvert", List.of("Unoconv"));
|
||||
put("qpdf", List.of("qpdf"));
|
||||
put("tesseract", List.of("tesseract"));
|
||||
}
|
||||
@@ -101,9 +101,9 @@ public class ExternalAppDepConfig {
|
||||
checkDependencyAndDisableGroup("tesseract");
|
||||
checkDependencyAndDisableGroup("soffice");
|
||||
checkDependencyAndDisableGroup("qpdf");
|
||||
checkDependencyAndDisableGroup("weasyprint");
|
||||
checkDependencyAndDisableGroup("/opt/venv/bin/weasyprint");
|
||||
checkDependencyAndDisableGroup("pdftohtml");
|
||||
checkDependencyAndDisableGroup("unoconv");
|
||||
checkDependencyAndDisableGroup("/opt/venv/bin/unoconvert");
|
||||
// Special handling for Python/OpenCV dependencies
|
||||
boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python");
|
||||
if (!pythonAvailable) {
|
||||
|
||||
@@ -7,8 +7,10 @@ import org.springframework.context.annotation.Configuration;
|
||||
import com.posthog.java.PostHog;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class PostHogConfig {
|
||||
|
||||
@Value("${posthog.api.key}")
|
||||
@@ -21,7 +23,11 @@ public class PostHogConfig {
|
||||
|
||||
@Bean
|
||||
public PostHog postHogClient() {
|
||||
postHogClient = new PostHog.Builder(posthogApiKey).host(posthogHost).build();
|
||||
postHogClient =
|
||||
new PostHog.Builder(posthogApiKey)
|
||||
.host(posthogHost)
|
||||
.logger(new PostHogLoggerImpl())
|
||||
.build();
|
||||
return postHogClient;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.posthog.java.PostHogLogger;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PostHogLoggerImpl implements PostHogLogger {
|
||||
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
log.debug(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message) {
|
||||
log.info(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message) {
|
||||
log.warn(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message) {
|
||||
log.error(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message, Throwable throwable) {
|
||||
if (message.contains("Error sending events to PostHog")) {
|
||||
log.warn(
|
||||
"Error sending metrics, Likely caused by no internet connection. Non Blocking");
|
||||
} else {
|
||||
log.error(message, throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,11 @@ public class DatabaseConfig {
|
||||
public DatabaseConfig(
|
||||
ApplicationProperties applicationProperties,
|
||||
@Qualifier("runningEE") boolean runningEE) {
|
||||
DATASOURCE_DEFAULT_URL = "jdbc:h2:file:" + InstallationPathConfig.getConfigPath() + File.separator + "stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE";
|
||||
DATASOURCE_DEFAULT_URL =
|
||||
"jdbc:h2:file:"
|
||||
+ InstallationPathConfig.getConfigPath()
|
||||
+ File.separator
|
||||
+ "stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE";
|
||||
this.applicationProperties = applicationProperties;
|
||||
this.runningEE = runningEE;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -26,11 +28,14 @@ public class AdditionalLanguageJsController {
|
||||
@Hidden
|
||||
@GetMapping(value = "/additionalLanguageCode.js", produces = "application/javascript")
|
||||
public void generateAdditionalLanguageJs(HttpServletResponse response) throws IOException {
|
||||
List<String> supportedLanguages = languageService.getSupportedLanguages();
|
||||
Set<String> supportedLanguages = languageService.getSupportedLanguages();
|
||||
response.setContentType("application/javascript");
|
||||
PrintWriter writer = response.getWriter();
|
||||
// Erstelle das JavaScript dynamisch
|
||||
writer.println("const supportedLanguages = " + toJsonArray(supportedLanguages) + ";");
|
||||
writer.println(
|
||||
"const supportedLanguages = "
|
||||
+ toJsonArray(new ArrayList<>(supportedLanguages))
|
||||
+ ";");
|
||||
// Generiere die `getDetailedLanguageCode`-Funktion
|
||||
writer.println(
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||
import org.apache.pdfbox.pdmodel.encryption.PDEncryption;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/analysis")
|
||||
@Tag(name = "Analysis", description = "Analysis APIs")
|
||||
public class AnalysisController {
|
||||
|
||||
@PostMapping(value = "/page-count", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Get PDF page count",
|
||||
description = "Returns total number of pages in PDF. Input:PDF Output:JSON Type:SISO")
|
||||
public Map<String, Integer> getPageCount(@ModelAttribute PDFFile file) throws IOException {
|
||||
try (PDDocument document = Loader.loadPDF(file.getFileInput().getBytes())) {
|
||||
return Map.of("pageCount", document.getNumberOfPages());
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(value = "/basic-info", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Get basic PDF information",
|
||||
description = "Returns page count, version, file size. Input:PDF Output:JSON Type:SISO")
|
||||
public Map<String, Object> getBasicInfo(@ModelAttribute PDFFile file) throws IOException {
|
||||
try (PDDocument document = Loader.loadPDF(file.getFileInput().getBytes())) {
|
||||
Map<String, Object> info = new HashMap<>();
|
||||
info.put("pageCount", document.getNumberOfPages());
|
||||
info.put("pdfVersion", document.getVersion());
|
||||
info.put("fileSize", file.getFileInput().getSize());
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(value = "/document-properties", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Get PDF document properties",
|
||||
description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO")
|
||||
public Map<String, String> getDocumentProperties(@ModelAttribute PDFFile file)
|
||||
throws IOException {
|
||||
try (PDDocument document = Loader.loadPDF(file.getFileInput().getBytes())) {
|
||||
PDDocumentInformation info = document.getDocumentInformation();
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put("title", info.getTitle());
|
||||
properties.put("author", info.getAuthor());
|
||||
properties.put("subject", info.getSubject());
|
||||
properties.put("keywords", info.getKeywords());
|
||||
properties.put("creator", info.getCreator());
|
||||
properties.put("producer", info.getProducer());
|
||||
properties.put("creationDate", info.getCreationDate().toString());
|
||||
properties.put("modificationDate", info.getModificationDate().toString());
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(value = "/page-dimensions", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Get page dimensions for all pages",
|
||||
description = "Returns width and height of each page. Input:PDF Output:JSON Type:SISO")
|
||||
public List<Map<String, Float>> getPageDimensions(@ModelAttribute PDFFile file)
|
||||
throws IOException {
|
||||
try (PDDocument document = Loader.loadPDF(file.getFileInput().getBytes())) {
|
||||
List<Map<String, Float>> dimensions = new ArrayList<>();
|
||||
PDPageTree pages = document.getPages();
|
||||
|
||||
for (PDPage page : pages) {
|
||||
Map<String, Float> pageDim = new HashMap<>();
|
||||
pageDim.put("width", page.getBBox().getWidth());
|
||||
pageDim.put("height", page.getBBox().getHeight());
|
||||
dimensions.add(pageDim);
|
||||
}
|
||||
return dimensions;
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(value = "/form-fields", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Get form field information",
|
||||
description =
|
||||
"Returns count and details of form fields. Input:PDF Output:JSON Type:SISO")
|
||||
public Map<String, Object> getFormFields(@ModelAttribute PDFFile file) throws IOException {
|
||||
try (PDDocument document = Loader.loadPDF(file.getFileInput().getBytes())) {
|
||||
Map<String, Object> formInfo = new HashMap<>();
|
||||
PDAcroForm form = document.getDocumentCatalog().getAcroForm();
|
||||
|
||||
if (form != null) {
|
||||
formInfo.put("fieldCount", form.getFields().size());
|
||||
formInfo.put("hasXFA", form.hasXFA());
|
||||
formInfo.put("isSignaturesExist", form.isSignaturesExist());
|
||||
} else {
|
||||
formInfo.put("fieldCount", 0);
|
||||
formInfo.put("hasXFA", false);
|
||||
formInfo.put("isSignaturesExist", false);
|
||||
}
|
||||
return formInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(value = "/annotation-info", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Get annotation information",
|
||||
description = "Returns count and types of annotations. Input:PDF Output:JSON Type:SISO")
|
||||
public Map<String, Object> getAnnotationInfo(@ModelAttribute PDFFile file) throws IOException {
|
||||
try (PDDocument document = Loader.loadPDF(file.getFileInput().getBytes())) {
|
||||
Map<String, Object> annotInfo = new HashMap<>();
|
||||
int totalAnnotations = 0;
|
||||
Map<String, Integer> annotationTypes = new HashMap<>();
|
||||
|
||||
for (PDPage page : document.getPages()) {
|
||||
for (PDAnnotation annot : page.getAnnotations()) {
|
||||
totalAnnotations++;
|
||||
String subType = annot.getSubtype();
|
||||
annotationTypes.merge(subType, 1, Integer::sum);
|
||||
}
|
||||
}
|
||||
|
||||
annotInfo.put("totalCount", totalAnnotations);
|
||||
annotInfo.put("typeBreakdown", annotationTypes);
|
||||
return annotInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(value = "/font-info", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Get font information",
|
||||
description =
|
||||
"Returns list of fonts used in the document. Input:PDF Output:JSON Type:SISO")
|
||||
public Map<String, Object> getFontInfo(@ModelAttribute PDFFile file) throws IOException {
|
||||
try (PDDocument document = Loader.loadPDF(file.getFileInput().getBytes())) {
|
||||
Map<String, Object> fontInfo = new HashMap<>();
|
||||
Set<String> fontNames = new HashSet<>();
|
||||
|
||||
for (PDPage page : document.getPages()) {
|
||||
for (COSName font : page.getResources().getFontNames()) {
|
||||
fontNames.add(font.getName());
|
||||
}
|
||||
}
|
||||
|
||||
fontInfo.put("fontCount", fontNames.size());
|
||||
fontInfo.put("fonts", fontNames);
|
||||
return fontInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(value = "/security-info", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Get security information",
|
||||
description =
|
||||
"Returns encryption and permission details. Input:PDF Output:JSON Type:SISO")
|
||||
public Map<String, Object> getSecurityInfo(@ModelAttribute PDFFile file) throws IOException {
|
||||
try (PDDocument document = Loader.loadPDF(file.getFileInput().getBytes())) {
|
||||
Map<String, Object> securityInfo = new HashMap<>();
|
||||
PDEncryption encryption = document.getEncryption();
|
||||
|
||||
if (encryption != null) {
|
||||
securityInfo.put("isEncrypted", true);
|
||||
securityInfo.put("keyLength", encryption.getLength());
|
||||
|
||||
// Get permissions
|
||||
Map<String, Boolean> permissions = new HashMap<>();
|
||||
permissions.put("canPrint", document.getCurrentAccessPermission().canPrint());
|
||||
permissions.put("canModify", document.getCurrentAccessPermission().canModify());
|
||||
permissions.put(
|
||||
"canExtractContent",
|
||||
document.getCurrentAccessPermission().canExtractContent());
|
||||
permissions.put(
|
||||
"canModifyAnnotations",
|
||||
document.getCurrentAccessPermission().canModifyAnnotations());
|
||||
|
||||
securityInfo.put("permissions", permissions);
|
||||
} else {
|
||||
securityInfo.put("isEncrypted", false);
|
||||
}
|
||||
|
||||
return securityInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,38 @@ public class RearrangePagesPDFController {
|
||||
return newPageOrderZeroBased;
|
||||
}
|
||||
|
||||
private List<Integer> processSortTypes(String sortTypes, int totalPages) {
|
||||
private List<Integer> duplicate(int totalPages, String pageOrder) {
|
||||
List<Integer> newPageOrder = new ArrayList<>();
|
||||
int duplicateCount;
|
||||
|
||||
try {
|
||||
// Parse the duplicate count from pageOrder
|
||||
duplicateCount =
|
||||
pageOrder != null && !pageOrder.isEmpty()
|
||||
? Integer.parseInt(pageOrder.trim())
|
||||
: 2; // Default to 2 if not specified
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("Invalid duplicate count specified", e);
|
||||
duplicateCount = 2; // Default to 2 if invalid input
|
||||
}
|
||||
|
||||
// Validate duplicate count
|
||||
if (duplicateCount < 1) {
|
||||
duplicateCount = 2; // Default to 2 if invalid input
|
||||
}
|
||||
|
||||
// For each page in the document
|
||||
for (int pageNum = 0; pageNum < totalPages; pageNum++) {
|
||||
// Add the current page index duplicateCount times
|
||||
for (int dupCount = 0; dupCount < duplicateCount; dupCount++) {
|
||||
newPageOrder.add(pageNum);
|
||||
}
|
||||
}
|
||||
|
||||
return newPageOrder;
|
||||
}
|
||||
|
||||
private List<Integer> processSortTypes(String sortTypes, int totalPages, String pageOrder) {
|
||||
try {
|
||||
SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase());
|
||||
switch (mode) {
|
||||
@@ -196,6 +227,8 @@ public class RearrangePagesPDFController {
|
||||
return removeLast(totalPages);
|
||||
case REMOVE_FIRST_AND_LAST:
|
||||
return removeFirstAndLast(totalPages);
|
||||
case DUPLICATE:
|
||||
return duplicate(totalPages, pageOrder);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported custom mode");
|
||||
}
|
||||
@@ -223,8 +256,10 @@ public class RearrangePagesPDFController {
|
||||
String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0];
|
||||
int totalPages = document.getNumberOfPages();
|
||||
List<Integer> newPageOrder;
|
||||
if (sortType != null && sortType.length() > 0) {
|
||||
newPageOrder = processSortTypes(sortType, totalPages);
|
||||
if (sortType != null
|
||||
&& sortType.length() > 0
|
||||
&& !"custom".equals(sortType.toLowerCase())) {
|
||||
newPageOrder = processSortTypes(sortType, totalPages, pageOrder);
|
||||
} else {
|
||||
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
|
||||
}
|
||||
|
||||
@@ -100,6 +100,8 @@ public class SplitPdfBySectionsController {
|
||||
|
||||
if (sectionNum == horiz * verti) pageNum++;
|
||||
}
|
||||
|
||||
zipOut.finish();
|
||||
data = Files.readAllBytes(zipFile);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.FileToPdf;
|
||||
@@ -27,19 +28,23 @@ public class ConvertHtmlToPDF {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
@Autowired
|
||||
public ConvertHtmlToPDF(
|
||||
CustomPDDocumentFactory pdfDocumentFactory,
|
||||
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled,
|
||||
ApplicationProperties applicationProperties) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
|
||||
@Operation(
|
||||
summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF",
|
||||
description =
|
||||
"This endpoint takes an HTML or ZIP file input and converts it to a PDF format.")
|
||||
"This endpoint takes an HTML or ZIP file input and converts it to a PDF format. Input:HTML Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute HTMLToPdfRequest request)
|
||||
throws Exception {
|
||||
MultipartFile fileInput = request.getFileInput();
|
||||
@@ -54,12 +59,17 @@ public class ConvertHtmlToPDF {
|
||||
|| (!originalFilename.endsWith(".html") && !originalFilename.endsWith(".zip"))) {
|
||||
throw new IllegalArgumentException("File must be either .html or .zip format.");
|
||||
}
|
||||
|
||||
boolean disableSanitize =
|
||||
Boolean.TRUE.equals(applicationProperties.getSystem().getDisableSanitize());
|
||||
|
||||
byte[] pdfBytes =
|
||||
FileToPdf.convertHtmlToPdf(
|
||||
request,
|
||||
fileInput.getBytes(),
|
||||
originalFilename,
|
||||
bookAndHtmlFormatsInstalled);
|
||||
bookAndHtmlFormatsInstalled,
|
||||
disableSanitize);
|
||||
|
||||
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.api.GeneralFile;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.FileToPdf;
|
||||
@@ -37,12 +38,16 @@ public class ConvertMarkdownToPdf {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
@Autowired
|
||||
public ConvertMarkdownToPdf(
|
||||
CustomPDDocumentFactory pdfDocumentFactory,
|
||||
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled,
|
||||
ApplicationProperties applicationProperties) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
|
||||
@@ -76,12 +81,16 @@ public class ConvertMarkdownToPdf {
|
||||
|
||||
String htmlContent = renderer.render(document);
|
||||
|
||||
boolean disableSanitize =
|
||||
Boolean.TRUE.equals(applicationProperties.getSystem().getDisableSanitize());
|
||||
|
||||
byte[] pdfBytes =
|
||||
FileToPdf.convertHtmlToPdf(
|
||||
null,
|
||||
htmlContent.getBytes(),
|
||||
"converted.html",
|
||||
bookAndHtmlFormatsInstalled);
|
||||
bookAndHtmlFormatsInstalled,
|
||||
disableSanitize);
|
||||
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||
String outputFilename =
|
||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||
|
||||
@@ -61,13 +61,13 @@ public class ConvertOfficeController {
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
"unoconv",
|
||||
"-vvv",
|
||||
"-f",
|
||||
"/opt/venv/bin/unoconvert",
|
||||
"--port",
|
||||
"2003",
|
||||
"--convert-to",
|
||||
"pdf",
|
||||
"-o",
|
||||
tempOutputFile.toString(),
|
||||
tempInputFile.toString()));
|
||||
tempInputFile.toString(),
|
||||
tempOutputFile.toString()));
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
@@ -73,8 +73,8 @@ public class ConvertPDFToPDFA {
|
||||
// Determine PDF/A filter based on requested format
|
||||
String pdfFilter =
|
||||
"pdfa".equals(outputFormat)
|
||||
? "writer_pdf_Export:{'SelectPdfVersion':{'Value':'2'}}:writer_pdf_Export"
|
||||
: "writer_pdf_Export:{'SelectPdfVersion':{'Value':'1'}}:writer_pdf_Export";
|
||||
? "pdf:writer_pdf_Export:{\"SelectPdfVersion\":{\"type\":\"long\",\"value\":\"2\"}}"
|
||||
: "pdf:writer_pdf_Export:{\"SelectPdfVersion\":{\"type\":\"long\",\"value\":\"1\"}}";
|
||||
|
||||
// Prepare LibreOffice command
|
||||
List<String> command =
|
||||
@@ -84,7 +84,7 @@ public class ConvertPDFToPDFA {
|
||||
"--headless",
|
||||
"--nologo",
|
||||
"--convert-to",
|
||||
"pdf:" + pdfFilter,
|
||||
pdfFilter,
|
||||
"--outdir",
|
||||
tempOutputDir.toString(),
|
||||
tempInputFile.toString()));
|
||||
|
||||
@@ -65,7 +65,7 @@ public class ConvertWebsiteToPDF {
|
||||
|
||||
// Prepare the WeasyPrint command
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("weasyprint");
|
||||
command.add("/opt/venv/bin/weasyprint");
|
||||
command.add(URL);
|
||||
command.add(tempOutputFile.toString());
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.QuoteMode;
|
||||
@@ -18,18 +25,18 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.extract.PDFFilePage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||
import stirling.software.SPDF.pdf.FlexibleCSVWriter;
|
||||
import technology.tabula.ObjectExtractor;
|
||||
import technology.tabula.Page;
|
||||
import technology.tabula.Table;
|
||||
import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
|
||||
import technology.tabula.writers.Writer;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/convert")
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
@Slf4j
|
||||
public class ExtractCSVController {
|
||||
|
||||
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
|
||||
@@ -37,31 +44,80 @@ public class ExtractCSVController {
|
||||
summary = "Extracts a CSV document from a PDF",
|
||||
description =
|
||||
"This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
|
||||
public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception {
|
||||
StringWriter writer = new StringWriter();
|
||||
public ResponseEntity<?> pdfToCsv(@ModelAttribute PDFWithPageNums form) throws Exception {
|
||||
String baseName = getBaseName(form.getFileInput().getOriginalFilename());
|
||||
List<CsvEntry> csvEntries = new ArrayList<>();
|
||||
|
||||
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
|
||||
CSVFormat format =
|
||||
CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build();
|
||||
Writer csvWriter = new FlexibleCSVWriter(format);
|
||||
List<Integer> pages = form.getPageNumbersList(document, true);
|
||||
SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
|
||||
try (ObjectExtractor extractor = new ObjectExtractor(document)) {
|
||||
Page page = extractor.extract(form.getPageId());
|
||||
List<Table> tables = sea.extract(page);
|
||||
csvWriter.write(writer, tables);
|
||||
CSVFormat format = CSVFormat.EXCEL.builder()
|
||||
.setEscape('"')
|
||||
.setQuoteMode(QuoteMode.ALL)
|
||||
.build();
|
||||
|
||||
for (int pageNum : pages) {
|
||||
try (ObjectExtractor extractor = new ObjectExtractor(document)) {
|
||||
log.info("{}",pageNum);
|
||||
Page page = extractor.extract(pageNum);
|
||||
List<Table> tables = sea.extract(page);
|
||||
|
||||
for (int i = 0; i < tables.size(); i++) {
|
||||
StringWriter sw = new StringWriter();
|
||||
FlexibleCSVWriter csvWriter = new FlexibleCSVWriter(format);
|
||||
csvWriter.write(sw, Collections.singletonList(tables.get(i)));
|
||||
|
||||
String entryName = generateEntryName(baseName, pageNum, i + 1);
|
||||
csvEntries.add(new CsvEntry(entryName, sw.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (csvEntries.isEmpty()) {
|
||||
return ResponseEntity.noContent().build();
|
||||
} else if (csvEntries.size() == 1) {
|
||||
return createCsvResponse(csvEntries.get(0), baseName);
|
||||
} else {
|
||||
return createZipResponse(csvEntries, baseName);
|
||||
}
|
||||
}
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentDisposition(
|
||||
ContentDisposition.builder("attachment")
|
||||
.filename(
|
||||
form.getFileInput()
|
||||
.getOriginalFilename()
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_extracted.csv")
|
||||
.build());
|
||||
headers.setContentType(MediaType.parseMediaType("text/csv"));
|
||||
|
||||
return ResponseEntity.ok().headers(headers).body(writer.toString());
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> createZipResponse(List<CsvEntry> entries, String baseName) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(baos)) {
|
||||
for (CsvEntry entry : entries) {
|
||||
ZipEntry zipEntry = new ZipEntry(entry.filename());
|
||||
zipOut.putNextEntry(zipEntry);
|
||||
zipOut.write(entry.content().getBytes(StandardCharsets.UTF_8));
|
||||
zipOut.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentDisposition(ContentDisposition.builder("attachment")
|
||||
.filename(baseName + "_extracted.zip").build());
|
||||
headers.setContentType(MediaType.parseMediaType("application/zip"));
|
||||
|
||||
return ResponseEntity.ok().headers(headers).body(baos.toByteArray());
|
||||
}
|
||||
|
||||
private ResponseEntity<String> createCsvResponse(CsvEntry entry, String baseName) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentDisposition(ContentDisposition.builder("attachment")
|
||||
.filename(baseName + "_extracted.csv").build());
|
||||
headers.setContentType(MediaType.parseMediaType("text/csv"));
|
||||
|
||||
return ResponseEntity.ok().headers(headers).body(entry.content());
|
||||
}
|
||||
|
||||
private String generateEntryName(String baseName, int pageNum, int tableIndex) {
|
||||
return String.format("%s_p%d_t%d.csv", baseName, pageNum, tableIndex);
|
||||
}
|
||||
|
||||
private String getBaseName(String filename) {
|
||||
return filename.replaceFirst("[.][^.]+$", "");
|
||||
}
|
||||
|
||||
private record CsvEntry(String filename, String content) {}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
@@ -41,8 +43,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||
public class AutoSplitPdfController {
|
||||
|
||||
private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF";
|
||||
private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF";
|
||||
private static final Set<String> VALID_QR_CONTENTS =
|
||||
new HashSet<>(
|
||||
Set.of(
|
||||
"https://github.com/Stirling-Tools/Stirling-PDF",
|
||||
"https://github.com/Frooodle/Stirling-PDF",
|
||||
"https://stirlingpdf.com"));
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@@ -120,13 +126,14 @@ public class AutoSplitPdfController {
|
||||
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
|
||||
String result = decodeQRCode(bim);
|
||||
if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) {
|
||||
|
||||
boolean isValidQrCode = VALID_QR_CONTENTS.contains(result);
|
||||
log.debug("detected qr code {}, code is vale={}", result, isValidQrCode);
|
||||
if (isValidQrCode && page != 0) {
|
||||
splitDocuments.add(new PDDocument());
|
||||
}
|
||||
|
||||
if (!splitDocuments.isEmpty()
|
||||
&& !QR_CONTENT.equals(result)
|
||||
&& !QR_CONTENT_OLD.equals(result)) {
|
||||
if (!splitDocuments.isEmpty() && !isValidQrCode) {
|
||||
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
|
||||
} else if (page == 0) {
|
||||
PDDocument firstDocument = new PDDocument();
|
||||
@@ -135,7 +142,7 @@ public class AutoSplitPdfController {
|
||||
}
|
||||
|
||||
// If duplexMode is true and current page is a divider, then skip next page
|
||||
if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) {
|
||||
if (duplexMode && isValidQrCode) {
|
||||
page++;
|
||||
}
|
||||
}
|
||||
@@ -168,6 +175,9 @@ public class AutoSplitPdfController {
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
} catch (Exception e) {
|
||||
log.error("Error in auto split", e);
|
||||
throw e;
|
||||
} finally {
|
||||
// Clean up resources
|
||||
if (document != null) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -94,7 +95,9 @@ public class CompressController {
|
||||
}
|
||||
}
|
||||
}
|
||||
doc.save(pdfFile.toString());
|
||||
Path tempOutput = Files.createTempFile("output_", ".pdf");
|
||||
doc.save(tempOutput.toString());
|
||||
Files.move(tempOutput, pdfFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +191,7 @@ public class CompressController {
|
||||
optimizeLevel =
|
||||
incrementOptimizeLevel(
|
||||
optimizeLevel, outputFileSize, expectedOutputSize);
|
||||
if (autoMode && optimizeLevel > 9) {
|
||||
if (autoMode && optimizeLevel >= 9) {
|
||||
log.info("Maximum compression level reached in auto mode");
|
||||
sizeMet = true;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public class ExtractImagesController {
|
||||
@Operation(
|
||||
summary = "Extract images from a PDF file",
|
||||
description =
|
||||
"This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input: PDF Output: IMAGE/ZIP Type: SIMO")
|
||||
"This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO")
|
||||
public ResponseEntity<byte[]> extractImages(@ModelAttribute PDFExtractImagesRequest request)
|
||||
throws IOException, InterruptedException, ExecutionException {
|
||||
MultipartFile file = request.getFileInput();
|
||||
|
||||
@@ -46,7 +46,7 @@ public class FlattenController {
|
||||
@Operation(
|
||||
summary = "Flatten PDF form fields or full page",
|
||||
description =
|
||||
"Flattening just PDF form fields or converting each page to images to make text unselectable. Input: PDF, Output: PDF. Type: SISO")
|
||||
"Flattening just PDF form fields or converting each page to images to make text unselectable. Input:PDF, Output:PDF. Type:SISO")
|
||||
public ResponseEntity<byte[]> flatten(@ModelAttribute FlattenRequest request) throws Exception {
|
||||
MultipartFile file = request.getFileInput();
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.BoundedLineReader;
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -65,6 +66,10 @@ public class OCRController {
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
||||
@Operation(
|
||||
summary = "Process PDF files with OCR using Tesseract",
|
||||
description =
|
||||
"Takes a PDF file as input, performs OCR using specified languages and OCR type (skip-text/force-ocr), and returns the processed PDF. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> processPdfWithOCR(
|
||||
@ModelAttribute ProcessPdfWithOcrRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
@@ -25,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.PipelineConfig;
|
||||
import stirling.software.SPDF.model.PipelineResult;
|
||||
import stirling.software.SPDF.model.api.HandleDataRequest;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -58,7 +59,8 @@ public class PipelineController {
|
||||
if (inputFiles == null || inputFiles.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config);
|
||||
PipelineResult result = processor.runPipelineAgainstFiles(inputFiles, config);
|
||||
List<Resource> outputFiles = result.getOutputFiles();
|
||||
if (outputFiles != null && outputFiles.size() == 1) {
|
||||
// If there is only one file, return it directly
|
||||
Resource singleFile = outputFiles.get(0);
|
||||
|
||||
@@ -27,6 +27,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||
import stirling.software.SPDF.model.PipelineConfig;
|
||||
import stirling.software.SPDF.model.PipelineOperation;
|
||||
import stirling.software.SPDF.model.PipelineResult;
|
||||
import stirling.software.SPDF.utils.FileMonitor;
|
||||
|
||||
@Service
|
||||
@@ -143,19 +144,64 @@ public class PipelineDirectoryProcessor {
|
||||
|
||||
private File[] collectFilesForProcessing(Path dir, Path jsonFile, PipelineOperation operation)
|
||||
throws IOException {
|
||||
|
||||
List<String> inputExtensions =
|
||||
apiDocService.getExtensionTypes(false, operation.getOperation());
|
||||
log.info(
|
||||
"Allowed extensions for operation {}: {}",
|
||||
operation.getOperation(),
|
||||
inputExtensions);
|
||||
|
||||
boolean allowAllFiles = inputExtensions.contains("ALL");
|
||||
|
||||
try (Stream<Path> paths = Files.list(dir)) {
|
||||
if ("automated".equals(operation.getParameters().get("fileInput"))) {
|
||||
return paths.filter(
|
||||
path ->
|
||||
!Files.isDirectory(path)
|
||||
&& !path.equals(jsonFile)
|
||||
&& fileMonitor.isFileReadyForProcessing(path))
|
||||
.map(Path::toFile)
|
||||
.toArray(File[]::new);
|
||||
} else {
|
||||
String fileInput = (String) operation.getParameters().get("fileInput");
|
||||
return new File[] {new File(fileInput)};
|
||||
}
|
||||
File[] files =
|
||||
paths.filter(
|
||||
path -> {
|
||||
if (Files.isDirectory(path)) {
|
||||
return false;
|
||||
}
|
||||
if (path.equals(jsonFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get file extension
|
||||
String filename = path.getFileName().toString();
|
||||
String extension =
|
||||
filename.contains(".")
|
||||
? filename.substring(
|
||||
filename.lastIndexOf(".")
|
||||
+ 1)
|
||||
.toLowerCase()
|
||||
: "";
|
||||
|
||||
// Check against allowed extensions
|
||||
boolean isAllowed =
|
||||
allowAllFiles
|
||||
|| inputExtensions.contains(extension);
|
||||
if (!isAllowed) {
|
||||
log.info(
|
||||
"Skipping file with unsupported extension: {} ({})",
|
||||
filename,
|
||||
extension);
|
||||
}
|
||||
return isAllowed;
|
||||
})
|
||||
.filter(
|
||||
path -> {
|
||||
boolean isReady =
|
||||
fileMonitor.isFileReadyForProcessing(path);
|
||||
if (!isReady) {
|
||||
log.info(
|
||||
"File not ready for processing (locked/created last 5s): {}",
|
||||
path);
|
||||
}
|
||||
return isReady;
|
||||
})
|
||||
.map(Path::toFile)
|
||||
.toArray(File[]::new);
|
||||
log.info("Collected {} files for processing", files.length);
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,19 +244,37 @@ public class PipelineDirectoryProcessor {
|
||||
try {
|
||||
List<Resource> inputFiles =
|
||||
processor.generateInputFiles(filesToProcess.toArray(new File[0]));
|
||||
if (inputFiles == null || inputFiles.size() == 0) {
|
||||
if (inputFiles == null || inputFiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config);
|
||||
if (outputFiles == null) return;
|
||||
moveAndRenameFiles(outputFiles, config, dir);
|
||||
deleteOriginalFiles(filesToProcess, processingDir);
|
||||
PipelineResult result = processor.runPipelineAgainstFiles(inputFiles, config);
|
||||
|
||||
if (result.isHasErrors()) {
|
||||
log.error("Errors occurred during processing, retaining original files");
|
||||
moveToErrorDirectory(filesToProcess, dir);
|
||||
} else {
|
||||
moveAndRenameFiles(result.getOutputFiles(), config, dir);
|
||||
deleteOriginalFiles(filesToProcess, processingDir);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("error during processing", e);
|
||||
log.error("Error during processing", e);
|
||||
moveFilesBack(filesToProcess, processingDir);
|
||||
}
|
||||
}
|
||||
|
||||
private void moveToErrorDirectory(List<File> files, Path originalDir) throws IOException {
|
||||
Path errorDir = originalDir.resolve("error");
|
||||
if (!Files.exists(errorDir)) {
|
||||
Files.createDirectories(errorDir);
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
Path target = errorDir.resolve(file.getName());
|
||||
Files.move(file.toPath(), target);
|
||||
log.info("Moved failed file to error directory for investigation: {}", target);
|
||||
}
|
||||
}
|
||||
|
||||
private void moveAndRenameFiles(List<Resource> resources, PipelineConfig config, Path dir)
|
||||
throws IOException {
|
||||
for (Resource resource : resources) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.SPDFApplication;
|
||||
import stirling.software.SPDF.model.PipelineConfig;
|
||||
import stirling.software.SPDF.model.PipelineOperation;
|
||||
import stirling.software.SPDF.model.PipelineResult;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
|
||||
@Service
|
||||
@@ -84,8 +85,10 @@ public class PipelineProcessor {
|
||||
return "http://localhost:" + port + contextPath + "/";
|
||||
}
|
||||
|
||||
List<Resource> runPipelineAgainstFiles(List<Resource> outputFiles, PipelineConfig config)
|
||||
PipelineResult runPipelineAgainstFiles(List<Resource> outputFiles, PipelineConfig config)
|
||||
throws Exception {
|
||||
PipelineResult result = new PipelineResult();
|
||||
|
||||
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
||||
PrintStream logPrintStream = new PrintStream(logStream);
|
||||
boolean hasErrors = false;
|
||||
@@ -130,7 +133,8 @@ public class PipelineProcessor {
|
||||
if (operation.startsWith("filter-")
|
||||
&& (response.getBody() == null
|
||||
|| response.getBody().length == 0)) {
|
||||
log.info("Skipping file due to failing {}", operation);
|
||||
result.setFiltersApplied(true);
|
||||
log.info("Skipping file due to filtering {}", operation);
|
||||
continue;
|
||||
}
|
||||
if (!response.getStatusCode().equals(HttpStatus.OK)) {
|
||||
@@ -208,7 +212,10 @@ public class PipelineProcessor {
|
||||
if (hasErrors) {
|
||||
log.error("Errors occurred during processing. Log: {}", logStream.toString());
|
||||
}
|
||||
return outputFiles;
|
||||
result.setHasErrors(hasErrors);
|
||||
result.setFiltersApplied(hasErrors);
|
||||
result.setOutputFiles(outputFiles);
|
||||
return result;
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> sendWebRequest(String url, MultiValueMap<String, Object> body) {
|
||||
|
||||
@@ -40,8 +40,7 @@ public class RemoveCertSignController {
|
||||
@Operation(
|
||||
summary = "Remove digital signature from PDF",
|
||||
description =
|
||||
"This endpoint accepts a PDF file and returns the PDF file without the digital signature."
|
||||
+ " Input: PDF, Output: PDF")
|
||||
"This endpoint accepts a PDF file and returns the PDF file without the digital signature. Input:PDF, Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> removeCertSignPDF(@ModelAttribute PDFFile request)
|
||||
throws Exception {
|
||||
MultipartFile pdf = request.getFileInput();
|
||||
|
||||
@@ -44,6 +44,13 @@ public class ConverterWebController {
|
||||
return "convert/markdown-to-pdf";
|
||||
}
|
||||
|
||||
@GetMapping("/pdf-to-markdown")
|
||||
@Hidden
|
||||
public String convertPdfToMarkdownForm(Model model) {
|
||||
model.addAttribute("currentPage", "pdf-to-markdown");
|
||||
return "convert/pdf-to-markdown";
|
||||
}
|
||||
|
||||
@GetMapping("/url-to-pdf")
|
||||
@Hidden
|
||||
public String convertURLToPdfForm(Model model) {
|
||||
|
||||
@@ -36,8 +36,9 @@ public class DatabaseWebController {
|
||||
}
|
||||
List<FileInfo> backupList = databaseService.getBackupList();
|
||||
model.addAttribute("backupFiles", backupList);
|
||||
model.addAttribute("databaseVersion", databaseService.getH2Version());
|
||||
if ("Unknown".equalsIgnoreCase(databaseService.getH2Version())) {
|
||||
String dbVersion = databaseService.getH2Version();
|
||||
model.addAttribute("databaseVersion", dbVersion);
|
||||
if ("Unknown".equalsIgnoreCase(dbVersion)) {
|
||||
model.addAttribute("infoMessage", "notSupported");
|
||||
}
|
||||
return "database";
|
||||
|
||||
@@ -55,7 +55,10 @@ public class GeneralWebController {
|
||||
List<String> pipelineConfigs = new ArrayList<>();
|
||||
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
||||
if (new File(InstallationPathConfig.getPipelineDefaultWebUIConfigsDir()).exists()) {
|
||||
try (Stream<Path> paths = Files.walk(Paths.get(InstallationPathConfig.getPipelineDefaultWebUIConfigsDir()))) {
|
||||
try (Stream<Path> paths =
|
||||
Files.walk(
|
||||
Paths.get(
|
||||
InstallationPathConfig.getPipelineDefaultWebUIConfigsDir()))) {
|
||||
List<Path> jsonFiles =
|
||||
paths.filter(Files::isRegularFile)
|
||||
.filter(p -> p.toString().endsWith(".json"))
|
||||
|
||||
@@ -74,6 +74,12 @@ public class HomeWebController {
|
||||
return "redirect:/";
|
||||
}
|
||||
|
||||
@GetMapping("/home-legacy")
|
||||
public String homeLegacy(Model model) {
|
||||
model.addAttribute("currentPage", "home-legacy");
|
||||
return "home-legacy";
|
||||
}
|
||||
|
||||
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@ResponseBody
|
||||
@Hidden
|
||||
|
||||
@@ -265,7 +265,8 @@ public class ApplicationProperties {
|
||||
return getKeycloak();
|
||||
default:
|
||||
throw new UnsupportedProviderException(
|
||||
"Logout from the provider is not supported? Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
|
||||
"Logout from the provider is not supported? Report it at"
|
||||
+ " https://github.com/Stirling-Tools/Stirling-PDF/issues");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,6 +284,7 @@ public class ApplicationProperties {
|
||||
private Boolean enableAlphaFunctionality;
|
||||
private String enableAnalytics;
|
||||
private Datasource datasource;
|
||||
private Boolean disableSanitize;
|
||||
}
|
||||
|
||||
@Data
|
||||
@@ -312,10 +314,10 @@ public class ApplicationProperties {
|
||||
@Override
|
||||
public String toString() {
|
||||
return """
|
||||
Driver {
|
||||
driverName='%s'
|
||||
}
|
||||
"""
|
||||
Driver {
|
||||
driverName='%s'
|
||||
}
|
||||
"""
|
||||
.formatted(driverName);
|
||||
}
|
||||
}
|
||||
@@ -325,6 +327,7 @@ public class ApplicationProperties {
|
||||
private String appName;
|
||||
private String homeDescription;
|
||||
private String appNameNavbar;
|
||||
private List<String> languages;
|
||||
|
||||
public String getAppName() {
|
||||
return appName != null && appName.trim().length() > 0 ? appName : null;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package stirling.software.SPDF.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PipelineResult {
|
||||
private List<Resource> outputFiles;
|
||||
private boolean hasErrors;
|
||||
private boolean filtersApplied;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package stirling.software.SPDF.model;
|
||||
|
||||
public enum SortTypes {
|
||||
CUSTOM,
|
||||
REVERSE_ORDER,
|
||||
DUPLEX_SORT,
|
||||
BOOKLET_SORT,
|
||||
@@ -10,4 +11,5 @@ public enum SortTypes {
|
||||
REMOVE_FIRST,
|
||||
REMOVE_LAST,
|
||||
REMOVE_FIRST_AND_LAST,
|
||||
DUPLICATE
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema.RequiredMode;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -25,7 +26,9 @@ public class PDFWithPageNums extends PDFFile {
|
||||
description =
|
||||
"The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the"
|
||||
+ " format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a"
|
||||
+ " constant (e.g., '2n+1', '3n', '6n-5')\"")
|
||||
+ " constant (e.g., '2n+1', '3n', '6n-5')\"",
|
||||
defaultValue = "all",
|
||||
requiredMode = RequiredMode.NOT_REQUIRED)
|
||||
private String pageNumbers;
|
||||
|
||||
@Hidden
|
||||
@@ -41,9 +44,9 @@ public class PDFWithPageNums extends PDFFile {
|
||||
}
|
||||
|
||||
@Hidden
|
||||
public List<Integer> getPageNumbersList(PDDocument doc, boolean zeroCount) {
|
||||
public List<Integer> getPageNumbersList(PDDocument doc, boolean oneBased) {
|
||||
int pageCount = 0;
|
||||
pageCount = doc.getNumberOfPages();
|
||||
return GeneralUtils.parsePageList(pageNumbers, pageCount, zeroCount);
|
||||
return GeneralUtils.parsePageList(pageNumbers, pageCount, oneBased);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package stirling.software.SPDF.model.api.converters;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.utils.PDFToFile;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
@RequestMapping("/api/v1/convert")
|
||||
public class ConvertPDFToMarkdown {
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/markdown")
|
||||
@Operation(
|
||||
summary = "Convert PDF to Markdown",
|
||||
description =
|
||||
"This endpoint converts a PDF file to Markdown format. Input:PDF Output:Markdown Type:SISO")
|
||||
public ResponseEntity<byte[]> processPdfToMarkdown(@ModelAttribute PDFFile request)
|
||||
throws Exception {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
PDFToFile pdfToFile = new PDFToFile();
|
||||
return pdfToFile.processPdfToMarkdown(inputFile);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package stirling.software.SPDF.model.api.extract;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PDFFilePage extends PDFFile {
|
||||
|
||||
@Schema(description = "Number of chosen page", type = "number")
|
||||
private int pageId;
|
||||
}
|
||||
@@ -15,6 +15,8 @@ public class RearrangePagesRequest extends PDFWithPageNums {
|
||||
implementation = SortTypes.class,
|
||||
description =
|
||||
"The custom mode for page rearrangement. Valid values are:\n"
|
||||
+ "CUSTOM: Uses order defined in PageNums "
|
||||
+ "DUPLICATE: Duplicate pages n times (if Page order defined as 4, then duplicates each page 4 times)"
|
||||
+ "REVERSE_ORDER: Reverses the order of all pages.\n"
|
||||
+ "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). "
|
||||
+ "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package stirling.software.SPDF.model.api.misc;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema.RequiredMode;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -11,24 +12,49 @@ import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||
public class AddPageNumbersRequest extends PDFWithPageNums {
|
||||
|
||||
@Schema(
|
||||
description = "Custom margin: small/medium/large",
|
||||
allowableValues = {"small", "medium", "large"})
|
||||
description = "Custom margin: small/medium/large/x-large",
|
||||
allowableValues = {"small", "medium", "large", "x-large"},
|
||||
defaultValue = "medium",
|
||||
requiredMode = RequiredMode.NOT_REQUIRED)
|
||||
private String customMargin;
|
||||
|
||||
@Schema(
|
||||
description = "Font size for page numbers",
|
||||
minimum = "1",
|
||||
requiredMode = RequiredMode.REQUIRED)
|
||||
private float fontSize;
|
||||
|
||||
@Schema(
|
||||
description = "Font type for page numbers",
|
||||
allowableValues = {"helvetica", "courier", "times"},
|
||||
requiredMode = RequiredMode.REQUIRED)
|
||||
private String fontType;
|
||||
|
||||
@Schema(description = "Position: 1 of 9 positions", minimum = "1", maximum = "9")
|
||||
@Schema(
|
||||
description =
|
||||
"Position: 1-9 representing positions on the page (1=top-left, 5=center, 9=bottom-right)",
|
||||
minimum = "1",
|
||||
maximum = "9",
|
||||
requiredMode = RequiredMode.REQUIRED)
|
||||
private int position;
|
||||
|
||||
@Schema(description = "Starting number", minimum = "1")
|
||||
@Schema(
|
||||
description = "Starting number for page numbering",
|
||||
minimum = "1",
|
||||
requiredMode = RequiredMode.REQUIRED)
|
||||
private int startingNumber;
|
||||
|
||||
@Schema(description = "Which pages to number, default all")
|
||||
@Schema(
|
||||
description = "Which pages to number (e.g. '1,3-5,7' or 'all')",
|
||||
defaultValue = "all",
|
||||
requiredMode = RequiredMode.NOT_REQUIRED)
|
||||
private String pagesToNumber;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"Custom text: defaults to just number but can have things like \"Page {n} of {p}\"")
|
||||
"Custom text pattern. Available variables: {n}=current page number, {total}=total pages, {filename}=original filename",
|
||||
example = "Page {n} of {total}",
|
||||
defaultValue = "{n}",
|
||||
requiredMode = RequiredMode.NOT_REQUIRED)
|
||||
private String customText;
|
||||
}
|
||||
|
||||
@@ -1,41 +1,56 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class LanguageService {
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
private final PathMatchingResourcePatternResolver resourcePatternResolver =
|
||||
new PathMatchingResourcePatternResolver();
|
||||
|
||||
public List<String> getSupportedLanguages() {
|
||||
List<String> supportedLanguages = new ArrayList<>();
|
||||
public LanguageService(ApplicationProperties applicationProperties) {
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
public Set<String> getSupportedLanguages() {
|
||||
try {
|
||||
Resource[] resources =
|
||||
resourcePatternResolver.getResources("classpath*:messages_*.properties");
|
||||
for (Resource resource : resources) {
|
||||
if (resource.exists() && resource.isReadable()) {
|
||||
String filename = resource.getFilename();
|
||||
if (filename != null
|
||||
&& filename.startsWith("messages_")
|
||||
&& filename.endsWith(".properties")) {
|
||||
String languageCode =
|
||||
filename.replace("messages_", "").replace(".properties", "");
|
||||
supportedLanguages.add(languageCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return supportedLanguages;
|
||||
return Arrays.stream(resources)
|
||||
.map(Resource::getFilename)
|
||||
.filter(
|
||||
filename ->
|
||||
filename != null
|
||||
&& filename.startsWith("messages_")
|
||||
&& filename.endsWith(".properties"))
|
||||
.map(filename -> filename.replace("messages_", "").replace(".properties", ""))
|
||||
.filter(
|
||||
languageCode -> {
|
||||
Set<String> allowedLanguages =
|
||||
new HashSet<>(applicationProperties.getUi().getLanguages());
|
||||
return allowedLanguages.isEmpty()
|
||||
|| allowedLanguages.contains(languageCode)
|
||||
|| "en_GB".equals(languageCode);
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("Error retrieving supported languages", e);
|
||||
return new HashSet<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class MetricsAggregatorService {
|
||||
if (method == null || uri == null) {
|
||||
return;
|
||||
}
|
||||
if (!method.equals("GET") && !method.equals("POST")) {
|
||||
if (!"GET".equals(method) && !"POST".equals(method)) {
|
||||
return;
|
||||
}
|
||||
// Skip URIs that are 2 characters or shorter
|
||||
|
||||
@@ -26,7 +26,8 @@ public class FileToPdf {
|
||||
HTMLToPdfRequest request,
|
||||
byte[] fileBytes,
|
||||
String fileName,
|
||||
boolean htmlFormatsInstalled)
|
||||
boolean htmlFormatsInstalled,
|
||||
boolean disableSanitize)
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
@@ -36,19 +37,20 @@ public class FileToPdf {
|
||||
if (fileName.endsWith(".html")) {
|
||||
tempInputFile = Files.createTempFile("input_", ".html");
|
||||
String sanitizedHtml =
|
||||
sanitizeHtmlContent(new String(fileBytes, StandardCharsets.UTF_8));
|
||||
sanitizeHtmlContent(
|
||||
new String(fileBytes, StandardCharsets.UTF_8), disableSanitize);
|
||||
Files.write(tempInputFile, sanitizedHtml.getBytes(StandardCharsets.UTF_8));
|
||||
} else if (fileName.endsWith(".zip")) {
|
||||
tempInputFile = Files.createTempFile("input_", ".zip");
|
||||
Files.write(tempInputFile, fileBytes);
|
||||
sanitizeHtmlFilesInZip(tempInputFile);
|
||||
sanitizeHtmlFilesInZip(tempInputFile, disableSanitize);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported file format: " + fileName);
|
||||
}
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
if (!htmlFormatsInstalled) {
|
||||
command.add("weasyprint");
|
||||
command.add("/opt/venv/bin/weasyprint");
|
||||
command.add("-e");
|
||||
command.add("utf-8");
|
||||
command.add("-v");
|
||||
@@ -89,11 +91,12 @@ public class FileToPdf {
|
||||
return pdfBytes;
|
||||
}
|
||||
|
||||
private static String sanitizeHtmlContent(String htmlContent) {
|
||||
return CustomHtmlSanitizer.sanitize(htmlContent);
|
||||
private static String sanitizeHtmlContent(String htmlContent, boolean disableSanitize) {
|
||||
return (!disableSanitize) ? CustomHtmlSanitizer.sanitize(htmlContent) : htmlContent;
|
||||
}
|
||||
|
||||
private static void sanitizeHtmlFilesInZip(Path zipFilePath) throws IOException {
|
||||
private static void sanitizeHtmlFilesInZip(Path zipFilePath, boolean disableSanitize)
|
||||
throws IOException {
|
||||
Path tempUnzippedDir = Files.createTempDirectory("unzipped_");
|
||||
try (ZipInputStream zipIn =
|
||||
ZipSecurity.createHardenedInputStream(
|
||||
@@ -106,7 +109,7 @@ public class FileToPdf {
|
||||
if (entry.getName().toLowerCase().endsWith(".html")
|
||||
|| entry.getName().toLowerCase().endsWith(".htm")) {
|
||||
String content = new String(zipIn.readAllBytes(), StandardCharsets.UTF_8);
|
||||
String sanitizedContent = sanitizeHtmlContent(content);
|
||||
String sanitizedContent = sanitizeHtmlContent(content, disableSanitize);
|
||||
Files.write(filePath, sanitizedContent.getBytes(StandardCharsets.UTF_8));
|
||||
} else {
|
||||
Files.copy(zipIn, filePath);
|
||||
|
||||
@@ -9,15 +9,17 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.simpleyaml.configuration.file.YamlFile;
|
||||
import org.simpleyaml.configuration.file.YamlFileWrapper;
|
||||
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
||||
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.fathzer.soft.javaluator.DoubleEvaluator;
|
||||
@@ -208,8 +210,7 @@ public class GeneralUtils {
|
||||
result.addAll(handlePart(page, totalPages, offset));
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(
|
||||
new java.util.LinkedHashSet<>(result)); // Remove duplicates and maintain order
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Integer> evaluateNFunc(String expression, int maxValue) {
|
||||
@@ -285,7 +286,10 @@ public class GeneralUtils {
|
||||
String[] rangeParts = part.split("-");
|
||||
try {
|
||||
int start = Integer.parseInt(rangeParts[0]);
|
||||
int end = Integer.parseInt(rangeParts[1]);
|
||||
int end =
|
||||
(rangeParts.length > 1 && !rangeParts[1].isEmpty())
|
||||
? Integer.parseInt(rangeParts[1])
|
||||
: totalPages;
|
||||
for (int i = start; i <= end; i++) {
|
||||
if (i >= 1 && i <= totalPages) {
|
||||
partResult.add(i - 1 + offset);
|
||||
@@ -343,41 +347,208 @@ public class GeneralUtils {
|
||||
|
||||
public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
|
||||
throws IOException {
|
||||
Path path =
|
||||
Paths.get(
|
||||
InstallationPathConfig
|
||||
.getSettingsPath()); // Target the 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();
|
||||
doSaveKeyToConfig(id, (key == null ? "" : key), autoGenerated);
|
||||
}
|
||||
|
||||
public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated)
|
||||
throws IOException {
|
||||
Path path = Paths.get(InstallationPathConfig.getSettingsPath());
|
||||
doSaveKeyToConfig(id, String.valueOf(key), autoGenerated);
|
||||
}
|
||||
|
||||
final YamlFile settingsYml = new YamlFile(path.toFile());
|
||||
DumperOptions yamlOptionssettingsYml =
|
||||
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
|
||||
yamlOptionssettingsYml.setSplitLines(false);
|
||||
/*------------------------------------------------------------------------*
|
||||
* Internal Implementation Details *
|
||||
*------------------------------------------------------------------------*/
|
||||
|
||||
settingsYml.loadWithComments();
|
||||
|
||||
YamlFileWrapper writer = settingsYml.path(id).set(key);
|
||||
if (autoGenerated) {
|
||||
writer.comment("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||
/**
|
||||
* Actually performs the line-based update for the given path (e.g. "security.csrfDisabled") to
|
||||
* a new string value (e.g. "true"), possibly marking it as auto-generated.
|
||||
*/
|
||||
private static void doSaveKeyToConfig(String fullPath, String newValue, boolean autoGenerated)
|
||||
throws IOException {
|
||||
// 1) Load the file (settings.yml)
|
||||
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
|
||||
if (!Files.exists(settingsPath)) {
|
||||
log.warn("Settings file not found at {}, creating a new empty file...", settingsPath);
|
||||
Files.createDirectories(settingsPath.getParent());
|
||||
Files.createFile(settingsPath);
|
||||
}
|
||||
settingsYml.save();
|
||||
List<String> lines = Files.readAllLines(settingsPath);
|
||||
|
||||
// 2) Build a map of "nestedKeyPath -> lineIndex" by parsing indentation
|
||||
// Also track each line's indentation so we can preserve it when rewriting.
|
||||
Map<String, LineInfo> pathToLine = parseNestedYamlKeys(lines);
|
||||
|
||||
// 3) If the path is found, rewrite its line. Else, append at the bottom (no indentation).
|
||||
boolean changed = false;
|
||||
if (pathToLine.containsKey(fullPath)) {
|
||||
// Rewrite existing line
|
||||
LineInfo info = pathToLine.get(fullPath);
|
||||
String oldLine = lines.get(info.lineIndex);
|
||||
String newLine =
|
||||
rewriteLine(oldLine, info.indentSpaces, fullPath, newValue, autoGenerated);
|
||||
if (!newLine.equals(oldLine)) {
|
||||
lines.set(info.lineIndex, newLine);
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
// Append a new line at the bottom, with zero indentation
|
||||
String appended = fullPath + ": " + newValue;
|
||||
if (autoGenerated) {
|
||||
appended += " # Automatically Generated Settings (Do Not Edit Directly)";
|
||||
}
|
||||
lines.add(appended);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// 4) If changed, write back to file
|
||||
if (changed) {
|
||||
Files.write(settingsPath, lines);
|
||||
log.info(
|
||||
"Updated '{}' to '{}' (autoGenerated={}) in {}",
|
||||
fullPath,
|
||||
newValue,
|
||||
autoGenerated,
|
||||
settingsPath);
|
||||
} else {
|
||||
log.info("No changes for '{}' (already set to '{}').", fullPath, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/** A small record-like class that holds: - lineIndex - indentSpaces */
|
||||
private static class LineInfo {
|
||||
int lineIndex;
|
||||
int indentSpaces;
|
||||
|
||||
public LineInfo(int lineIndex, int indentSpaces) {
|
||||
this.lineIndex = lineIndex;
|
||||
this.indentSpaces = indentSpaces;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the YAML lines to build a map: "full.nested.key" -> (lineIndex, indentSpaces). We do a
|
||||
* naive indentation-based path stacking: - 2 spaces = 1 indent level - lines that start with
|
||||
* fewer or equal indentation pop the stack - lines that look like "key:" or "key: value" cause
|
||||
* a push
|
||||
*/
|
||||
private static Map<String, LineInfo> parseNestedYamlKeys(List<String> lines) {
|
||||
Map<String, LineInfo> result = new HashMap<>();
|
||||
|
||||
// We'll maintain a stack of (keyName, indentLevel).
|
||||
// Each line that looks like "myKey:" or "myKey: value" is a new "child" of the top of the
|
||||
// stack if indent is deeper.
|
||||
Deque<String> pathStack = new ArrayDeque<>();
|
||||
Deque<Integer> indentStack = new ArrayDeque<>();
|
||||
indentStack.push(-1); // sentinel
|
||||
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
String line = lines.get(i);
|
||||
String trimmed = line.trim();
|
||||
|
||||
// skip blank lines, comment lines, or list items
|
||||
if (trimmed.isEmpty() || trimmed.startsWith("#") || trimmed.startsWith("-")) {
|
||||
continue;
|
||||
}
|
||||
// check if there's a colon
|
||||
int colonIdx = trimmed.indexOf(':');
|
||||
if (colonIdx <= 0) { // must have at least one char before ':'
|
||||
continue;
|
||||
}
|
||||
// parse out key
|
||||
String keyPart = trimmed.substring(0, colonIdx).trim();
|
||||
if (keyPart.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// count leading spaces for indentation
|
||||
int leadingSpaces = countLeadingSpaces(line);
|
||||
int indentLevel = leadingSpaces / 2; // assume 2 spaces per level
|
||||
|
||||
// pop from stack until we get to a shallower indentation
|
||||
while (indentStack.peek() != null && indentStack.peek() >= indentLevel) {
|
||||
indentStack.pop();
|
||||
pathStack.pop();
|
||||
}
|
||||
|
||||
// push the new key
|
||||
pathStack.push(keyPart);
|
||||
indentStack.push(indentLevel);
|
||||
|
||||
// build the full path
|
||||
String[] arr = pathStack.toArray(new String[0]);
|
||||
List<String> reversed = Arrays.asList(arr);
|
||||
Collections.reverse(reversed);
|
||||
String fullPath = String.join(".", reversed);
|
||||
|
||||
// store line info
|
||||
result.put(fullPath, new LineInfo(i, leadingSpaces));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite a single line to set a new value, preserving indentation and (optionally) the
|
||||
* existing or auto-generated inline comment.
|
||||
*
|
||||
* <p>For example, oldLine might be: " csrfDisabled: false # set to 'true' to disable CSRF
|
||||
* protection" newValue = "true" autoGenerated = false
|
||||
*
|
||||
* <p>We'll produce something like: " csrfDisabled: true # set to 'true' to disable CSRF
|
||||
* protection"
|
||||
*/
|
||||
private static String rewriteLine(
|
||||
String oldLine, int indentSpaces, String path, String newValue, boolean autoGenerated) {
|
||||
// We'll keep the exact leading indentation (indentSpaces).
|
||||
// Then "key: newValue". We'll try to preserve any existing inline comment unless
|
||||
// autoGenerated is true.
|
||||
|
||||
// 1) Extract leading spaces from the old line (just in case they differ from indentSpaces).
|
||||
int actualLeadingSpaces = countLeadingSpaces(oldLine);
|
||||
String leading = oldLine.substring(0, actualLeadingSpaces);
|
||||
|
||||
// 2) Remove leading spaces from the rest
|
||||
String trimmed = oldLine.substring(actualLeadingSpaces);
|
||||
|
||||
// 3) Check for existing comment
|
||||
int hashIndex = trimmed.indexOf('#');
|
||||
String lineWithoutComment =
|
||||
(hashIndex >= 0) ? trimmed.substring(0, hashIndex).trim() : trimmed.trim();
|
||||
String oldComment = (hashIndex >= 0) ? trimmed.substring(hashIndex).trim() : "";
|
||||
|
||||
// 4) Rebuild "key: newValue"
|
||||
// The "key" here is everything before ':' in lineWithoutComment
|
||||
int colonIdx = lineWithoutComment.indexOf(':');
|
||||
String existingKey =
|
||||
(colonIdx >= 0)
|
||||
? lineWithoutComment.substring(0, colonIdx).trim()
|
||||
: path; // fallback if line is malformed
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(leading); // restore original leading spaces
|
||||
|
||||
// "key: newValue"
|
||||
sb.append(existingKey).append(": ").append(newValue);
|
||||
|
||||
// 5) If autoGenerated, add/replace comment
|
||||
if (autoGenerated) {
|
||||
sb.append(" # Automatically Generated Settings (Do Not Edit Directly)");
|
||||
} else {
|
||||
// preserve the old comment if it exists
|
||||
if (!oldComment.isEmpty()) {
|
||||
sb.append(" ").append(oldComment);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static int countLeadingSpaces(String line) {
|
||||
int count = 0;
|
||||
for (char c : line.toCharArray()) {
|
||||
if (c == ' ') count++;
|
||||
else break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static String generateMachineFingerprint() {
|
||||
@@ -415,9 +586,7 @@ public class GeneralUtils {
|
||||
for (byte b : hash) {
|
||||
fingerprint.append(String.format("%02x", b));
|
||||
}
|
||||
|
||||
return fingerprint.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
return "GenericID";
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
|
||||
import com.vladsch.flexmark.util.data.MutableDataSet;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -28,6 +31,123 @@ import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
@Slf4j
|
||||
public class PDFToFile {
|
||||
|
||||
public ResponseEntity<byte[]> processPdfToMarkdown(MultipartFile inputFile)
|
||||
throws IOException, InterruptedException {
|
||||
if (!"application/pdf".equals(inputFile.getContentType())) {
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
MutableDataSet options =
|
||||
new MutableDataSet()
|
||||
.set(
|
||||
FlexmarkHtmlConverter.MAX_BLANK_LINES,
|
||||
2) // Control max consecutive blank lines
|
||||
.set(
|
||||
FlexmarkHtmlConverter.MAX_TRAILING_BLANK_LINES,
|
||||
1) // Control trailing blank lines
|
||||
.set(
|
||||
FlexmarkHtmlConverter.SETEXT_HEADINGS,
|
||||
true) // Use Setext headings for h1 and h2
|
||||
.set(
|
||||
FlexmarkHtmlConverter.OUTPUT_UNKNOWN_TAGS,
|
||||
false) // Don't output HTML for unknown tags
|
||||
.set(
|
||||
FlexmarkHtmlConverter.TYPOGRAPHIC_QUOTES,
|
||||
true) // Convert quotation marks
|
||||
.set(
|
||||
FlexmarkHtmlConverter.BR_AS_PARA_BREAKS,
|
||||
true) // Convert <br> to paragraph breaks
|
||||
.set(FlexmarkHtmlConverter.CODE_INDENT, " "); // Indent for code blocks
|
||||
|
||||
FlexmarkHtmlConverter htmlToMarkdownConverter =
|
||||
FlexmarkHtmlConverter.builder(options).build();
|
||||
|
||||
String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||
String pdfBaseName = originalPdfFileName;
|
||||
if (originalPdfFileName.contains(".")) {
|
||||
pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
Path tempInputFile = null;
|
||||
Path tempOutputDir = null;
|
||||
byte[] fileBytes;
|
||||
String fileName = "temp.file";
|
||||
|
||||
try {
|
||||
tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
inputFile.transferTo(tempInputFile);
|
||||
tempOutputDir = Files.createTempDirectory("output_");
|
||||
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
"pdftohtml",
|
||||
"-s",
|
||||
"-noframes",
|
||||
"-c",
|
||||
tempInputFile.toString(),
|
||||
pdfBaseName));
|
||||
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)
|
||||
.runCommandWithOutputHandling(command, tempOutputDir.toFile());
|
||||
// Process HTML files to Markdown
|
||||
File[] outputFiles = Objects.requireNonNull(tempOutputDir.toFile().listFiles());
|
||||
List<File> markdownFiles = new ArrayList<>();
|
||||
|
||||
// Convert HTML files to Markdown
|
||||
for (File outputFile : outputFiles) {
|
||||
if (outputFile.getName().endsWith(".html")) {
|
||||
String html = Files.readString(outputFile.toPath());
|
||||
String markdown = htmlToMarkdownConverter.convert(html);
|
||||
|
||||
String mdFileName = outputFile.getName().replace(".html", ".md");
|
||||
File mdFile = new File(tempOutputDir.toFile(), mdFileName);
|
||||
Files.writeString(mdFile.toPath(), markdown);
|
||||
markdownFiles.add(mdFile);
|
||||
}
|
||||
}
|
||||
|
||||
// If there's only one markdown file, return it directly
|
||||
if (markdownFiles.size() == 1) {
|
||||
fileName = pdfBaseName + ".md";
|
||||
fileBytes = Files.readAllBytes(markdownFiles.get(0).toPath());
|
||||
} else {
|
||||
// Multiple files - create a zip
|
||||
fileName = pdfBaseName + "ToMarkdown.zip";
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
|
||||
// Add markdown files
|
||||
for (File mdFile : markdownFiles) {
|
||||
ZipEntry mdEntry = new ZipEntry(mdFile.getName());
|
||||
zipOutputStream.putNextEntry(mdEntry);
|
||||
Files.copy(mdFile.toPath(), zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
// Add images and other assets
|
||||
for (File file : outputFiles) {
|
||||
if (!file.getName().endsWith(".html") && !file.getName().endsWith(".md")) {
|
||||
ZipEntry assetEntry = new ZipEntry(file.getName());
|
||||
zipOutputStream.putNextEntry(assetEntry);
|
||||
Files.copy(file.toPath(), zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBytes = byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
|
||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||
}
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
public ResponseEntity<byte[]> processPdfToHtml(MultipartFile inputFile)
|
||||
throws IOException, InterruptedException {
|
||||
if (!"application/pdf".equals(inputFile.getContentType())) {
|
||||
|
||||
@@ -218,6 +218,9 @@ public class ProcessExecutor {
|
||||
errorReaderThread.join();
|
||||
outputReaderThread.join();
|
||||
|
||||
boolean isQpdf =
|
||||
command != null && !command.isEmpty() && command.get(0).contains("qpdf");
|
||||
|
||||
if (outputLines.size() > 0) {
|
||||
String outputMessage = String.join("\n", outputLines);
|
||||
messages += outputMessage;
|
||||
@@ -233,20 +236,28 @@ public class ProcessExecutor {
|
||||
log.warn("Command error output:\n" + errorMessage);
|
||||
}
|
||||
if (exitCode != 0) {
|
||||
throw new IOException(
|
||||
"Command process failed with exit code "
|
||||
+ exitCode
|
||||
+ ". Error message: "
|
||||
+ errorMessage);
|
||||
if (isQpdf && exitCode == 3) {
|
||||
log.warn("qpdf succeeded with warnings: {}", messages);
|
||||
} else {
|
||||
throw new IOException(
|
||||
"Command process failed with exit code "
|
||||
+ exitCode
|
||||
+ ". Error message: "
|
||||
+ errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exitCode != 0) {
|
||||
throw new IOException(
|
||||
"Command process failed with exit code "
|
||||
+ exitCode
|
||||
+ "\nLogs: "
|
||||
+ messages);
|
||||
if (isQpdf && exitCode == 3) {
|
||||
log.warn("qpdf succeeded with warnings: {}", messages);
|
||||
} else {
|
||||
throw new IOException(
|
||||
"Command process failed with exit code "
|
||||
+ exitCode
|
||||
+ "\nLogs: "
|
||||
+ messages);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
semaphore.release();
|
||||
|
||||
67
src/main/java/stirling/software/SPDF/utils/UIScaling.java
Normal file
67
src/main/java/stirling/software/SPDF/utils/UIScaling.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class UIScaling {
|
||||
private static final double BASE_RESOLUTION_WIDTH = 1920.0;
|
||||
private static final double BASE_RESOLUTION_HEIGHT = 1080.0;
|
||||
|
||||
public static double getWidthScaleFactor() {
|
||||
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
return screenSize.getWidth() / BASE_RESOLUTION_WIDTH;
|
||||
}
|
||||
|
||||
public static double getHeightScaleFactor() {
|
||||
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
return screenSize.getHeight() / BASE_RESOLUTION_HEIGHT;
|
||||
}
|
||||
|
||||
public static int scaleWidth(int value) {
|
||||
return (int) Math.round(value * getWidthScaleFactor());
|
||||
}
|
||||
|
||||
public static int scaleHeight(int value) {
|
||||
return (int) Math.round(value * getHeightScaleFactor());
|
||||
}
|
||||
|
||||
public static Dimension scale(Dimension dim) {
|
||||
return new Dimension(scaleWidth(dim.width), scaleHeight(dim.height));
|
||||
}
|
||||
|
||||
public static Insets scale(Insets insets) {
|
||||
return new Insets(
|
||||
scaleHeight(insets.top),
|
||||
scaleWidth(insets.left),
|
||||
scaleHeight(insets.bottom),
|
||||
scaleWidth(insets.right));
|
||||
}
|
||||
|
||||
public static Font scaleFont(Font font) {
|
||||
// For fonts, we'll use the smaller scale factor to ensure readability
|
||||
double scaleFactor = Math.min(getWidthScaleFactor(), getHeightScaleFactor());
|
||||
return font.deriveFont((float) (font.getSize() * scaleFactor));
|
||||
}
|
||||
|
||||
// Utility method for aspect ratio aware icon scaling
|
||||
public static Image scaleIcon(Image icon, int targetWidth, int targetHeight) {
|
||||
if (icon == null) return null;
|
||||
|
||||
double widthScale = getWidthScaleFactor();
|
||||
double heightScale = getHeightScaleFactor();
|
||||
|
||||
int scaledWidth = (int) Math.round(targetWidth * widthScale);
|
||||
int scaledHeight = (int) Math.round(targetHeight * heightScale);
|
||||
|
||||
// Maintain aspect ratio for icons
|
||||
double aspectRatio = (double) icon.getWidth(null) / icon.getHeight(null);
|
||||
if (scaledWidth / scaledHeight > aspectRatio) {
|
||||
scaledWidth = (int) (scaledHeight * aspectRatio);
|
||||
} else {
|
||||
scaledHeight = (int) (scaledWidth / aspectRatio);
|
||||
}
|
||||
|
||||
return icon.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
|
||||
}
|
||||
}
|
||||
@@ -30,52 +30,69 @@ public class InvertFullColorStrategy extends ReplaceAndInvertColorStrategy {
|
||||
@Override
|
||||
public InputStreamResource replace() throws IOException {
|
||||
|
||||
// Create a temporary file, with the original filename from the multipart file
|
||||
File file = Files.createTempFile("temp", getFileInput().getOriginalFilename()).toFile();
|
||||
File file = null;
|
||||
try {
|
||||
// Create a temporary file, with the original filename from the multipart file
|
||||
file = Files.createTempFile("temp", getFileInput().getOriginalFilename()).toFile();
|
||||
|
||||
// Transfer the content of the multipart file to the file
|
||||
getFileInput().transferTo(file);
|
||||
// Transfer the content of the multipart file to the file
|
||||
getFileInput().transferTo(file);
|
||||
|
||||
// Load the uploaded PDF
|
||||
PDDocument document = Loader.loadPDF(file);
|
||||
// Load the uploaded PDF
|
||||
PDDocument document = Loader.loadPDF(file);
|
||||
|
||||
// Render each page and invert colors
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
for (int page = 0; page < document.getNumberOfPages(); page++) {
|
||||
BufferedImage image =
|
||||
pdfRenderer.renderImageWithDPI(page, 300); // Render page at 300 DPI
|
||||
// Render each page and invert colors
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
for (int page = 0; page < document.getNumberOfPages(); page++) {
|
||||
BufferedImage image =
|
||||
pdfRenderer.renderImageWithDPI(page, 300); // Render page at 300 DPI
|
||||
|
||||
// Invert the colors
|
||||
invertImageColors(image);
|
||||
// Invert the colors
|
||||
invertImageColors(image);
|
||||
|
||||
// Create a new PDPage from the inverted image
|
||||
PDPage pdPage = document.getPage(page);
|
||||
PDImageXObject pdImage =
|
||||
PDImageXObject.createFromFileByContent(
|
||||
convertToBufferedImageTpFile(image), document);
|
||||
// Create a new PDPage from the inverted image
|
||||
PDPage pdPage = document.getPage(page);
|
||||
File tempImageFile = null;
|
||||
try {
|
||||
tempImageFile = convertToBufferedImageTpFile(image);
|
||||
PDImageXObject pdImage =
|
||||
PDImageXObject.createFromFileByContent(tempImageFile, document);
|
||||
|
||||
PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document, pdPage, PDPageContentStream.AppendMode.OVERWRITE, true);
|
||||
contentStream.drawImage(
|
||||
pdImage,
|
||||
0,
|
||||
0,
|
||||
pdPage.getMediaBox().getWidth(),
|
||||
pdPage.getMediaBox().getHeight());
|
||||
contentStream.close();
|
||||
PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document,
|
||||
pdPage,
|
||||
PDPageContentStream.AppendMode.OVERWRITE,
|
||||
true);
|
||||
contentStream.drawImage(
|
||||
pdImage,
|
||||
0,
|
||||
0,
|
||||
pdPage.getMediaBox().getWidth(),
|
||||
pdPage.getMediaBox().getHeight());
|
||||
contentStream.close();
|
||||
} finally {
|
||||
if (tempImageFile != null && tempImageFile.exists()) {
|
||||
Files.delete(tempImageFile.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the modified PDF to a ByteArrayOutputStream
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
document.save(byteArrayOutputStream);
|
||||
document.close();
|
||||
|
||||
// Prepare the modified PDF for download
|
||||
ByteArrayInputStream inputStream =
|
||||
new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||
InputStreamResource resource = new InputStreamResource(inputStream);
|
||||
return resource;
|
||||
} finally {
|
||||
if (file != null && file.exists()) {
|
||||
Files.delete(file.toPath());
|
||||
}
|
||||
}
|
||||
|
||||
// Save the modified PDF to a ByteArrayOutputStream
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
document.save(byteArrayOutputStream);
|
||||
document.close();
|
||||
|
||||
// Prepare the modified PDF for download
|
||||
ByteArrayInputStream inputStream =
|
||||
new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||
InputStreamResource resource = new InputStreamResource(inputStream);
|
||||
return resource;
|
||||
}
|
||||
|
||||
// Method to invert image colors
|
||||
@@ -98,7 +115,7 @@ public class InvertFullColorStrategy extends ReplaceAndInvertColorStrategy {
|
||||
|
||||
// Helper method to convert BufferedImage to InputStream
|
||||
private File convertToBufferedImageTpFile(BufferedImage image) throws IOException {
|
||||
File file = new File("image.png");
|
||||
File file = File.createTempFile("image", ".png");
|
||||
ImageIO.write(image, "png", file);
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ analytics.settings=يمكنك تغيير إعدادات الإحصائيات ف
|
||||
# NAVBAR #
|
||||
#############
|
||||
navbar.favorite=المفضلة
|
||||
navbar.recent=New and recently updated
|
||||
navbar.darkmode=الوضع الداكن
|
||||
navbar.language=اللغات
|
||||
navbar.settings=إعدادات
|
||||
@@ -261,10 +262,19 @@ home.desc=متجرك الشامل المستضاف محليًا لجميع اح
|
||||
home.searchBar=البحث عن الميزات...
|
||||
|
||||
|
||||
home.viewPdf.title=عرض PDF
|
||||
home.viewPdf.title=View/Edit PDF
|
||||
home.viewPdf.desc=عرض وتعليق وإضافة نص أو صور
|
||||
viewPdf.tags=عرض,قراءة,تعليق,نص,صورة
|
||||
|
||||
home.setFavorites=Set Favourites
|
||||
home.hideFavorites=Hide Favourites
|
||||
home.showFavorites=Show Favourites
|
||||
home.legacyHomepage=Old homepage
|
||||
home.newHomePage=Try our new homepage!
|
||||
home.alphabetical=Alphabetical
|
||||
home.globalPopularity=Global Popularity
|
||||
home.sortBy=Sort by:
|
||||
|
||||
home.multiTool.title=أداة متعددة PDF
|
||||
home.multiTool.desc=دمج الصفحات وتدويرها وإعادة ترتيبها وإزالتها
|
||||
multiTool.tags=أداة متعددة,عملية متعددة,واجهة مستخدم,النقر والسحب,واجهة أمامية,جانب العميل
|
||||
@@ -452,6 +462,9 @@ home.MarkdownToPDF.title=Markdown إلى PDF
|
||||
home.MarkdownToPDF.desc=يحول أي ملف Markdown إلى PDF
|
||||
MarkdownToPDF.tags=لغة الترميز,محتوى الويب,تحويل,تحويل
|
||||
|
||||
home.PDFToMarkdown.title=PDF to Markdown
|
||||
home.PDFToMarkdown.desc=Converts any PDF to Markdown
|
||||
PDFToMarkdown.tags=markup,web-content,transformation,convert,md
|
||||
|
||||
home.getPdfInfo.title=الحصول على جميع المعلومات عن PDF
|
||||
home.getPdfInfo.desc=يجمع أي وكل المعلومات الممكنة عن ملفات PDF
|
||||
@@ -610,6 +623,7 @@ redact.showAttatchments=Show Attachments
|
||||
redact.showLayers=Show Layers (double-click to reset all layers to the default state)
|
||||
redact.colourPicker=Colour Picker
|
||||
redact.findCurrentOutlineItem=Find current outline item
|
||||
redact.applyChanges=Apply Changes
|
||||
|
||||
#showJS
|
||||
showJS.title=إظهار جافا سكريبت
|
||||
@@ -646,6 +660,11 @@ MarkdownToPDF.help=العمل قيد التقدم
|
||||
MarkdownToPDF.credit=يستخدم WeasyPrint
|
||||
|
||||
|
||||
#pdf-to-markdown
|
||||
PDFToMarkdown.title=PDF To Markdown
|
||||
PDFToMarkdown.header=PDF To Markdown
|
||||
PDFToMarkdown.submit=Convert
|
||||
|
||||
|
||||
#url-to-pdf
|
||||
URLToPDF.title=URL إلى PDF
|
||||
@@ -750,7 +769,6 @@ autoSplitPDF.selectText.3=ارفع ملف PDF الممسوح ضوئيًا الك
|
||||
autoSplitPDF.selectText.4=يتم اكتشاف صفحات الفاصل تلقائيًا وإزالتها، مما يضمن مستندًا نهائيًا نظيفًا.
|
||||
autoSplitPDF.formPrompt=أرسل ملف PDF يحتوي على فواصل صفحات Stirling-PDF:
|
||||
autoSplitPDF.duplexMode=وضع الطباعة على الوجهين (المسح الضوئي للوجه الأمامي والخلفي)
|
||||
autoSplitPDF.dividerDownload1=تنزيل 'فاصل التقسيم التلقائي (الحد الأدنى).pdf'
|
||||
autoSplitPDF.dividerDownload2=تنزيل 'فاصل التقسيم التلقائي (مع التعليمات).pdf'
|
||||
autoSplitPDF.submit=إرسال
|
||||
|
||||
@@ -974,6 +992,7 @@ pdfOrganiser.mode.7=إزالة الأول
|
||||
pdfOrganiser.mode.8=إزالة الأخير
|
||||
pdfOrganiser.mode.9=إزالة
|
||||
pdfOrganiser.mode.10=دمج فردي-زوجي
|
||||
pdfOrganiser.mode.11=Duplicate all pages
|
||||
pdfOrganiser.placeholder=(مثال: 1,3,2 أو 4-8,2,10-12 أو 2n-1)
|
||||
|
||||
|
||||
@@ -1016,7 +1035,7 @@ decrypt.success=File decrypted successfully.
|
||||
multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=عرض PDF
|
||||
viewPdf.title=View/Edit PDF
|
||||
viewPdf.header=عرض PDF
|
||||
|
||||
#pageRemover
|
||||
@@ -1167,7 +1186,7 @@ changeMetadata.submit=تغيير
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF إلى PDF/A
|
||||
pdfToPDFA.header=PDF إلى PDF/A
|
||||
pdfToPDFA.credit=تستخدم هذه الخدمة qpdf لتحويل PDF/A.
|
||||
pdfToPDFA.credit=تستخدم هذه الخدمة libreoffice لتحويل PDF/A.
|
||||
pdfToPDFA.submit=تحويل
|
||||
pdfToPDFA.tip=لا يعمل حاليًا لمدخلات متعددة في وقت واحد
|
||||
pdfToPDFA.outputFormat=تنسيق الإخراج
|
||||
|
||||
@@ -138,6 +138,7 @@ analytics.settings=Analitikanın parametrlərini config/settings.yml faylından
|
||||
# NAVBAR #
|
||||
#############
|
||||
navbar.favorite=Sevimlilər
|
||||
navbar.recent=New and recently updated
|
||||
navbar.darkmode=Qaranlıq Tema
|
||||
navbar.language=Dillər
|
||||
navbar.settings=Parametrlər
|
||||
@@ -261,10 +262,19 @@ home.desc=Bütün PDF ehtiyaclarınız üçün lokal-host edilən biraddımlıq
|
||||
home.searchBar=Xüsusiyyətləri axtar...
|
||||
|
||||
|
||||
home.viewPdf.title=PDF-ə bax
|
||||
home.viewPdf.title=View/Edit PDF
|
||||
home.viewPdf.desc=Bax, sitat götür, mətn və ya şəkil əlavə et
|
||||
viewPdf.tags=bax,oxu,sitat götür,mətn,şəkil
|
||||
|
||||
home.setFavorites=Set Favourites
|
||||
home.hideFavorites=Hide Favourites
|
||||
home.showFavorites=Show Favourites
|
||||
home.legacyHomepage=Old homepage
|
||||
home.newHomePage=Try our new homepage!
|
||||
home.alphabetical=Alphabetical
|
||||
home.globalPopularity=Global Popularity
|
||||
home.sortBy=Sort by:
|
||||
|
||||
home.multiTool.title=PDF Multi-alət
|
||||
home.multiTool.desc=Səhifələri Birləşdir, Çevir, Yenidən Sırala, Böl və Sil
|
||||
multiTool.tags=Multi-alət,Çoxlu əməliyyat,UI,tut-sürüşdür,front end,istifadəçi-tərəf,interaktiv,qarşılıqlı,yerini dəyiş,sil,köçür,böl
|
||||
@@ -452,6 +462,9 @@ home.MarkdownToPDF.title=Markdown-dan PDF-ə
|
||||
home.MarkdownToPDF.desc=Hər hansı Markdown faylını PDF-ə çevirir
|
||||
MarkdownToPDF.tags=işarələmə,web-məzmun,dəyişmə,çevirmə
|
||||
|
||||
home.PDFToMarkdown.title=PDF to Markdown
|
||||
home.PDFToMarkdown.desc=Converts any PDF to Markdown
|
||||
PDFToMarkdown.tags=markup,web-content,transformation,convert,md
|
||||
|
||||
home.getPdfInfo.title=PDF-in Bütün Məlumatları
|
||||
home.getPdfInfo.desc=PDF barədə mümkün olan bütün məlumatları əldə edir
|
||||
@@ -610,6 +623,7 @@ redact.showAttatchments=Show Attachments
|
||||
redact.showLayers=Show Layers (double-click to reset all layers to the default state)
|
||||
redact.colourPicker=Colour Picker
|
||||
redact.findCurrentOutlineItem=Find current outline item
|
||||
redact.applyChanges=Apply Changes
|
||||
|
||||
#showJS
|
||||
showJS.title=Javascripti Göstər
|
||||
@@ -646,6 +660,11 @@ MarkdownToPDF.help=İş davam edir
|
||||
MarkdownToPDF.credit=WeasyPrint İstifadə Edir
|
||||
|
||||
|
||||
#pdf-to-markdown
|
||||
PDFToMarkdown.title=PDF To Markdown
|
||||
PDFToMarkdown.header=PDF To Markdown
|
||||
PDFToMarkdown.submit=Convert
|
||||
|
||||
|
||||
#url-to-pdf
|
||||
URLToPDF.title=URL-i PDF-ə
|
||||
@@ -750,7 +769,6 @@ autoSplitPDF.selectText.3=Tək böyük skan edilmiş PDF faylını yükləyin v
|
||||
autoSplitPDF.selectText.4=Ayırıcı səhifələr avtomatik aşkarlanır və silinir, səliqəli yekun sənədə zəmanət verir.
|
||||
autoSplitPDF.formPrompt=Stirling-PDF ə Səhifə bölücüləri olan PDF-i təqdim edin:
|
||||
autoSplitPDF.duplexMode=Dupleks rejimi (Ön və arxa skanlama)
|
||||
autoSplitPDF.dividerDownload1='Auto Splitter Divider (minimal).pdf'-ı yükləyin
|
||||
autoSplitPDF.dividerDownload2='Auto Splitter Divider (with instructions).pdf'-ı yükləyin
|
||||
autoSplitPDF.submit=Təsdiq edin
|
||||
|
||||
@@ -974,6 +992,7 @@ pdfOrganiser.mode.7=Birincini Sil
|
||||
pdfOrganiser.mode.8=Sonuncunu Sil
|
||||
pdfOrganiser.mode.9=Birinci və Sonuncunu Sil
|
||||
pdfOrganiser.mode.10=Tək-Cüt Birləşdirmə
|
||||
pdfOrganiser.mode.11=Duplicate all pages
|
||||
pdfOrganiser.placeholder=(məs., 1,3,2 və ya 4-8,2,10-12 və ya 2n-1)
|
||||
|
||||
|
||||
@@ -1016,7 +1035,7 @@ decrypt.success=File decrypted successfully.
|
||||
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 pdf
|
||||
viewPdf.title=PDF-ə baxın
|
||||
viewPdf.title=View/Edit PDF
|
||||
viewPdf.header=PDF-ə baxın
|
||||
|
||||
#pageRemover
|
||||
@@ -1167,7 +1186,7 @@ changeMetadata.submit=Dəyiş
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF-i PDF/A-ya
|
||||
pdfToPDFA.header=PDF-i PDF/A-ya
|
||||
pdfToPDFA.credit=Bu Servis PDF/A Çevirmək Üçün ghostscript İşlədir
|
||||
pdfToPDFA.credit=Bu Servis PDF/A Çevirmək Üçün libreoffice İşlədir
|
||||
pdfToPDFA.submit=Çevir
|
||||
pdfToPDFA.tip=Hazırda Birdən Çox Giriş Üçün İşləmir
|
||||
pdfToPDFA.outputFormat=Çıxış Formatı
|
||||
|
||||
@@ -79,10 +79,10 @@ info=Информация
|
||||
pro=Pro
|
||||
page=Страница
|
||||
pages=Страници
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
apply=Apply
|
||||
loading=Зареждане на...
|
||||
addToDoc=Добавяне към документ
|
||||
reset=Нулиране
|
||||
apply=Приложи
|
||||
|
||||
legal.privacy=Политика за поверителност
|
||||
legal.terms=Правила и условия
|
||||
@@ -138,12 +138,13 @@ analytics.settings=Можете да промените настройките
|
||||
# NAVBAR #
|
||||
#############
|
||||
navbar.favorite=Любими
|
||||
navbar.recent=Нови и наскоро актуализирани
|
||||
navbar.darkmode=Тъмна тема
|
||||
navbar.language=Езици
|
||||
navbar.settings=Настройки
|
||||
navbar.allTools=Инструменти
|
||||
navbar.multiTool=Мулти инструменти
|
||||
navbar.search=Search
|
||||
navbar.search=Търсене
|
||||
navbar.sections.organize=Организирайте
|
||||
navbar.sections.convertTo=Преобразуване в PDF
|
||||
navbar.sections.convertFrom=Преобразуване от PDF
|
||||
@@ -239,20 +240,20 @@ database.creationDate=Дата на създаване
|
||||
database.fileSize=Размер на файла
|
||||
database.deleteBackupFile=Изтриване на архивен файл
|
||||
database.importBackupFile=Импортиране на архивен файл
|
||||
database.createBackupFile=Create Backup File
|
||||
database.createBackupFile=Създаване на файл с резервно копие
|
||||
database.downloadBackupFile=Изтеглете архивен файл
|
||||
database.info_1=Когато импортирате данни, е от решаващо значение да осигурите правилната структура. Ако не сте сигурни в това, което правите, потърсете съвет и подкрепа от професионалист. Грешка в структурата може да причини неизправност на приложението, включително пълна невъзможност за стартиране на приложението.
|
||||
database.info_2=Името на файла няма значение при качване. След това ще бъде преименуван, за да следва формата backup_user_yyyyMMddHHmm.sql, осигурявайки последователна конвенция за именуване.
|
||||
database.submit=Импортиране на резервно копие
|
||||
database.importIntoDatabaseSuccessed=Импортирането в базата данни бе успешно
|
||||
database.backupCreated=Database backup successful
|
||||
database.backupCreated=Успешно архивиране на базата данни
|
||||
database.fileNotFound=Файлът не е намерен
|
||||
database.fileNullOrEmpty=Файлът не трябва да е нулев или празен
|
||||
database.failedImportFile=Неуспешно импортиране на файл
|
||||
database.notSupported=This function is not available for your database connection.
|
||||
database.notSupported=Тази функция не е налична за вашата връзка с база данни.
|
||||
|
||||
session.expired=Вашата сесия е изтекла. Моля, опреснете страницата и опитайте отново.
|
||||
session.refreshPage=Refresh Page
|
||||
session.refreshPage=Презареждане на страницата
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
@@ -261,10 +262,19 @@ home.desc=Вашето локално хоствано обслужване на
|
||||
home.searchBar=Търсене на функции...
|
||||
|
||||
|
||||
home.viewPdf.title=Преглед на PDF
|
||||
home.viewPdf.title=Преглед/Редактиране PDF
|
||||
home.viewPdf.desc=Преглеждайте, коментирайте, добавяйте текст или изображения
|
||||
viewPdf.tags=преглед,четене,анотиране,текст,изображение
|
||||
|
||||
home.setFavorites=Задаване на предпочитани
|
||||
home.hideFavorites=Скриване на предпочитани
|
||||
home.showFavorites=Покажи предпочитани
|
||||
home.legacyHomepage=Стара начална страница
|
||||
home.newHomePage=Опитайте новата ни начална страница!
|
||||
home.alphabetical=По азбучен ред
|
||||
home.globalPopularity=Световна популярност
|
||||
home.sortBy=Сортиране по:
|
||||
|
||||
home.multiTool.title=PDF Мулти инструмент
|
||||
home.multiTool.desc=Обединяване, завъртане, пренареждане и премахване на страници
|
||||
multiTool.tags=Мултиинструмент,Мулти операции,UI,плъзгане с щракване,потребителска част,страна на клиента,интерактивен,неразрешим,преместване
|
||||
@@ -452,6 +462,9 @@ home.MarkdownToPDF.title=Markdown към PDF
|
||||
home.MarkdownToPDF.desc=Преобразува всеки Markdown файл към PDF
|
||||
MarkdownToPDF.tags=маркиране,уеб-съдържание,трансформация,преобразуване
|
||||
|
||||
home.PDFToMarkdown.title=PDF към Markdown
|
||||
home.PDFToMarkdown.desc=Преобразува всеки PDF файл в Markdown
|
||||
PDFToMarkdown.tags=маркиране, уеб-съдържание, трансформация, конвертиране, MD
|
||||
|
||||
home.getPdfInfo.title=Вземете ЦЯЛАТА информация от PDF
|
||||
home.getPdfInfo.desc=Взима всяка възможна информация от PDF файлове
|
||||
@@ -476,11 +489,11 @@ home.autoRedact.title=Автоматично редактиране
|
||||
home.autoRedact.desc=Автоматично редактира (зачернява) текст в PDF въз основа на въведен текст
|
||||
autoRedact.tags=Редактиране,Скриване,затъмняване,черен,маркер,скрит
|
||||
|
||||
home.redact.title=Manual Redaction
|
||||
home.redact.desc=Redacts a PDF based on selected text, drawn shapes and/or selected page(s)
|
||||
redact.tags=Redact,Hide,black out,black,marker,hidden,manual
|
||||
home.redact.title=Ръчно редактиране
|
||||
home.redact.desc=Редактиране на PDF файл въз основа на избран текст, нарисувани форми и/или избрана страница(и)
|
||||
redact.tags=Редактиране, скриване, затъмнение, черно, маркер, скрито, ръчно
|
||||
|
||||
home.tableExtraxt.title=PDF в CSV
|
||||
home.tableExtraxt.title=PDF към CSV
|
||||
home.tableExtraxt.desc=Извлича таблици от PDF, като ги конвертира в CSV
|
||||
tableExtraxt.tags=CSV,извличане на таблица,извличане,конвертиране
|
||||
|
||||
@@ -520,9 +533,9 @@ home.splitPdfByChapters.title=Разделете PDF по глави
|
||||
home.splitPdfByChapters.desc=Разделете PDF на множество файлове въз основа на неговата структура на глави.
|
||||
splitPdfByChapters.tags=разделяне, глави, отметки, организиране
|
||||
|
||||
home.validateSignature.title=Validate PDF Signature
|
||||
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||
home.validateSignature.title=Валидиране на PDF подпис
|
||||
home.validateSignature.desc=Проверка на цифрови подписи и сертификати в PDF документи
|
||||
validateSignature.tags=подпис,проверка,валидиране,pdf,сертификат,цифров подпис,валидиране на подпис,валидиране на сертификат
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Замени-инвертиране-на-цвят
|
||||
@@ -567,7 +580,7 @@ login.oauth2invalidRequest=Невалидна заявка
|
||||
login.oauth2AccessDenied=Отказан достъп
|
||||
login.oauth2InvalidTokenResponse=Невалиден отговор на токена
|
||||
login.oauth2InvalidIdToken=Невалиден токен за идентификатор
|
||||
login.relyingPartyRegistrationNotFound=No relying party registration found
|
||||
login.relyingPartyRegistrationNotFound=Не е открита регистрация на доверяваща се страна
|
||||
login.userIsDisabled=Потребителят е деактивиран, влизането в момента е блокирано с това потребителско име. Моля, свържете се с администратора.
|
||||
login.alreadyLoggedIn=Вече сте влезли в
|
||||
login.alreadyLoggedIn2=устройства. Моля, излезте от устройствата и опитайте отново.
|
||||
@@ -586,30 +599,31 @@ autoRedact.convertPDFToImageLabel=Преобразуване на PDF към PDF
|
||||
autoRedact.submitButton=Изпращане
|
||||
|
||||
#redact
|
||||
redact.title=Manual Redaction
|
||||
redact.header=Manual Redaction
|
||||
redact.submit=Redact
|
||||
redact.textBasedRedaction=Text based Redaction
|
||||
redact.pageBasedRedaction=Page-based Redaction
|
||||
redact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
redact.pageRedactionNumbers.title=Pages
|
||||
redact.pageRedactionNumbers.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||
redact.redactionColor.title=Redaction Color
|
||||
redact.export=Export
|
||||
redact.upload=Upload
|
||||
redact.boxRedaction=Box draw redaction
|
||||
redact.zoom=Zoom
|
||||
redact.zoomIn=Zoom in
|
||||
redact.zoomOut=Zoom out
|
||||
redact.nextPage=Next Page
|
||||
redact.previousPage=Previous Page
|
||||
redact.toggleSidebar=Toggle Sidebar
|
||||
redact.showThumbnails=Show Thumbnails
|
||||
redact.showDocumentOutline=Show Document Outline (double-click to expand/collapse all items)
|
||||
redact.showAttatchments=Show Attachments
|
||||
redact.showLayers=Show Layers (double-click to reset all layers to the default state)
|
||||
redact.colourPicker=Colour Picker
|
||||
redact.findCurrentOutlineItem=Find current outline item
|
||||
redact.title=Ръчно редактиране
|
||||
redact.header=Ръчно редактиране
|
||||
redact.submit=Редактиране
|
||||
redact.textBasedRedaction=Редактиране на базата на текст
|
||||
redact.pageBasedRedaction=Редактиране на базата на страници
|
||||
redact.convertPDFToImageLabel=Конвертиране на PDF в PDF-изображение (използва се за премахване на текста зад полето)
|
||||
redact.pageRedactionNumbers.title=Страници
|
||||
redact.pageRedactionNumbers.placeholder=(напр. 1,2,8 или 4,7,12-16 или 2n-1)
|
||||
redact.redactionColor.title=Цвят на редактиране
|
||||
redact.export=Експорт
|
||||
redact.upload=Качване
|
||||
redact.boxRedaction=Редактиране на рисунката в квадратчето
|
||||
redact.zoom=Увеличаване
|
||||
redact.zoomIn=Увеличаване на мащаба
|
||||
redact.zoomOut=Намаляване на мащаба
|
||||
redact.nextPage=Следваща страница
|
||||
redact.previousPage=Предишна страница
|
||||
redact.toggleSidebar=Превключване на страничната лента
|
||||
redact.showThumbnails=Показване на миниатюри
|
||||
redact.showDocumentOutline=Показване на контура на документа (кликнете два пъти, за да разгънете/свиете всички елементи)
|
||||
redact.showAttatchments=Показване на прикачени файлове
|
||||
redact.showLayers=Показване на слоеве (щракнете два пъти, за да върнете всички слоеве в състояние по подразбиране)
|
||||
redact.colourPicker=Избор на цвят
|
||||
redact.findCurrentOutlineItem=Намиране на текущия елемент от контура
|
||||
redact.applyChanges=Прилагане на промените
|
||||
|
||||
#showJS
|
||||
showJS.title=Покажи Javascript
|
||||
@@ -646,6 +660,11 @@ MarkdownToPDF.help=Работата е в ход
|
||||
MarkdownToPDF.credit=Използва WeasyPrint
|
||||
|
||||
|
||||
#pdf-to-markdown
|
||||
PDFToMarkdown.title=PDF към Markdown
|
||||
PDFToMarkdown.header=PDF към Markdown
|
||||
PDFToMarkdown.submit=Преобразуване
|
||||
|
||||
|
||||
#url-to-pdf
|
||||
URLToPDF.title=URL към PDF
|
||||
@@ -750,7 +769,6 @@ autoSplitPDF.selectText.3=Качете единствения голям ска
|
||||
autoSplitPDF.selectText.4=Разделителните страници се откриват и премахват автоматично, което гарантира чист краен документ.
|
||||
autoSplitPDF.formPrompt=Изпратете PDF, съдържащ разделители на страници на Stirling-PDF:
|
||||
autoSplitPDF.duplexMode=Дуплексен режим (сканиране отпред и отзад)
|
||||
autoSplitPDF.dividerDownload1=Изтеглете 'Автоматичен сплитер разделител (минимален).pdf'
|
||||
autoSplitPDF.dividerDownload2=Изтеглете 'Автоматичен сплитер разделител (с инструкции).pdf'
|
||||
autoSplitPDF.submit=Подайте
|
||||
|
||||
@@ -791,7 +809,7 @@ certSign.showSig=Показване на подпис
|
||||
certSign.reason=Причина
|
||||
certSign.location=Местоположение
|
||||
certSign.name=Име
|
||||
certSign.showLogo=Show Logo
|
||||
certSign.showLogo=Покажи лого
|
||||
certSign.submit=Подпишете PDF
|
||||
|
||||
|
||||
@@ -826,9 +844,9 @@ compare.highlightColor.2=Цвят на маркирането 2:
|
||||
compare.document.1=Документ 1
|
||||
compare.document.2=Документ 2
|
||||
compare.submit=Сравнявай
|
||||
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
|
||||
compare.large.file.message=One or Both of the provided documents are too large to process
|
||||
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
|
||||
compare.complex.message=Един или и двата предоставени документа са големи файлове, точността на сравнението може да бъде намалена.
|
||||
compare.large.file.message=Един или и двата предоставени документа са твърде големи за обработка
|
||||
compare.no.text.message=Един или и двата избрани PDF файла нямат текстово съдържание. Моля, изберете PDF файлове с текст за сравнение.
|
||||
|
||||
#BookToPDF
|
||||
BookToPDF.title=Книги и комикси в PDF
|
||||
@@ -851,18 +869,18 @@ sign.draw=Начертайте подпис
|
||||
sign.text=Въвеждане на текст
|
||||
sign.clear=Изчисти
|
||||
sign.add=Добави
|
||||
sign.saved=Saved Signatures
|
||||
sign.save=Save Signature
|
||||
sign.personalSigs=Personal Signatures
|
||||
sign.sharedSigs=Shared Signatures
|
||||
sign.noSavedSigs=No saved signatures found
|
||||
sign.addToAll=Add to all pages
|
||||
sign.delete=Delete
|
||||
sign.first=First page
|
||||
sign.last=Last page
|
||||
sign.next=Next page
|
||||
sign.previous=Previous page
|
||||
sign.maintainRatio=Toggle maintain aspect ratio
|
||||
sign.saved=Съхранени подписи
|
||||
sign.save=Запазване на подпис
|
||||
sign.personalSigs=Лични подписи
|
||||
sign.sharedSigs=Споделени подписи
|
||||
sign.noSavedSigs=Не са намерени запазени подписи
|
||||
sign.addToAll=Дибави към всички страници
|
||||
sign.delete=Изтрий
|
||||
sign.first=Първа страница
|
||||
sign.last=Последна страница
|
||||
sign.next=Следваща страница
|
||||
sign.previous=Предишна стараница
|
||||
sign.maintainRatio=Превключване за поддържане на съотношението на страните
|
||||
|
||||
|
||||
#repair
|
||||
@@ -934,7 +952,7 @@ compress.title=Компресиране
|
||||
compress.header=Компресиране на PDF
|
||||
compress.credit=Тази услуга използва qpdf за PDF компресиране/оптимизиране.
|
||||
compress.selectText.1=Ръчен режим - от 1 до 5
|
||||
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||
compress.selectText.1.1=При нива на оптимизация от 6 до 9, в допълнение към общото компресиране на PDF, резолюцията на изображението се намалява, за да се намали допълнително размерът на файла. По-високите нива водят до по-силна компресия на изображенията (до 50% от оригиналния размер), като се постига по-голямо намаляване на размера, но с потенциална загуба на качество на изображенията.
|
||||
compress.selectText.2=Ниво на оптимизация:
|
||||
compress.selectText.3=4 (Ужасно за текстови изображения)
|
||||
compress.selectText.4=Автоматичен режим - Автоматично настройва качеството, за да получи PDF с точен размер
|
||||
@@ -974,6 +992,7 @@ pdfOrganiser.mode.7=Премахни първо
|
||||
pdfOrganiser.mode.8=Премахване на последния
|
||||
pdfOrganiser.mode.9=Премахване на първия и последния
|
||||
pdfOrganiser.mode.10=Обединяване на четно и нечетно
|
||||
pdfOrganiser.mode.11=Дублиране на всички страници
|
||||
pdfOrganiser.placeholder=(напр. 1,3,2 или 4-8,2,10-12 или 2n-1)
|
||||
|
||||
|
||||
@@ -981,42 +1000,42 @@ pdfOrganiser.placeholder=(напр. 1,3,2 или 4-8,2,10-12 или 2n-1)
|
||||
multiTool.title=PDF Мулти инструмент
|
||||
multiTool.header=PDF Мулти инструмент
|
||||
multiTool.uploadPrompts=Име на файл
|
||||
multiTool.selectAll=Select All
|
||||
multiTool.deselectAll=Deselect All
|
||||
multiTool.selectPages=Page Select
|
||||
multiTool.selectedPages=Selected Pages
|
||||
multiTool.page=Page
|
||||
multiTool.deleteSelected=Delete Selected
|
||||
multiTool.downloadAll=Export
|
||||
multiTool.downloadSelected=Export Selected
|
||||
multiTool.selectAll=Изберете всички
|
||||
multiTool.deselectAll=Отмяна на избора на всички
|
||||
multiTool.selectPages=Избор на страница
|
||||
multiTool.selectedPages=Избрани страници
|
||||
multiTool.page=Страница
|
||||
multiTool.deleteSelected=Изтриване на избраното
|
||||
multiTool.downloadAll=Експорт
|
||||
multiTool.downloadSelected=Избрано за експортиране
|
||||
|
||||
multiTool.insertPageBreak=Insert Page Break
|
||||
multiTool.addFile=Add File
|
||||
multiTool.rotateLeft=Rotate Left
|
||||
multiTool.rotateRight=Rotate Right
|
||||
multiTool.split=Split
|
||||
multiTool.moveLeft=Move Left
|
||||
multiTool.moveRight=Move Right
|
||||
multiTool.delete=Delete
|
||||
multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
multiTool.insertPageBreak=Вмъкване на прекъсване на страница
|
||||
multiTool.addFile=Добавяне на файл
|
||||
multiTool.rotateLeft=Завъртете наляво
|
||||
multiTool.rotateRight=Завъртете надясно
|
||||
multiTool.split=Разделяне
|
||||
multiTool.moveLeft=Преместване наляво
|
||||
multiTool.moveRight=Преместване надясно
|
||||
multiTool.delete=Изтрий
|
||||
multiTool.dragDropMessage=Избран(и) страница(/и)
|
||||
multiTool.undo=Отмяна на
|
||||
multiTool.redo=Повтори
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
decrypt.passwordPrompt=Този файл е защитен с парола. Моля, въведете паролата:
|
||||
decrypt.cancelled=Операцията за PDF е отменена: {0}
|
||||
decrypt.noPassword=Не е предоставена парола за шифрирания PDF: {0}
|
||||
decrypt.invalidPassword=Моля, опитайте отново с правилната парола.
|
||||
decrypt.invalidPasswordHeader=Неправилна парола или неподдържано криптиране за PDF: {0}
|
||||
decrypt.unexpectedError=Възникна грешка при обработката на файла. Моля, опитайте отново.
|
||||
decrypt.serverError=Грешка в работата на сървъра повреме на дешифриране: {0}
|
||||
decrypt.success=Файлът е успешно дешифриран.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
multiTool-advert.message=Тази функция е налична и в нашата <a href="{0}">страница с множество инструменти</a>. Проверете го за подобрен потребителски интерфейс страница по страница и допълнителни функции!
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=Преглед на PDF
|
||||
viewPdf.title=Преглед/Редактиране на PDF
|
||||
viewPdf.header=Преглед на PDF
|
||||
|
||||
#pageRemover
|
||||
@@ -1167,7 +1186,7 @@ changeMetadata.submit=Промени
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF към PDF/A
|
||||
pdfToPDFA.header=PDF към PDF/A
|
||||
pdfToPDFA.credit=Тази услуга използва qpdf за PDF/A преобразуване.
|
||||
pdfToPDFA.credit=Тази услуга използва libreoffice за PDF/A преобразуване.
|
||||
pdfToPDFA.submit=Преобразуване
|
||||
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
|
||||
pdfToPDFA.outputFormat=Изходен формат
|
||||
@@ -1317,51 +1336,51 @@ splitByChapters.desc.4=Разрешаване на дубликати: Ако е
|
||||
splitByChapters.submit=Разделяне на PDF
|
||||
|
||||
#File Chooser
|
||||
fileChooser.click=Click
|
||||
fileChooser.or=or
|
||||
fileChooser.dragAndDrop=Drag & Drop
|
||||
fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||
fileChooser.click=Щракнете
|
||||
fileChooser.or=или
|
||||
fileChooser.dragAndDrop=Влачете и пуснете
|
||||
fileChooser.dragAndDropPDF=Влачете и пуснете PDF файл
|
||||
fileChooser.dragAndDropImage=Влачете и пуснете изображение
|
||||
fileChooser.hoveredDragAndDrop=Влачете и пуснете файл(ове) тук
|
||||
|
||||
#release notes
|
||||
releases.footer=Releases
|
||||
releases.title=Release Notes
|
||||
releases.header=Release Notes
|
||||
releases.current.version=Current Release
|
||||
releases.note=Release notes are only available in English
|
||||
releases.footer=Версии
|
||||
releases.title=Бележки към версиите
|
||||
releases.header=Бележки към версиите
|
||||
releases.current.version=Текуща версия
|
||||
releases.note=Бележките към изданието са налични само на английски език
|
||||
|
||||
#Validate Signature
|
||||
validateSignature.title=Validate PDF Signatures
|
||||
validateSignature.header=Validate Digital Signatures
|
||||
validateSignature.selectPDF=Select signed PDF file
|
||||
validateSignature.submit=Validate Signatures
|
||||
validateSignature.results=Validation Results
|
||||
validateSignature.status=Status
|
||||
validateSignature.signer=Signer
|
||||
validateSignature.date=Date
|
||||
validateSignature.reason=Reason
|
||||
validateSignature.location=Location
|
||||
validateSignature.noSignatures=No digital signatures found in this document
|
||||
validateSignature.status.valid=Valid
|
||||
validateSignature.status.invalid=Invalid
|
||||
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||
validateSignature.cert.expired=Certificate has expired
|
||||
validateSignature.cert.revoked=Certificate has been revoked
|
||||
validateSignature.signature.info=Signature Information
|
||||
validateSignature.signature=Signature
|
||||
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||
validateSignature.cert.info=Certificate Details
|
||||
validateSignature.cert.issuer=Issuer
|
||||
validateSignature.cert.subject=Subject
|
||||
validateSignature.cert.serialNumber=Serial Number
|
||||
validateSignature.cert.validFrom=Valid From
|
||||
validateSignature.cert.validUntil=Valid Until
|
||||
validateSignature.cert.algorithm=Algorithm
|
||||
validateSignature.cert.keySize=Key Size
|
||||
validateSignature.cert.version=Version
|
||||
validateSignature.cert.keyUsage=Key Usage
|
||||
validateSignature.cert.selfSigned=Self-Signed
|
||||
validateSignature.cert.bits=bits
|
||||
validateSignature.title=Удостоверяване на PDF подписи
|
||||
validateSignature.header=Потвърждаване на цифрови подписи
|
||||
validateSignature.selectPDF=Изберете подписания PDF файл
|
||||
validateSignature.submit=Потвърждаване на подписите
|
||||
validateSignature.results=Резултати от валидирането
|
||||
validateSignature.status=Статус
|
||||
validateSignature.signer=Подписващ
|
||||
validateSignature.date=Дата
|
||||
validateSignature.reason=Причина
|
||||
validateSignature.location=Местоположение
|
||||
validateSignature.noSignatures=В този документ не са открити цифрови подписи
|
||||
validateSignature.status.valid=Валиден
|
||||
validateSignature.status.invalid=Невалиден
|
||||
validateSignature.chain.invalid=Удостоверяването на веригата на сертификата е неуспешно - не може да се провери самоличността на подписващия
|
||||
validateSignature.trust.invalid=Сертификатът не е в хранилището за доверие - източникът не може да бъде проверен
|
||||
validateSignature.cert.expired=Сертификатът е изтекъл
|
||||
validateSignature.cert.revoked=Сертификатът е отменен
|
||||
validateSignature.signature.info=Информация за подписа
|
||||
validateSignature.signature=Подпис
|
||||
validateSignature.signature.mathValid=Подписът е математически валиден, НО:
|
||||
validateSignature.selectCustomCert=Файл със сертификат X.509 по поръчка (по избор)
|
||||
validateSignature.cert.info=Подробности за сертификата
|
||||
validateSignature.cert.issuer=Издател
|
||||
validateSignature.cert.subject=Тема
|
||||
validateSignature.cert.serialNumber=Сериен номер
|
||||
validateSignature.cert.validFrom=Валиден от
|
||||
validateSignature.cert.validUntil=Валиден до
|
||||
validateSignature.cert.algorithm=Алгоритъм
|
||||
validateSignature.cert.keySize=Размер на ключ
|
||||
validateSignature.cert.version=Версия
|
||||
validateSignature.cert.keyUsage=Предназначение на ключа за използване
|
||||
validateSignature.cert.selfSigned=Самостоятелно подписан
|
||||
validateSignature.cert.bits=битове
|
||||
|
||||
@@ -138,6 +138,7 @@ analytics.settings=Pots canviar la configuració de les analítiques al fitxer c
|
||||
# NAVBAR #
|
||||
#############
|
||||
navbar.favorite=Favorits
|
||||
navbar.recent=New and recently updated
|
||||
navbar.darkmode=Mode Fosc
|
||||
navbar.language=Idiomes
|
||||
navbar.settings=Opcions
|
||||
@@ -261,10 +262,19 @@ home.desc=L'eina allotjada localment per a necessitats de PDF.
|
||||
home.searchBar=Cerca funcions...
|
||||
|
||||
|
||||
home.viewPdf.title=Visualitza PDF
|
||||
home.viewPdf.title=View/Edit PDF
|
||||
home.viewPdf.desc=Visualitza, anota, afegeix text o imatges
|
||||
viewPdf.tags=view,read,annotate,text,image
|
||||
|
||||
home.setFavorites=Set Favourites
|
||||
home.hideFavorites=Hide Favourites
|
||||
home.showFavorites=Show Favourites
|
||||
home.legacyHomepage=Old homepage
|
||||
home.newHomePage=Try our new homepage!
|
||||
home.alphabetical=Alphabetical
|
||||
home.globalPopularity=Global Popularity
|
||||
home.sortBy=Sort by:
|
||||
|
||||
home.multiTool.title=Eina Multifunció de PDF
|
||||
home.multiTool.desc=Fusiona, Rota, Reorganitza i Esborra pàgines
|
||||
multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side
|
||||
@@ -452,6 +462,9 @@ home.MarkdownToPDF.title=Markdown a PDF
|
||||
home.MarkdownToPDF.desc=Converteix qualsevol fitxer Markdown a PDF
|
||||
MarkdownToPDF.tags=markup,web-content,transformation,convert
|
||||
|
||||
home.PDFToMarkdown.title=PDF to Markdown
|
||||
home.PDFToMarkdown.desc=Converts any PDF to Markdown
|
||||
PDFToMarkdown.tags=markup,web-content,transformation,convert,md
|
||||
|
||||
home.getPdfInfo.title=Obteniu Tota la Informació sobre el PDF
|
||||
home.getPdfInfo.desc=Recupera tota la informació possible sobre els PDFs
|
||||
@@ -610,6 +623,7 @@ redact.showAttatchments=Show Attachments
|
||||
redact.showLayers=Show Layers (double-click to reset all layers to the default state)
|
||||
redact.colourPicker=Colour Picker
|
||||
redact.findCurrentOutlineItem=Find current outline item
|
||||
redact.applyChanges=Apply Changes
|
||||
|
||||
#showJS
|
||||
showJS.title=Mostra Javascript
|
||||
@@ -646,6 +660,11 @@ MarkdownToPDF.help=Work in progress
|
||||
MarkdownToPDF.credit=Uses WeasyPrint
|
||||
|
||||
|
||||
#pdf-to-markdown
|
||||
PDFToMarkdown.title=PDF To Markdown
|
||||
PDFToMarkdown.header=PDF To Markdown
|
||||
PDFToMarkdown.submit=Convert
|
||||
|
||||
|
||||
#url-to-pdf
|
||||
URLToPDF.title=URL a PDF
|
||||
@@ -750,7 +769,6 @@ autoSplitPDF.selectText.3=Puja el fitxer PDF escanejat gran i deixa que Stirling
|
||||
autoSplitPDF.selectText.4=Les pàgines divisòries es detecten i eliminen automàticament, garantint un document final ordenat.
|
||||
autoSplitPDF.formPrompt=Envia un PDF que contingui les pàgines divisòries de Stirling-PDF:
|
||||
autoSplitPDF.duplexMode=Mode Dúplex (Escaneig de davant i darrere)
|
||||
autoSplitPDF.dividerDownload1=Descarrega 'Divisor Automàtic (mínim).pdf'
|
||||
autoSplitPDF.dividerDownload2=Descarrega 'Divisor Automàtic (amb instruccions).pdf'
|
||||
autoSplitPDF.submit=Envia
|
||||
|
||||
@@ -974,6 +992,7 @@ pdfOrganiser.mode.7=Eliminar Primer
|
||||
pdfOrganiser.mode.8=Eliminar Últim
|
||||
pdfOrganiser.mode.9=Eliminar Primer i Últim
|
||||
pdfOrganiser.mode.10=Fusionar Parells-Senars
|
||||
pdfOrganiser.mode.11=Duplicate all pages
|
||||
pdfOrganiser.placeholder=(p. ex. 1,3,2 o 4-8,2,10-12 o 2n-1)
|
||||
|
||||
|
||||
@@ -1016,7 +1035,7 @@ decrypt.success=File decrypted successfully.
|
||||
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
|
||||
viewPdf.title=Visualitza PDF
|
||||
viewPdf.title=View/Edit PDF
|
||||
viewPdf.header=Visualitza PDF
|
||||
|
||||
#pageRemover
|
||||
@@ -1167,7 +1186,7 @@ changeMetadata.submit=Canvia
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF a PDF/A
|
||||
pdfToPDFA.header=PDF a PDF/A
|
||||
pdfToPDFA.credit=Utilitza qpdf per a la conversió a PDF/A
|
||||
pdfToPDFA.credit=Utilitza libreoffice per a la conversió a PDF/A
|
||||
pdfToPDFA.submit=Converteix
|
||||
pdfToPDFA.tip=Actualment no funciona per a múltiples entrades al mateix temps
|
||||
pdfToPDFA.outputFormat=Format de sortida
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user