Merge branch 'main' of git@github.com:Stirling-Tools/Stirling-PDF.git into main

This commit is contained in:
a 2024-08-20 22:33:05 +01:00
commit c778fa73ef
36 changed files with 816 additions and 487 deletions

View File

@ -10,7 +10,21 @@ body:
Thanks for taking the time to fill out this bug report! Thanks for taking the time to fill out this bug report!
This issue form is for reporting bugs only. Please fill out the following sections to help us understand the issue you are facing. This issue form is for reporting bugs only. Please fill out the following sections to help us understand the issue you are facing.
- type: dropdown
id: installation-method
attributes:
label: Installation Method
description: |
Indicate whether you are using Docker or a local installation.
options:
- Docker
- Docker ultra lite
- Docker fat
- Local Installation
validations:
required: true
- type: textarea - type: textarea
id: problem id: problem
validations: validations:

View File

@ -2,6 +2,7 @@ Translation:
- changed-files: - changed-files:
- any-glob-to-any-file: 'src/main/resources/messages_*_*.properties' - any-glob-to-any-file: 'src/main/resources/messages_*_*.properties'
- any-glob-to-any-file: 'scripts/ignore_translation.toml' - any-glob-to-any-file: 'scripts/ignore_translation.toml'
- any-glob-to-any-file: 'src/main/resources/templates/fragments/languages.html'
Front End: Front End:
- changed-files: - changed-files:

View File

@ -31,7 +31,7 @@ jobs:
distribution: "temurin" distribution: "temurin"
- name: Set up Gradle - name: Set up Gradle
uses: gradle/actions/setup-gradle@v3 uses: gradle/actions/setup-gradle@v4
with: with:
gradle-version: 8.7 gradle-version: 8.7
@ -39,7 +39,7 @@ jobs:
run: ./gradlew build --no-build-cache run: ./gradlew build --no-build-cache
docker-compose-tests: docker-compose-tests:
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' || # if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
# (github.event_name == 'pull_request' && # (github.event_name == 'pull_request' &&
# contains(github.event.pull_request.labels.*.name, 'licenses') == false && # contains(github.event.pull_request.labels.*.name, 'licenses') == false &&
# ( # (
@ -74,14 +74,14 @@ jobs:
sudo chmod +x /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: "3.7" python-version: "3.7"
- name: Pip requirements - name: Pip requirements
run: | run: |
pip install -r ./cucumber/requirements.txt pip install -r ./cucumber/requirements.txt
- name: Run Docker Compose Tests - name: Run Docker Compose Tests
run: | run: |
chmod +x ./test.sh chmod +x ./test.sh

View File

@ -25,7 +25,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "adopt" distribution: "adopt"
- uses: gradle/actions/setup-gradle@v3 - uses: gradle/actions/setup-gradle@v4
- name: Run Gradle Command - name: Run Gradle Command
run: ./gradlew clean generateLicenseReport run: ./gradlew clean generateLicenseReport
@ -58,6 +58,7 @@ jobs:
title: "Update 3rd Party Licenses" title: "Update 3rd Party Licenses"
body: | body: |
Auto-generated by [create-pull-request][1] Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request [1]: https://github.com/peter-evans/create-pull-request
labels: licenses labels: licenses
draft: false draft: false
@ -68,7 +69,7 @@ jobs:
run: gh pr review --approve "${{ steps.cpr.outputs.pull-request-number }}" run: gh pr review --approve "${{ steps.cpr.outputs.pull-request-number }}"
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Enable auto-merge - name: Enable auto-merge
if: steps.cpr.outputs.pull-request-operation == 'created' if: steps.cpr.outputs.pull-request-operation == 'created'
uses: peter-evans/enable-pull-request-automerge@v3 uses: peter-evans/enable-pull-request-automerge@v3

View File

@ -22,7 +22,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@v3 - uses: gradle/actions/setup-gradle@v4
with: with:
gradle-version: 8.7 gradle-version: 8.7
@ -72,7 +72,7 @@ jobs:
type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }} type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push main Dockerfile - name: Build and push main Dockerfile
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
with: with:
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
context: . context: .
@ -98,7 +98,7 @@ jobs:
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }} type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
- name: Build and push Dockerfile-ultra-lite - name: Build and push Dockerfile-ultra-lite
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
if: github.ref != 'refs/heads/main' if: github.ref != 'refs/heads/main'
with: with:
context: . context: .
@ -111,7 +111,6 @@ jobs:
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8 platforms: linux/amd64,linux/arm64/v8
- name: Generate tags fat - name: Generate tags fat
id: meta3 id: meta3
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@ -125,7 +124,7 @@ jobs:
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/master' }} type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/master' }}
- name: Build and push main Dockerfile fat - name: Build and push main Dockerfile fat
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
if: github.ref != 'refs/heads/main' if: github.ref != 'refs/heads/main'
with: with:
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}

View File

@ -27,7 +27,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@v3 - uses: gradle/actions/setup-gradle@v4
with: with:
gradle-version: 8.7 gradle-version: 8.7

View File

@ -18,7 +18,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@v3 - uses: gradle/actions/setup-gradle@v4
- name: Generate Swagger documentation - name: Generate Swagger documentation
run: ./gradlew generateOpenApiDocs run: ./gradlew generateOpenApiDocs

View File

@ -39,16 +39,16 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
libreoffice \ libreoffice \
# pdftohtml # pdftohtml
poppler-utils \ poppler-utils \
# OCR MY PDF (unpaper for descew and other advanced featues) # OCR MY PDF (unpaper for descew and other advanced features)
ocrmypdf \ ocrmypdf \
tesseract-ocr-data-eng \ tesseract-ocr-data-eng \
# CV # CV
py3-opencv \ py3-opencv \
# python3/pip # python3/pip
python3 \ python3 \
py3-pip && \ py3-pip && \
# uno unoconv and HTML # uno unoconv and HTML
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \ pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
mv /usr/share/tessdata /usr/share/tessdata-original && \ mv /usr/share/tessdata /usr/share/tessdata-original && \
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
fc-cache -f -v && \ fc-cache -f -v && \

View File

@ -9,7 +9,7 @@ COPY . .
# Build the application with DOCKER_ENABLE_SECURITY=false # Build the application with DOCKER_ENABLE_SECURITY=false
RUN DOCKER_ENABLE_SECURITY=true \ RUN DOCKER_ENABLE_SECURITY=true \
./gradlew clean build ./gradlew clean build
# Main stage # Main stage
FROM alpine:3.20.2 FROM alpine:3.20.2
@ -32,7 +32,7 @@ ENV DOCKER_ENABLE_SECURITY=false \
UMASK=022 \ UMASK=022 \
FAT_DOCKER=true \ FAT_DOCKER=true \
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
# JDK for app # JDK for app
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
@ -64,7 +64,7 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
python3 \ python3 \
py3-pip && \ py3-pip && \
# uno unoconv and HTML # uno unoconv and HTML
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \ pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
mv /usr/share/tessdata /usr/share/tessdata-original && \ mv /usr/share/tessdata /usr/share/tessdata-original && \
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
fc-cache -f -v && \ fc-cache -f -v && \

View File

@ -15,7 +15,7 @@
| file-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | | | file-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | |
| img-to-pdf | | ✔️ | | | | | | | | ✔️ | | | img-to-pdf | | ✔️ | | | | | | | | ✔️ | |
| pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | | | pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | |
| pdf-to-img | | ✔️ | | | | | | | | ✔️ | | | pdf-to-img | | ✔️ | | | | ✔️ | | | | ✔️ | |
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | | | pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | |
| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | | | pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | |
| pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | | | pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | |
@ -44,4 +44,4 @@
| remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | |
| repair | | | | ✔️ | ✔️ | | | ✔️ | | | | | repair | | | | ✔️ | ✔️ | | | ✔️ | | | |
| show-javascript | | | | ✔️ | | | | | | | ✔️ | | show-javascript | | | | ✔️ | | | | | | | ✔️ |
| sign | | | | ✔️ | | | | | | | ✔️ | | sign | | | | ✔️ | | | | | | | ✔️ |

View File

@ -169,42 +169,42 @@ Stirling PDF currently supports 38!
| Language | Progress | | Language | Progress |
| ------------------------------------------- | -------------------------------------- | | ------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![45%](https://geps.dev/progress/45) | | Arabic (العربية) (ar_AR) | ![44%](https://geps.dev/progress/44) |
| Basque (Euskara) (eu_ES) | ![61%](https://geps.dev/progress/61) | | Basque (Euskara) (eu_ES) | ![60%](https://geps.dev/progress/60) |
| Bulgarian (Български) (bg_BG) | ![94%](https://geps.dev/progress/94) | | Bulgarian (Български) (bg_BG) | ![92%](https://geps.dev/progress/92) |
| Catalan (Català) (ca_CA) | ![48%](https://geps.dev/progress/48) | | Catalan (Català) (ca_CA) | ![47%](https://geps.dev/progress/47) |
| Croatian (Hrvatski) (hr_HR) | ![94%](https://geps.dev/progress/94) | | Croatian (Hrvatski) (hr_HR) | ![93%](https://geps.dev/progress/93) |
| Czech (Česky) (cs_CZ) | ![89%](https://geps.dev/progress/89) | | Czech (Česky) (cs_CZ) | ![88%](https://geps.dev/progress/88) |
| Danish (Dansk) (da_DK) | ![9%](https://geps.dev/progress/9) | | Danish (Dansk) (da_DK) | ![9%](https://geps.dev/progress/9) |
| Dutch (Nederlands) (nl_NL) | ![95%](https://geps.dev/progress/95) | | Dutch (Nederlands) (nl_NL) | ![94%](https://geps.dev/progress/94) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![93%](https://geps.dev/progress/93) | | French (Français) (fr_FR) | ![91%](https://geps.dev/progress/91) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) | | German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) |
| Greek (Ελληνικά) (el_GR) | ![81%](https://geps.dev/progress/81) | | Greek (Ελληνικά) (el_GR) | ![80%](https://geps.dev/progress/80) |
| Hindi (हिंदी) (hi_IN) | ![76%](https://geps.dev/progress/76) | | Hindi (हिंदी) (hi_IN) | ![75%](https://geps.dev/progress/75) |
| Hungarian (Magyar) (hu_HU) | ![75%](https://geps.dev/progress/75) | | Hungarian (Magyar) (hu_HU) | ![74%](https://geps.dev/progress/74) |
| Indonesia (Bahasa Indonesia) (id_ID) | ![76%](https://geps.dev/progress/76) | | Indonesia (Bahasa Indonesia) (id_ID) | ![75%](https://geps.dev/progress/75) |
| Irish (Gaeilge) (ga_IE) | ![98%](https://geps.dev/progress/98) | | Irish (Gaeilge) (ga_IE) | ![96%](https://geps.dev/progress/96) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | | Italian (Italiano) (it_IT) | ![98%](https://geps.dev/progress/98) |
| Japanese (日本語) (ja_JP) | ![92%](https://geps.dev/progress/92) | | Japanese (日本語) (ja_JP) | ![91%](https://geps.dev/progress/91) |
| Korean (한국어) (ko_KR) | ![84%](https://geps.dev/progress/84) | | Korean (한국어) (ko_KR) | ![82%](https://geps.dev/progress/82) |
| Norwegian (Norsk) (no_NB) | ![97%](https://geps.dev/progress/97) | | Norwegian (Norsk) (no_NB) | ![96%](https://geps.dev/progress/96) |
| Polish (Polski) (pl_PL) | ![92%](https://geps.dev/progress/92) | | Polish (Polski) (pl_PL) | ![90%](https://geps.dev/progress/90) |
| Portuguese (Português) (pt_PT) | ![78%](https://geps.dev/progress/78) | | Portuguese (Português) (pt_PT) | ![77%](https://geps.dev/progress/77) |
| Portuguese Brazilian (Português) (pt_BR) | ![85%](https://geps.dev/progress/85) | | Portuguese Brazilian (Português) (pt_BR) | ![99%](https://geps.dev/progress/99) |
| Romanian (Română) (ro_RO) | ![38%](https://geps.dev/progress/38) | | Romanian (Română) (ro_RO) | ![38%](https://geps.dev/progress/38) |
| Russian (Русский) (ru_RU) | ![83%](https://geps.dev/progress/83) | | Russian (Русский) (ru_RU) | ![82%](https://geps.dev/progress/82) |
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) | ![78%](https://geps.dev/progress/78) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![77%](https://geps.dev/progress/77) |
| Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) | | Simplified Chinese (简体中文) (zh_CN) | ![97%](https://geps.dev/progress/97) |
| Slovakian (Slovensky) (sk_SK) | ![91%](https://geps.dev/progress/91) | | Slovakian (Slovensky) (sk_SK) | ![90%](https://geps.dev/progress/90) |
| Spanish (Español) (es_ES) | ![97%](https://geps.dev/progress/97) | | Spanish (Español) (es_ES) | ![96%](https://geps.dev/progress/96) |
| Swedish (Svenska) (sv_SE) | ![39%](https://geps.dev/progress/39) | | Swedish (Svenska) (sv_SE) | ![38%](https://geps.dev/progress/38) |
| Thai (ไทย) (th_TH) | ![99%](https://geps.dev/progress/99) | | Thai (ไทย) (th_TH) | ![97%](https://geps.dev/progress/97) |
| Traditional Chinese (繁體中文) (zh_TW) | ![97%](https://geps.dev/progress/97) | | Traditional Chinese (繁體中文) (zh_TW) | ![96%](https://geps.dev/progress/96) |
| Turkish (Türkçe) (tr_TR) | ![98%](https://geps.dev/progress/98) | | Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) |
| Ukrainian (Українська) (uk_UA) | ![89%](https://geps.dev/progress/89) | | Ukrainian (Українська) (uk_UA) | ![88%](https://geps.dev/progress/88) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![98%](https://geps.dev/progress/98) | | Vietnamese (Tiếng Việt) (vi_VN) | ![97%](https://geps.dev/progress/97) |
## Contributing (creating issues, translations, fixing bugs, etc.) ## Contributing (creating issues, translations, fixing bugs, etc.)

View File

@ -21,7 +21,7 @@ ext {
} }
group = "stirling.software" group = "stirling.software"
version = "0.28.0" version = "0.28.2"
java { java {
// 17 is lowest but we support and recommend 21 // 17 is lowest but we support and recommend 21
@ -108,7 +108,7 @@ dependencies {
//security updates //security updates
implementation "ch.qos.logback:logback-classic:$logbackVersion" implementation "ch.qos.logback:logback-classic:$logbackVersion"
implementation "ch.qos.logback:logback-core:$logbackVersion" implementation "ch.qos.logback:logback-core:$logbackVersion"
implementation "org.springframework:spring-webmvc:6.1.12" implementation "org.springframework:spring-webmvc:6.1.9"
implementation("io.github.pixee:java-security-toolkit:1.2.0") implementation("io.github.pixee:java-security-toolkit:1.2.0")
@ -189,7 +189,7 @@ dependencies {
implementation "org.commonmark:commonmark:0.22.0" implementation "org.commonmark:commonmark:0.22.0"
implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0" implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0"
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17 // https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
implementation "com.bucket4j:bucket4j_jdk17-core:8.13.1" implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
implementation "com.fathzer:javaluator:3.0.4" implementation "com.fathzer:javaluator:3.0.4"
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion") developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")

View File

@ -1,5 +1,5 @@
apiVersion: v2 apiVersion: v2
appVersion: 0.28.0 appVersion: 0.28.2
description: locally hosted web application that allows you to perform various operations description: locally hosted web application that allows you to perform various operations
on PDF files on PDF files
home: https://github.com/Stirling-Tools/Stirling-PDF home: https://github.com/Stirling-Tools/Stirling-PDF

View File

@ -181,7 +181,9 @@ ignore = [
[pt_BR] [pt_BR]
ignore = [ ignore = [
'changeMetadata.trapped',
'language.direction', 'language.direction',
'pipelineOptions.pipelineHeader',
] ]
[pt_PT] [pt_PT]

174
scripts/png_to_webp.py Normal file
View File

@ -0,0 +1,174 @@
"""
Author: Ludy87
Description: This script converts a PDF file to WebP images. It includes functionality to resize images if they exceed specified dimensions and handle conversion of PDF pages to WebP format.
Example
-------
To convert a PDF file to WebP images with each page as a separate WebP file:
python script.py input.pdf output_directory
To convert a PDF file to a single WebP image:
python script.py input.pdf output_directory --single
To adjust the DPI resolution for rendering PDF pages:
python script.py input.pdf output_directory --dpi 150
"""
import argparse
import os
from pdf2image import convert_from_path
from PIL import Image
def resize_image(input_image_path, output_image_path, max_size=(16383, 16383)):
"""
Resize the image if its dimensions exceed the maximum allowed size and save it as WebP.
Parameters
----------
input_image_path : str
Path to the input image file.
output_image_path : str
Path where the output WebP image will be saved.
max_size : tuple of int, optional
Maximum allowed dimensions for the image (width, height). Default is (16383, 16383).
Returns
-------
None
"""
try:
# Open the image
image = Image.open(input_image_path)
width, height = image.size
max_width, max_height = max_size
# Check if the image dimensions exceed the maximum allowed dimensions
if width > max_width or height > max_height:
# Calculate the scaling ratio
ratio = min(max_width / width, max_height / height)
new_width = int(width * ratio)
new_height = int(height * ratio)
# Resize the image
resized_image = image.resize((new_width, new_height), Image.LANCZOS)
resized_image.save(output_image_path, format="WEBP", quality=100)
print(
f"The image was successfully resized to ({new_width}, {new_height}) and saved as WebP: {output_image_path}"
)
else:
# If dimensions are within the allowed limits, save the image directly
image.save(output_image_path, format="WEBP", quality=100)
print(f"The image was successfully saved as WebP: {output_image_path}")
except Exception as e:
print(f"An error occurred: {e}")
def convert_image_to_webp(input_image, output_file):
"""
Convert an image to WebP format, resizing it if it exceeds the maximum dimensions.
Parameters
----------
input_image : str
Path to the input image file.
output_file : str
Path where the output WebP image will be saved.
Returns
-------
None
"""
# Resize the image if it exceeds the maximum dimensions
resize_image(input_image, output_file, max_size=(16383, 16383))
def pdf_to_webp(pdf_path, output_dir, dpi=300):
"""
Convert each page of a PDF file to WebP images.
Parameters
----------
pdf_path : str
Path to the input PDF file.
output_dir : str
Directory where the WebP images will be saved.
dpi : int, optional
DPI resolution for rendering PDF pages. Default is 300.
Returns
-------
None
"""
# Convert the PDF to a list of images
images = convert_from_path(pdf_path, dpi=dpi)
for page_number, image in enumerate(images):
# Define temporary PNG path
temp_png_path = os.path.join(output_dir, f"temp_page_{page_number + 1}.png")
image.save(temp_png_path, format="PNG")
# Define the output path for WebP
output_path = os.path.join(output_dir, f"page_{page_number + 1}.webp")
# Convert PNG to WebP
convert_image_to_webp(temp_png_path, output_path)
# Delete the temporary PNG file
os.remove(temp_png_path)
def main(pdf_image_path, output_dir, dpi=300, single_images_flag=False):
"""
Main function to handle conversion from PDF to WebP images.
Parameters
----------
pdf_image_path : str
Path to the input PDF file or image.
output_dir : str
Directory where the WebP images will be saved.
dpi : int, optional
DPI resolution for rendering PDF pages. Default is 300.
single_images_flag : bool, optional
If True, combine all pages into a single WebP image. Default is False.
Returns
-------
None
"""
if single_images_flag:
# Combine all pages into a single WebP image
output_path = os.path.join(output_dir, "combined_image.webp")
convert_image_to_webp(pdf_image_path, output_path)
else:
# Convert each PDF page to a separate WebP image
pdf_to_webp(pdf_image_path, output_dir, dpi)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Convert a PDF file to WebP images.")
parser.add_argument("pdf_path", help="The path to the input PDF file.")
parser.add_argument(
"output_dir", help="The directory where the WebP images should be saved."
)
parser.add_argument(
"--dpi",
type=int,
default=300,
help="The DPI resolution for rendering the PDF pages (default: 300).",
)
parser.add_argument(
"--single",
action="store_true",
help="Combine all pages into a single WebP image.",
)
args = parser.parse_args()
os.makedirs(args.output_dir, exist_ok=True)
main(
args.pdf_path,
args.output_dir,
dpi=args.dpi,
single_images_flag=args.single,
)

View File

@ -14,6 +14,8 @@ import java.util.List;
import org.simpleyaml.configuration.comments.CommentType; import org.simpleyaml.configuration.comments.CommentType;
import org.simpleyaml.configuration.file.YamlFile; import org.simpleyaml.configuration.file.YamlFile;
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
@ -71,9 +73,17 @@ public class ConfigInitializer
} }
final YamlFile settingsTemplateFile = new YamlFile(tempTemplatePath.toFile()); final YamlFile settingsTemplateFile = new YamlFile(tempTemplatePath.toFile());
DumperOptions yamlOptionsSettingsTemplateFile =
((SimpleYamlImplementation) settingsTemplateFile.getImplementation())
.getDumperOptions();
yamlOptionsSettingsTemplateFile.setSplitLines(false);
settingsTemplateFile.loadWithComments(); settingsTemplateFile.loadWithComments();
final YamlFile settingsFile = new YamlFile(settingsPath.toFile()); final YamlFile settingsFile = new YamlFile(settingsPath.toFile());
DumperOptions yamlOptionsSettingsFile =
((SimpleYamlImplementation) settingsFile.getImplementation())
.getDumperOptions();
yamlOptionsSettingsFile.setSplitLines(false);
settingsFile.loadWithComments(); settingsFile.loadWithComments();
// Load headers and comments // Load headers and comments
@ -81,6 +91,10 @@ public class ConfigInitializer
// Create a new file for temporary settings // Create a new file for temporary settings
final YamlFile tempSettingFile = new YamlFile(settingsPath.toFile()); final YamlFile tempSettingFile = new YamlFile(settingsPath.toFile());
DumperOptions yamlOptionsTempSettingFile =
((SimpleYamlImplementation) tempSettingFile.getImplementation())
.getDumperOptions();
yamlOptionsTempSettingFile.setSplitLines(false);
tempSettingFile.createNewFile(true); tempSettingFile.createNewFile(true);
tempSettingFile.setHeader(header); tempSettingFile.setHeader(header);

View File

@ -166,6 +166,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Python", REMOVE_BLANKS); addEndpointToGroup("Python", REMOVE_BLANKS);
addEndpointToGroup("Python", "html-to-pdf"); addEndpointToGroup("Python", "html-to-pdf");
addEndpointToGroup("Python", "url-to-pdf"); addEndpointToGroup("Python", "url-to-pdf");
addEndpointToGroup("Python", "pdf-to-img");
// openCV // openCV
addEndpointToGroup("OpenCV", "extract-image-scans"); addEndpointToGroup("OpenCV", "extract-image-scans");

View File

@ -6,6 +6,8 @@ import java.nio.file.Paths;
import java.util.UUID; import java.util.UUID;
import org.simpleyaml.configuration.file.YamlFile; import org.simpleyaml.configuration.file.YamlFile;
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -92,6 +94,9 @@ public class InitialSecuritySetup {
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
final YamlFile settingsYml = new YamlFile(path.toFile()); final YamlFile settingsYml = new YamlFile(path.toFile());
DumperOptions yamlOptionssettingsYml =
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
yamlOptionssettingsYml.setSplitLines(false);
settingsYml.loadWithComments(); settingsYml.loadWithComments();

View File

@ -2,6 +2,8 @@ package stirling.software.SPDF.config.security;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
@ -9,6 +11,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
@ -22,6 +25,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.ApiKeyAuthenticationToken; import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
import stirling.software.SPDF.model.User;
@Component @Component
public class UserAuthenticationFilter extends OncePerRequestFilter { public class UserAuthenticationFilter extends OncePerRequestFilter {
@ -54,15 +58,20 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
try { try {
// Use API key to authenticate. This requires you to have an authentication // Use API key to authenticate. This requires you to have an authentication
// provider for API keys. // provider for API keys.
UserDetails userDetails = userService.loadUserByApiKey(apiKey); Optional<User> user = userService.loadUserByApiKey(apiKey);
if (userDetails == null) { if (!user.isPresent()) {
response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("Invalid API Key."); response.getWriter().write("Invalid API Key.");
return; return;
} }
authentication = List<SimpleGrantedAuthority> authorities =
new ApiKeyAuthenticationToken( user.get().getAuthorities().stream()
userDetails, apiKey, userDetails.getAuthorities()); .map(
authority ->
new SimpleGrantedAuthority(
authority.getAuthority()))
.collect(Collectors.toList());
authentication = new ApiKeyAuthenticationToken(user.get(), apiKey, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication); SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (AuthenticationException e) { } catch (AuthenticationException e) {
// If API key authentication fails, deny the request // If API key authentication fails, deny the request

View File

@ -22,6 +22,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.DatabaseBackupInterface; import stirling.software.SPDF.config.DatabaseBackupInterface;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
@ -65,8 +66,8 @@ public class UserService implements UserServiceInterface {
} }
public Authentication getAuthentication(String apiKey) { public Authentication getAuthentication(String apiKey) {
User user = getUserByApiKey(apiKey); Optional<User> user = getUserByApiKey(apiKey);
if (user == null) { if (!user.isPresent()) {
throw new UsernameNotFoundException("API key is not valid"); throw new UsernameNotFoundException("API key is not valid");
} }
@ -74,7 +75,7 @@ public class UserService implements UserServiceInterface {
return new UsernamePasswordAuthenticationToken( return new UsernamePasswordAuthenticationToken(
user, // principal (typically the user) user, // principal (typically the user)
null, // credentials (we don't expose the password or API key here) null, // credentials (we don't expose the password or API key here)
getAuthorities(user) // user's authorities (roles/permissions) getAuthorities(user.get()) // user's authorities (roles/permissions)
); );
} }
@ -89,17 +90,17 @@ public class UserService implements UserServiceInterface {
String apiKey; String apiKey;
do { do {
apiKey = UUID.randomUUID().toString(); apiKey = UUID.randomUUID().toString();
} while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness } while (userRepository.findByApiKey(apiKey).isPresent()); // Ensure uniqueness
return apiKey; return apiKey;
} }
public User addApiKeyToUser(String username) { public User addApiKeyToUser(String username) {
User user = Optional<User> user = findByUsernameIgnoreCase(username);
findByUsernameIgnoreCase(username) if (user.isPresent()) {
.orElseThrow(() -> new UsernameNotFoundException("User not found")); user.get().setApiKey(generateApiKey());
return userRepository.save(user.get());
user.setApiKey(generateApiKey()); }
return userRepository.save(user); throw new UsernameNotFoundException("User not found");
} }
public User refreshApiKeyForUser(String username) { public User refreshApiKeyForUser(String username) {
@ -114,21 +115,18 @@ public class UserService implements UserServiceInterface {
} }
public boolean isValidApiKey(String apiKey) { public boolean isValidApiKey(String apiKey) {
return userRepository.findByApiKey(apiKey) != null; return userRepository.findByApiKey(apiKey).isPresent();
} }
public User getUserByApiKey(String apiKey) { public Optional<User> getUserByApiKey(String apiKey) {
return userRepository.findByApiKey(apiKey); return userRepository.findByApiKey(apiKey);
} }
public UserDetails loadUserByApiKey(String apiKey) { public Optional<User> loadUserByApiKey(String apiKey) {
User user = userRepository.findByApiKey(apiKey); Optional<User> user = userRepository.findByApiKey(apiKey);
if (user != null) {
// Convert your User entity to a UserDetails object with authorities if (user.isPresent()) {
return new org.springframework.security.core.userdetails.User( return user;
user.getUsername(),
user.getPassword(), // you might not need this for API key auth
getAuthorities(user));
} }
return null; // or throw an exception return null; // or throw an exception
} }

View File

@ -1,11 +1,23 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.URLConnection; import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.ImageType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@ -21,6 +33,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest; import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest; import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@ -60,15 +74,92 @@ public class ConvertImgPDFController {
result = result =
PdfUtils.convertFromPdf( PdfUtils.convertFromPdf(
pdfBytes, pdfBytes,
imageFormat.toUpperCase(), imageFormat.equalsIgnoreCase("webp") ? "png" : imageFormat.toUpperCase(),
colorTypeResult, colorTypeResult,
singleImage, singleImage,
Integer.valueOf(dpi), Integer.valueOf(dpi),
filename); filename);
if (result == null || result.length == 0) { if (result == null || result.length == 0) {
logger.error("resultant bytes for {} is null, error converting ", filename); logger.error("resultant bytes for {} is null, error converting ", filename);
} }
if (imageFormat.equalsIgnoreCase("webp")) {
// Write the output stream to a temp file
Path tempFile = Files.createTempFile("temp_png", ".png");
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
fos.write(result);
fos.flush();
}
String pythonVersion = "python3";
try {
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(Arrays.asList("python3", "--version"));
} catch (IOException e) {
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(Arrays.asList("python", "--version"));
pythonVersion = "python";
}
List<String> command = new ArrayList<>();
command.add(pythonVersion);
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
// Create a temporary directory for the output WebP files
Path tempOutputDir = Files.createTempDirectory("webp_output");
if (singleImage) {
// Run the Python script to convert PNG to WebP
command.add(tempFile.toString());
command.add(tempOutputDir.toString());
command.add("--single");
} else {
// Save the uploaded PDF to a temporary file
Path tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
file.transferTo(tempPdfPath.toFile());
// Run the Python script to convert PDF to WebP
command.add(tempPdfPath.toString());
command.add(tempOutputDir.toString());
}
command.add("--dpi");
command.add(dpi);
ProcessExecutorResult resultProcess =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command);
// Find all WebP files in the output directory
List<Path> webpFiles =
Files.walk(tempOutputDir)
.filter(path -> path.toString().endsWith(".webp"))
.collect(Collectors.toList());
if (webpFiles.isEmpty()) {
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
throw new IOException("No WebP files were created. " + resultProcess.getMessages());
}
byte[] bodyBytes = new byte[0];
if (webpFiles.size() == 1) {
// Return the single WebP file directly
Path webpFilePath = webpFiles.get(0);
bodyBytes = Files.readAllBytes(webpFilePath);
} else {
// Create a ZIP file containing all WebP images
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
for (Path webpFile : webpFiles) {
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
Files.copy(webpFile, zos);
zos.closeEntry();
}
}
bodyBytes = zipOutputStream.toByteArray();
}
// Clean up the temporary files
Files.deleteIfExists(tempFile);
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
result = bodyBytes;
}
if (singleImage) { if (singleImage) {
String docName = filename + "." + imageFormat; String docName = filename + "." + imageFormat;
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat)); MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));

View File

@ -140,6 +140,9 @@ public class ExtractImagesController {
Set<Integer> processedImages, Set<Integer> processedImages,
ZipOutputStream zos) ZipOutputStream zos)
throws IOException { throws IOException {
if(page.getResources() == null || page.getResources().getXObjectNames() == null) {
return;
}
for (COSName name : page.getResources().getXObjectNames()) { for (COSName name : page.getResources().getXObjectNames()) {
if (page.getResources().isImageXObject(name)) { if (page.getResources().isImageXObject(name)) {
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name); PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);

View File

@ -21,14 +21,6 @@ public class ConverterWebController {
return "convert/book-to-pdf"; return "convert/book-to-pdf";
} }
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
@GetMapping("/pdf-to-book")
@Hidden
public String convertPdfToBookForm(Model model) {
model.addAttribute("currentPage", "pdf-to-book");
return "convert/pdf-to-book";
}
@GetMapping("/img-to-pdf") @GetMapping("/img-to-pdf")
@Hidden @Hidden
public String convertImgToPdfForm(Model model) { public String convertImgToPdfForm(Model model) {
@ -57,13 +49,6 @@ public class ConverterWebController {
return "convert/url-to-pdf"; return "convert/url-to-pdf";
} }
@GetMapping("/pdf-to-img")
@Hidden
public String pdfToimgForm(Model model) {
model.addAttribute("currentPage", "pdf-to-img");
return "convert/pdf-to-img";
}
@GetMapping("/file-to-pdf") @GetMapping("/file-to-pdf")
@Hidden @Hidden
public String convertToPdfForm(Model model) { public String convertToPdfForm(Model model) {
@ -73,6 +58,21 @@ public class ConverterWebController {
// PDF TO...... // PDF TO......
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
@GetMapping("/pdf-to-book")
@Hidden
public String convertPdfToBookForm(Model model) {
model.addAttribute("currentPage", "pdf-to-book");
return "convert/pdf-to-book";
}
@GetMapping("/pdf-to-img")
@Hidden
public String pdfToimgForm(Model model) {
model.addAttribute("currentPage", "pdf-to-img");
return "convert/pdf-to-img";
}
@GetMapping("/pdf-to-html") @GetMapping("/pdf-to-html")
@Hidden @Hidden
public ModelAndView pdfToHTML() { public ModelAndView pdfToHTML() {

View File

@ -1,5 +1,7 @@
package stirling.software.SPDF.model; package stirling.software.SPDF.model;
import java.io.Serializable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
@ -11,7 +13,9 @@ import jakarta.persistence.Table;
@Entity @Entity
@Table(name = "authorities") @Table(name = "authorities")
public class Authority { public class Authority implements Serializable {
private static final long serialVersionUID = 1L;
public Authority() {} public Authority() {}

View File

@ -1,5 +1,6 @@
package stirling.software.SPDF.model; package stirling.software.SPDF.model;
import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -23,7 +24,9 @@ import jakarta.persistence.Table;
@Entity @Entity
@Table(name = "users") @Table(name = "users")
public class User { public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@ -12,7 +12,7 @@ public class ConvertToImageRequest extends PDFFile {
@Schema( @Schema(
description = "The output image format", description = "The output image format",
allowableValues = {"png", "jpeg", "jpg", "gif"}) allowableValues = {"png", "jpeg", "jpg", "gif", "webp"})
private String imageFormat; private String imageFormat;
@Schema( @Schema(

View File

@ -13,5 +13,5 @@ public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username); Optional<User> findByUsername(String username);
User findByApiKey(String apiKey); Optional<User> findByApiKey(String apiKey);
} }

View File

@ -1,6 +1,8 @@
package stirling.software.SPDF.service; package stirling.software.SPDF.service;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@ -16,7 +18,7 @@ public class PdfImageRemovalService {
/** /**
* Removes all image objects from the provided PDF document. * Removes all image objects from the provided PDF document.
* *
* This method iterates over each page in the document and removes any image XObjects found * <p>This method iterates over each page in the document and removes any image XObjects found
* in the page's resources. * in the page's resources.
* *
* @param document The PDF document from which images will be removed. * @param document The PDF document from which images will be removed.
@ -27,14 +29,22 @@ public class PdfImageRemovalService {
// Iterate over each page in the PDF document // Iterate over each page in the PDF document
for (PDPage page : document.getPages()) { for (PDPage page : document.getPages()) {
PDResources resources = page.getResources(); PDResources resources = page.getResources();
// Collect the XObject names to remove
List<COSName> namesToRemove = new ArrayList<>();
// Iterate over all XObject names in the page's resources // Iterate over all XObject names in the page's resources
for (COSName name : resources.getXObjectNames()) { for (COSName name : resources.getXObjectNames()) {
// Check if the XObject is an image // Check if the XObject is an image
if (resources.isImageXObject(name)) { if (resources.isImageXObject(name)) {
// Remove the image XObject by setting it to null // Collect the name for removal
resources.put(name, (PDXObject) null); namesToRemove.add(name);
} }
} }
// Now, modify the resources by removing the collected names
for (COSName name : namesToRemove) {
resources.put(name, (PDXObject) null);
}
} }
return document; return document;
} }

View File

@ -1147,4 +1147,4 @@ error.discordSubmit=Discord - Submit Support post
removeImage.title=Remove image removeImage.title=Remove image
removeImage.header=Remove image removeImage.header=Remove image
removeImage.removeImage=Remove image removeImage.removeImage=Remove image
removeImage.submit=Remove image removeImage.submit=Remove image

View File

@ -491,14 +491,14 @@ login.locked=Il tuo account è stato bloccato.
login.signinTitle=Per favore accedi login.signinTitle=Per favore accedi
login.ssoSignIn=Accedi tramite Single Sign-on login.ssoSignIn=Accedi tramite Single Sign-on
login.oauth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA login.oauth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. login.oauth2AdminBlockedUser=La registrazione o l'accesso degli utenti non registrati è attualmente bloccata. Si prega di contattare l'amministratore.
login.oauth2RequestNotFound=Richiesta di autorizzazione non trovata login.oauth2RequestNotFound=Richiesta di autorizzazione non trovata
login.oauth2InvalidUserInfoResponse=Risposta relativa alle informazioni utente non valida login.oauth2InvalidUserInfoResponse=Risposta relativa alle informazioni utente non valida
login.oauth2invalidRequest=Richiesta non valida login.oauth2invalidRequest=Richiesta non valida
login.oauth2AccessDenied=Accesso negato login.oauth2AccessDenied=Accesso negato
login.oauth2InvalidTokenResponse=Risposta token non valida login.oauth2InvalidTokenResponse=Risposta token non valida
login.oauth2InvalidIdToken=Id Token non valido login.oauth2InvalidIdToken=Id Token non valido
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=L'utente è disattivato, l'accesso è attualmente bloccato con questo nome utente. Si prega di contattare l'amministratore.
#auto-redact #auto-redact

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@
{ {
"moduleName": "com.bucket4j:bucket4j_jdk17-core", "moduleName": "com.bucket4j:bucket4j_jdk17-core",
"moduleUrl": "http://github.com/bucket4j/bucket4j/bucket4j_jdk17-core", "moduleUrl": "http://github.com/bucket4j/bucket4j/bucket4j_jdk17-core",
"moduleVersion": "8.13.1", "moduleVersion": "8.14.0",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0"
}, },
@ -1143,7 +1143,7 @@
{ {
"moduleName": "org.springframework:spring-webmvc", "moduleName": "org.springframework:spring-webmvc",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.1.12", "moduleVersion": "6.1.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },

View File

@ -28,6 +28,7 @@
<option value="gif">GIF</option> <option value="gif">GIF</option>
<option value="tiff">TIFF</option> <option value="tiff">TIFF</option>
<option value="bmp">BMP</option> <option value="bmp">BMP</option>
<option value="webp">WEPB</option>
</select> </select>
</div> </div>
<div class="mb-3"> <div class="mb-3">

View File

@ -12,7 +12,7 @@
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="es_ES"> <img th:src="@{'/images/flags/es.svg'}" alt="icon" width="20" height="15"> Español</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="es_ES"> <img th:src="@{'/images/flags/es.svg'}" alt="icon" width="20" height="15"> Español</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fr_FR"> <img th:src="@{'/images/flags/fr.svg'}" alt="icon" width="20" height="15"> Français</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fr_FR"> <img th:src="@{'/images/flags/fr.svg'}" alt="icon" width="20" height="15"> Français</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="id_ID"> <img th:src="@{'/images/flags/id.svg'}" alt="icon" width="20" height="15"> Indonesia</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="id_ID"> <img th:src="@{'/images/flags/id.svg'}" alt="icon" width="20" height="15"> Indonesia</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="id_ID"> <img th:src="@{'/images/flags/ie.svg'}" alt="icon" width="20" height="15"> Irish</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ga_IE"> <img th:src="@{'/images/flags/ie.svg'}" alt="icon" width="20" height="15"> Irish</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="it_IT"> <img th:src="@{'/images/flags/it.svg'}" alt="icon" width="20" height="15"> Italiano</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="it_IT"> <img th:src="@{'/images/flags/it.svg'}" alt="icon" width="20" height="15"> Italiano</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="nl_NL"> <img th:src="@{'/images/flags/nl.svg'}" alt="icon" width="20" height="15"> Nederlands</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="nl_NL"> <img th:src="@{'/images/flags/nl.svg'}" alt="icon" width="20" height="15"> Nederlands</a>
<a 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> <a 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>

View File

@ -335,7 +335,7 @@
</div> </div>
</div> </div>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link" href="#" id="searchDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link" href="#" id="searchDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">