Compare commits

...

29 Commits

Author SHA1 Message Date
pixeebot[bot]
72636dda9f Introduced protections against HTTP header injection / smuggling attacks 2024-11-29 14:41:02 +00:00
Anthony Stirling
329f755823 Merge branch 'main' into saml 2024-11-29 14:32:04 +00:00
Anthony Stirling
acc50e48c5 debugs 2024-11-29 14:31:15 +00:00
Anthony Stirling
92a571d31c saml stuff 2024-11-29 14:29:54 +00:00
Omar Ahmed Hassan
99d481d69f Fix Array.from syntax in nonmultiple file upload (#2357)
- Fix Array.from syntax in nonmultiple file upload as Array.from(<non-array or string>) returns an empty array which is the case when a file is selected from an input element (when multiple attribute isn't  supported) which can be found in Array.from(element.files[0]) -> results in an empty array.
2024-11-29 12:22:52 +00:00
Anthony Stirling
5976e69f54 debugs 2024-11-29 10:40:10 +00:00
Anthony Stirling
e588d8f99e spring dev fix for saml 2024-11-29 09:06:51 +00:00
Anthony Stirling
1c0f423510 remove unused repo 2024-11-29 08:59:29 +00:00
Anthony Stirling
2d6fe55985 info to debug 2024-11-29 08:53:54 +00:00
Anthony Stirling
5171088fca more fixes 2024-11-29 08:43:57 +00:00
Anthony Stirling
b4837df76c ee flag for saml 2024-11-28 19:41:39 +00:00
Anthony Stirling
d20e8f7d54 oauth to saml and compare fixes etc 2024-11-28 19:27:37 +00:00
Anthony Stirling
2885fac30d remove debugs 2024-11-28 15:43:24 +00:00
github-actions[bot]
a5ba6c403a 📝 Update README: Translation Progress Table (#2356)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-28 15:39:39 +00:00
Anthony Stirling
2a4a19a80f backup 2024-11-28 15:00:37 +00:00
albanobattistella
b2e6d89d16 Update messages_it_IT.properties (#2355) 2024-11-28 14:42:55 +00:00
github-actions[bot]
b59d2d15b4 Update translation files (#2354)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-28 14:42:29 +00:00
Omar Ahmed Hassan
61e750646c Feature: Undo Redo options multi tool #2297 (#2348)
* Implement Command class for Command Pattern

Created a base `Command` class to implement the **Command Pattern**. This class provides a skeletal implementation for `execute`, `undo`, and `redo` methods.

**Note:** This class is intended to be subclassed and not instantiated directly.

* Add undo/redo stacks and operations

* Use rotate element command to perform execute/undo/redo operations

* Handle commands executed through events
- Add "command-execution" event listener to execute commands that are not invoked from the same class while adding the command to the undo stack and clearing the redo stack.

* Add and use rotate all command to rotate/redo/undo all elements

* Use command pattern to delete pages

* Use command pattern for page selection

* Use command pattern to move pages up and down

* Use command pattern to remove selected pages

* Use command pattern to perform the splitting operation

* Add undo/redo functionality with filename input exclusion

- Implement undo (Ctrl+Z) and redo (Ctrl+Y) functionality.
- Prevent undo/redo actions when the filename input field is focused.
- Ensures proper handling of undo/redo actions without interfering with text editing.

* Introduce UndoManager for managing undo/redo operations

 - Encapsulate undo/redo stacks and operations within UndoManager.
- Simplify handling of undo/redo functionality through a dedicated manager.

* Call execute on splitAllCommand

- Fix a bug that caused split all functionality to not work as execute() wasn't called on splitAllCommand

* Add undo/redo buttons to multi tool

- Add undo/redo buttons to multi tool
- Dispatch an event upon state change (such as changes in the undo/redo stacks) to update the UI accordingly.

* Add undo/redo to translations

* Replace hard-coded "Undo"/"Redo" with translation keys in multi tool

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-28 14:25:13 +00:00
Omar Ahmed Hassan
de9c21b3de Fix: page break insertion functionality in Multi Tool (#2350)
Fix page break insertion functionality

- Page Break insertion functionality now successfully inserts page breaks upon request
2024-11-28 10:21:14 +00:00
Nureddin Farzaliyev
b32d6cb858 Azerbaijani Language Translation (#2347)
* Azerbaijani flag and dropdown item added

* Azerbaijani Language file Added

* AZ - ignore_translation.toml init

* AZ Translation Enterprise Edition Section

* Translation for Generic

* translation-az pipeline

* Translation for Analytics

* Translation for NAVBAR

* Translation for SETTINGS

* translation-az homepage

* Translation for #login

* Translation for (showJS)

* Translation for #showJS

* Translation for #PDFToWord

* Translation for #PDFToPresentation

* Translation for #PDFToText

* Translation for #PDFToHTML

* Translation for #PDFToXML

* Translation for #PDFToCSV

* Translation for #repair

* Translation for #pageLayout

* Translation for #pdfToSinglePage

* Translation for #pageExtracter

* Translation for #getPdfInfo

* Translation for #markdown-to-pdf

* Translation for #PDFToXML

* Translation for #html-to-pdf

* Translation for #PDFToHTML

* Translation for #PDFToText

* Translation for #PDFToPresentation

* Translation for #PDFToWord

* Translation for #PDFToCSV

* Translation for #url-to-pdf

* Translation for #pdfToImage

* Translation for #BookToPDF

* Translation for #PDFToBook

* Translation for #autoRedact

* Translation for #Add image

* Translation for #File to PDF

* Translation for (remove-image)

* Translation for (remove-image)

* Translation for (survey)

* Translation for (licenses)

* Translation for (printFile)

* Translation for (split-bysections)

* Translation for (overlay-pdfs)

* Translation for (split-by-size-or-count)

* Translation for (addPageNumbers)

* Translation for (adjustContrast)

* Translation for (autoSplitPDF)

* Translation for (scalePages)

* Translation for (removeCertSign)

* Translation for (removeAnnotations)

* Translation for (sign)

* Translation for (flatten)

* Translation for (extractImages)

* Translation for (merge)

* Translation for (view pdf)

* Translation for (pageRemover)

* Translation for (rotate)

* az Translation for replace-invert-color

* az Translation for addstamprequest

* Translation for (remove-image)

* Translation for #url-to-pdf

* Update messages_az_AZ.properties

* Update README.md

* Update README.md

* Update messages_az_AZ.properties

* Translation for #compress

* Translation for #Change permissions

* Translation for #remove password

* Translation for #pdfToPDFA

* Translation for #changeMetadata

* Translation for #merge

* Translation for #split-pdfs

* translation-az addpass

* translation-az watermark

* translation-az removeblanks

* translation-az compare

* translation-az certsign

* Translation for #pdfOrganiser

* Translation for #multiTool

* Translation for #multiTool

* az translation scannerimagesplit

* az translation ocr

* az translation fix

* az translation linebreak fix

* az translation linebreak fix

---------

Co-authored-by: Lucifer25x <lucifer25x@protonmail.com>
Co-authored-by: islamd7 <vusal04999@gmail.com>
Co-authored-by: Valida Rahmanova <validerehmanova04@gmail.com>
Co-authored-by: yusif043-bit <yusif.abbaszade.043@gmail.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-27 18:09:42 +00:00
pixeebot[bot]
d832a90de0 (CodeQL) Fixed finding: "Arbitrary file access during archive extraction ("Zip Slip")
" (#2344)

(CodeQL) Fixed finding: "Arbitrary file access during archive extraction ("Zip Slip")
"

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
2024-11-27 07:16:03 +00:00
Anthony Stirling
212e521238 Update MetricsAggregatorService.java 2024-11-26 21:30:47 +00:00
github-actions[bot]
0915e72a3d 📝 Update README: Translation Progress Table (#2341)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-26 20:52:54 +00:00
github-actions[bot]
ee5013651f Update 3rd Party Licenses (#2342)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-26 20:52:39 +00:00
github-actions[bot]
4aa44e6fc0 Update translation files (#2343)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-26 20:51:57 +00:00
github-actions[bot]
41c743a9f8 Update 3rd Party Licenses (#2337)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-26 20:51:02 +00:00
Anthony Stirling
833b3c45c6 Removal of Ghostscript to use qpdf and tesseract directly (#2338)
* navbar fix multi tool and compress location

* release notes and ghostscript removal

* cleanups

* formatting

* update docs

* more

* more

* docs

* release bump

* Hardening suggestions for Stirling-PDF / ghostscript (#2339)

* Protect `readLine()` against DoS

* Sanitized user-provided file names in HTTP multipart uploads

---------

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>

---------

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
2024-11-26 20:50:35 +00:00
Omar Ahmed Hassan
654bc94d44 Fix: input file overwrite in merge (#2335)
* Fix input files being overwritten by newly uploaded files

- Fix a bug that caused existing selected/uploaded files to be overwritten when a new input file is uploaded through input element.
- Add source property to change event to differentiate between uploaded files using input element and drag/drop uploads to avoid processing drag/drop files more than once, thus avoiding file duplication (file duplication resulting from copying drop/drop files to input files on each 'change' event).

* Dispatch and use file-input-change instead of change event for merging

- Dispatch "file-input-change" event after each "change" event in file upload, to notify other functions/components relying on the files provided by the \<input\> element.
- Use "file-input-change" instead of "change" event to display the latest version of uploaded files.

# FAQ:
- Why use "file-input-change" instead of "change" in merge.js?
= "change" event is automatically triggered when a file is uploaded through \<input\> element which would replace all the existing selected/uploaded files including the drag/drop files.

## Example:
Let's say that the user wants to upload/select the x.pdf, y.pdf and z.pdf all together:

- user selects "x.pdf" -> file selected successfully.
= selected files: x.pdf

- user drags and drops "y.pdf" -> file dropped successfully
= selected files: x.pdf, y.pdf

- user selects again using \<input\> "z.pdf" -> file selected succesfully overwriting selected files.
= selected files: z.pdf
2024-11-26 20:41:08 +00:00
dependabot[bot]
86fa404c90 Bump commons-io:commons-io from 2.17.0 to 2.18.0 (#2333)
Bumps commons-io:commons-io from 2.17.0 to 2.18.0.

---
updated-dependencies:
- dependency-name: commons-io:commons-io
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-26 20:38:51 +00:00
102 changed files with 2910 additions and 1259 deletions

View File

@@ -11,7 +11,7 @@ Stirling-PDF is built using:
- Spring Boot + Thymeleaf - Spring Boot + Thymeleaf
- PDFBox - PDFBox
- LibreOffice - LibreOffice
- OcrMyPdf - qpdf
- HTML, CSS, JavaScript - HTML, CSS, JavaScript
- Docker - Docker
- PDF.js - PDF.js
@@ -243,7 +243,7 @@ To run Stirling-PDF locally:
Important notes: Important notes:
- Local testing doesn't include features that depend on external tools like OCRmyPDF, LibreOffice, or Python scripts. - Local testing doesn't include features that depend on external tools like qpdf, LibreOffice, or Python scripts.
- There are currently no automated unit tests. All testing is done manually through the UI or API calls. (You are welcome to add JUnits!) - There are currently no automated unit tests. All testing is done manually through the UI or API calls. (You are welcome to add JUnits!)
- Always verify your changes in the full Docker environment before submitting pull requests, as some integrations and features will only work in the complete setup. - Always verify your changes in the full Docker environment before submitting pull requests, as some integrations and features will only work in the complete setup.

View File

@@ -30,6 +30,7 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
tini \ tini \
bash \ bash \
curl \ curl \
qpdf \
shadow \ shadow \
su-exec \ su-exec \
openssl \ openssl \
@@ -40,7 +41,6 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
# pdftohtml # pdftohtml
poppler-utils \ poppler-utils \
# OCR MY PDF (unpaper for descew and other advanced features) # OCR MY PDF (unpaper for descew and other advanced features)
ocrmypdf \
tesseract-ocr-data-eng \ tesseract-ocr-data-eng \
# CV # CV
py3-opencv \ py3-opencv \

View File

@@ -55,7 +55,7 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
# 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 featues)
ocrmypdf \ qpdf \
tesseract-ocr-data-eng \ tesseract-ocr-data-eng \
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \ font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
# CV # CV

View File

@@ -1,4 +1,4 @@
| Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | OCRmyPDF | Java | Javascript | Unoconv | Ghostscript | | Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | qpdf | Java | Javascript | Unoconv | tesseract |
| ------------------- | ------- | ------- | -------- | ----- | --- | ------ | ------ | ----------- | -------- | ---- | ---------- | ------- | ----------- | | ------------------- | ------- | ------- | -------- | ----- | --- | ------ | ------ | ----------- | -------- | ---- | ---------- | ------- | ----------- |
| adjust-contrast | ✔️ | | | | | | | | | | ✔️ | | | | adjust-contrast | ✔️ | | | | | | | | | | ✔️ | | |
| auto-split-pdf | ✔️ | | | | | | | | | ✔️ | | | | | auto-split-pdf | ✔️ | | | | | | | | | ✔️ | | | |
@@ -16,7 +16,7 @@
| 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 | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
| pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | | | | | pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
@@ -34,13 +34,13 @@
| auto-rename | | | | ✔️ | | | | | | ✔️ | | | | | auto-rename | | | | ✔️ | | | | | | ✔️ | | | |
| change-metadata | | | | ✔️ | | | | | | ✔️ | | | | | change-metadata | | | | ✔️ | | | | | | ✔️ | | | |
| compare | | | | ✔️ | | | | | | | ✔️ | | | | compare | | | | ✔️ | | | | | | | ✔️ | | |
| compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | ✔️ | | compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | |
| extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | | | extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
| extract-images | | | | ✔️ | | | | | | ✔️ | | | | | extract-images | | | | ✔️ | | | | | | ✔️ | | | |
| flatten | | | | ✔️ | | | | | | | ✔️ | | | | flatten | | | | ✔️ | | | | | | | ✔️ | | |
| get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | | | | | get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | | | |
| ocr-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | | | ocr-pdf | | | | ✔️ | ✔️ | | | | | | | | |
| remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | | | remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
| repair | | | | ✔️ | ✔️ | | | ✔️ | | | | | ✔️ | | repair | | | | ✔️ | ✔️ | | | ✔️ | | | | | |
| show-javascript | | | | ✔️ | | | | | | | ✔️ | | | | show-javascript | | | | ✔️ | | | | | | | ✔️ | | |
| sign | | | | ✔️ | | | | | | | ✔️ | | | | sign | | | | ✔️ | | | | | | | ✔️ | | |

View File

@@ -8,7 +8,7 @@ The paths have changed for the tessdata locations on new Docker images. Please u
## How does the OCR Work ## How does the OCR Work
Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF), which in turn uses Tesseract for its text recognition. All credit goes to them for this awesome work! Stirling-PDF uses Tesseract for its text recognition. All credit goes to them for this awesome work!
## Language Packs ## Language Packs
@@ -52,8 +52,6 @@ Add the following to your existing Docker run command:
### Non-Docker Setup ### Non-Docker Setup
If you are not using Docker, you need to install the OCR components, including the `ocrmypdf` app. You can see the [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html).
For Debian-based systems, install languages with this command: For Debian-based systems, install languages with this command:
```bash ```bash
@@ -83,8 +81,7 @@ rpm -qa | grep tesseract-langpack | sed 's/tesseract-langpack-//g'
For Windows: For Windows:
Ensure ocrmypdf in installed with You must ensure tesseract is installed
``pip install ocrmypdf``
Additional languages must be downloaded manually: Additional languages must be downloaded manually:
Download desired .traineddata files from tessdata or tessdata_fast Download desired .traineddata files from tessdata or tessdata_fast

View File

@@ -68,7 +68,7 @@ nix-env -iA nixpkgs.jbig2enc
### Step 3: Install Additional Software ### Step 3: Install Additional Software
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and OpenCV for pattern recognition functionality. Next we need to install LibreOffice for conversions, qpdf for OCR, and OpenCV for pattern recognition functionality.
Install the following software: Install the following software:
@@ -81,27 +81,27 @@ Install the following software:
- unoconv - unoconv
- pngquant - pngquant
- unpaper - unpaper
- ocrmypdf - qpdf
- opencv-python-headless - opencv-python-headless
For Debian-based systems, you can use the following command: For Debian-based systems, you can use the following command:
```bash ```bash
sudo apt-get install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf sudo apt-get install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper qpdf
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint --break-system-packages pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint --break-system-packages
``` ```
For Fedora: For Fedora:
```bash ```bash
sudo dnf install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf sudo dnf install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper qpdf
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
``` ```
For Nix: For Nix:
```bash ```bash
nix-env -iA nixpkgs.unpaper nixpkgs.libreoffice nixpkgs.ocrmypdf nixpkgs.poppler_utils nix-env -iA nixpkgs.unpaper nixpkgs.libreoffice nixpkgs.qpdf nixpkgs.poppler_utils
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
``` ```
@@ -146,7 +146,6 @@ The easiest method is to use the language packs provided by your repositories. S
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need. 1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata` 2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
3. Please view [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for more info.
**IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED. **IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED.

View File

@@ -79,15 +79,15 @@ All files and PDFs exist either exclusively on the client side, reside in server
- Detect and remove blank pages - Detect and remove blank pages
- Compare two PDFs and show differences in text - Compare two PDFs and show differences in text
- Add images to PDFs - Add images to PDFs
- Compress PDFs to decrease their filesize (using OCRMyPDF) - Compress PDFs to decrease their filesize (using qpdf)
- Extract images from PDF - Extract images from PDF
- Remove images from PDF - Remove images from PDF
- Extract images from scans - Extract images from scans
- Remove annotations - Remove annotations
- Add page numbers - Add page numbers
- Auto rename file by detecting PDF header text - Auto rename file by detecting PDF header text
- OCR on PDF (using OCRMyPDF) - OCR on PDF (using tesseract)
- PDF/A conversion (using OCRMyPDF) - PDF/A conversion (using libreoffice)
- Edit metadata - Edit metadata
- Flatten PDFs - Flatten PDFs
- Get all information on a PDF to view or export as JSON - Get all information on a PDF to view or export as JSON
@@ -102,7 +102,7 @@ A demo of the app is available [here](https://stirlingpdf.io).
- Spring Boot + Thymeleaf - Spring Boot + Thymeleaf
- [PDFBox](https://github.com/apache/pdfbox/tree/trunk) - [PDFBox](https://github.com/apache/pdfbox/tree/trunk)
- [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions - [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions
- [OcrMyPdf](https://github.com/ocrmypdf/OCRmyPDF) - [qpdf](https://github.com/qpdf/qpdf)
- HTML, CSS, JavaScript - HTML, CSS, JavaScript
- Docker - Docker
- [PDF.js](https://github.com/mozilla/pdf.js) - [PDF.js](https://github.com/mozilla/pdf.js)
@@ -191,43 +191,43 @@ Stirling-PDF currently supports 37 languages!
| Language | Progress | | Language | Progress |
| -------------------------------------------- | -------------------------------------- | | -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![99%](https://geps.dev/progress/99) | | Arabic (العربية) (ar_AR) | ![98%](https://geps.dev/progress/98) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![76%](https://geps.dev/progress/76) | | Azerbaijani (Azərbaycan Dili) (az_AZ) | ![97%](https://geps.dev/progress/97) |
| Basque (Euskara) (eu_ES) | ![54%](https://geps.dev/progress/54) | | Basque (Euskara) (eu_ES) | ![54%](https://geps.dev/progress/54) |
| Bulgarian (Български) (bg_BG) | ![95%](https://geps.dev/progress/95) | | Bulgarian (Български) (bg_BG) | ![94%](https://geps.dev/progress/94) |
| Catalan (Català) (ca_CA) | ![89%](https://geps.dev/progress/89) | | Catalan (Català) (ca_CA) | ![88%](https://geps.dev/progress/88) |
| Croatian (Hrvatski) (hr_HR) | ![96%](https://geps.dev/progress/96) | | Croatian (Hrvatski) (hr_HR) | ![96%](https://geps.dev/progress/96) |
| Czech (Česky) (cs_CZ) | ![96%](https://geps.dev/progress/96) | | Czech (Česky) (cs_CZ) | ![95%](https://geps.dev/progress/95) |
| Danish (Dansk) (da_DK) | ![95%](https://geps.dev/progress/95) | | Danish (Dansk) (da_DK) | ![94%](https://geps.dev/progress/94) |
| Dutch (Nederlands) (nl_NL) | ![94%](https://geps.dev/progress/94) | | 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) | ![98%](https://geps.dev/progress/98) | | French (Français) (fr_FR) | ![97%](https://geps.dev/progress/97) |
| German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) | | German (Deutsch) (de_DE) | ![97%](https://geps.dev/progress/97) |
| Greek (Ελληνικά) (el_GR) | ![96%](https://geps.dev/progress/96) | | Greek (Ελληνικά) (el_GR) | ![95%](https://geps.dev/progress/95) |
| Hindi (हिंदी) (hi_IN) | ![93%](https://geps.dev/progress/93) | | Hindi (हिंदी) (hi_IN) | ![93%](https://geps.dev/progress/93) |
| Hungarian (Magyar) (hu_HU) | ![96%](https://geps.dev/progress/96) | | Hungarian (Magyar) (hu_HU) | ![95%](https://geps.dev/progress/95) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![96%](https://geps.dev/progress/96) | | Indonesian (Bahasa Indonesia) (id_ID) | ![95%](https://geps.dev/progress/95) |
| Irish (Gaeilge) (ga_IE) | ![86%](https://geps.dev/progress/86) | | Irish (Gaeilge) (ga_IE) | ![86%](https://geps.dev/progress/86) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | | Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Japanese (日本語) (ja_JP) | ![84%](https://geps.dev/progress/84) | | Japanese (日本語) (ja_JP) | ![83%](https://geps.dev/progress/83) |
| Korean (한국어) (ko_KR) | ![94%](https://geps.dev/progress/94) | | Korean (한국어) (ko_KR) | ![93%](https://geps.dev/progress/93) |
| Norwegian (Norsk) (no_NB) | ![86%](https://geps.dev/progress/86) | | Norwegian (Norsk) (no_NB) | ![85%](https://geps.dev/progress/85) |
| Polish (Polski) (pl_PL) | ![95%](https://geps.dev/progress/95) | | Polish (Polski) (pl_PL) | ![94%](https://geps.dev/progress/94) |
| Portuguese (Português) (pt_PT) | ![96%](https://geps.dev/progress/96) | | Portuguese (Português) (pt_PT) | ![95%](https://geps.dev/progress/95) |
| Portuguese Brazilian (Português) (pt_BR) | ![96%](https://geps.dev/progress/96) | | Portuguese Brazilian (Português) (pt_BR) | ![96%](https://geps.dev/progress/96) |
| Romanian (Română) (ro_RO) | ![89%](https://geps.dev/progress/89) | | Romanian (Română) (ro_RO) | ![88%](https://geps.dev/progress/88) |
| Russian (Русский) (ru_RU) | ![95%](https://geps.dev/progress/95) | | Russian (Русский) (ru_RU) | ![95%](https://geps.dev/progress/95) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![69%](https://geps.dev/progress/69) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![68%](https://geps.dev/progress/68) |
| Simplified Chinese (简体中文) (zh_CN) | ![90%](https://geps.dev/progress/90) | | Simplified Chinese (简体中文) (zh_CN) | ![89%](https://geps.dev/progress/89) |
| Slovakian (Slovensky) (sk_SK) | ![81%](https://geps.dev/progress/81) | | Slovakian (Slovensky) (sk_SK) | ![80%](https://geps.dev/progress/80) |
| Spanish (Español) (es_ES) | ![96%](https://geps.dev/progress/96) | | Spanish (Español) (es_ES) | ![96%](https://geps.dev/progress/96) |
| Swedish (Svenska) (sv_SE) | ![95%](https://geps.dev/progress/95) | | Swedish (Svenska) (sv_SE) | ![94%](https://geps.dev/progress/94) |
| Thai (ไทย) (th_TH) | ![94%](https://geps.dev/progress/94) | | Thai (ไทย) (th_TH) | ![94%](https://geps.dev/progress/94) |
| 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) | ![90%](https://geps.dev/progress/90) | | Turkish (Türkçe) (tr_TR) | ![90%](https://geps.dev/progress/90) |
| Ukrainian (Українська) (uk_UA) | ![79%](https://geps.dev/progress/79) | | Ukrainian (Українська) (uk_UA) | ![78%](https://geps.dev/progress/78) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![87%](https://geps.dev/progress/87) | | Vietnamese (Tiếng Việt) (vi_VN) | ![86%](https://geps.dev/progress/86) |
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.) ## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)

View File

@@ -8,7 +8,7 @@ The 'Fat' container contains all those found in 'Full' with security jar along w
| Libre | | ✔️ | | Libre | | ✔️ |
| Python | | ✔️ | | Python | | ✔️ |
| OpenCV | | ✔️ | | OpenCV | | ✔️ |
| OCRmyPDF | | ✔️ | | qpdf | | ✔️ |
| Operation | Ultra-Lite | Full | | Operation | Ultra-Lite | Full |
| ---------------------- | ---------- | ---- | | ---------------------- | ---------- | ---- |

View File

@@ -21,10 +21,13 @@ ext {
imageioVersion = "3.12.0" imageioVersion = "3.12.0"
lombokVersion = "1.18.36" lombokVersion = "1.18.36"
bouncycastleVersion = "1.79" bouncycastleVersion = "1.79"
springSecuritySamlVersion = "6.4.1"
openSamlVersion = "4.3.2"
} }
group = "stirling.software" group = "stirling.software"
version = "0.34.0" version = "0.35.0"
java { java {
// 17 is lowest but we support and recommend 21 // 17 is lowest but we support and recommend 21
@@ -143,17 +146,18 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
implementation 'org.springframework.security:spring-security-saml2-service-provider:6.4.1' implementation "org.springframework.session:spring-session-core:$springBootVersion"
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5' implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
// Don't upgrade h2database // Don't upgrade h2database
runtimeOnly "com.h2database:h2:2.3.232" runtimeOnly "com.h2database:h2:2.3.232"
constraints { constraints {
implementation "org.opensaml:opensaml-core" implementation "org.opensaml:opensaml-core:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-api" implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-impl" implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
} }
implementation "org.springframework.security:spring-security-saml2-service-provider" implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
implementation 'com.coveo:saml-client:5.0.0' implementation 'com.coveo:saml-client:5.0.0'
@@ -185,7 +189,7 @@ dependencies {
// Image metadata extractor // Image metadata extractor
implementation "com.drewnoakes:metadata-extractor:2.19.0" implementation "com.drewnoakes:metadata-extractor:2.19.0"
implementation "commons-io:commons-io:2.17.0" implementation "commons-io:commons-io:2.18.0"
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0" implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
//general PDF //general PDF

View File

@@ -48,24 +48,6 @@ Feature: API Validation
And the response status code should be 200 And the response status code should be 200
@ocr @negative
Scenario: Process PDF with text and OCR with type normal
Given I generate a PDF file as "fileInput"
And the pdf contains 3 pages with random text
And the request data includes
| parameter | value |
| languages | eng |
| sidecar | false |
| deskew | true |
| clean | true |
| cleanFinal | true |
| ocrType | Normal |
| ocrRenderType | hocr |
| removeImagesAfter| false |
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
Then the response status code should be 500
@ocr @positive @ocr @positive
Scenario: Process PDF with OCR Scenario: Process PDF with OCR
Given I generate a PDF file as "fileInput" Given I generate a PDF file as "fileInput"
@@ -84,26 +66,6 @@ Feature: API Validation
And the response file should have size greater than 0 And the response file should have size greater than 0
And the response status code should be 200 And the response status code should be 200
@ocr @positive
Scenario: Process PDF with OCR with sidecar
Given I generate a PDF file as "fileInput"
And the request data includes
| parameter | value |
| languages | eng |
| sidecar | true |
| deskew | true |
| clean | true |
| cleanFinal | true |
| ocrType | Force |
| ocrRenderType | hocr |
| removeImagesAfter| false |
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
Then the response content type should be "application/octet-stream"
And the response file should have extension ".zip"
And the response ZIP should contain 2 files
And the response file should have size greater than 0
And the response status code should be 200
@libre @positive @libre @positive
Scenario Outline: Convert PDF to various word formats Scenario Outline: Convert PDF to various word formats
@@ -145,7 +107,7 @@ Feature: API Validation
And the response file should have extension ".pdf" And the response file should have extension ".pdf"
And the response file should have size greater than 100 And the response file should have size greater than 100
@compress @ghostscript @positive @compress @qpdf @positive
Scenario: Compress Scenario: Compress
Given I use an example file at "exampleFiles/ghost3.pdf" as parameter "fileInput" Given I use an example file at "exampleFiles/ghost3.pdf" as parameter "fileInput"
And the request data includes And the request data includes
@@ -156,7 +118,7 @@ Feature: API Validation
And the response file should have extension ".pdf" And the response file should have extension ".pdf"
And the response file should have size greater than 100 And the response file should have size greater than 100
@compress @ghostscript @positive @compress @qpdf @positive
Scenario: Compress Scenario: Compress
Given I use an example file at "exampleFiles/ghost2.pdf" as parameter "fileInput" Given I use an example file at "exampleFiles/ghost2.pdf" as parameter "fileInput"
And the request data includes And the request data includes
@@ -169,7 +131,7 @@ Feature: API Validation
And the response file should have size greater than 100 And the response file should have size greater than 100
@compress @ghostscript @positive @compress @qpdf @positive
Scenario: Compress Scenario: Compress
Given I use an example file at "exampleFiles/ghost1.pdf" as parameter "fileInput" Given I use an example file at "exampleFiles/ghost1.pdf" as parameter "fileInput"
And the request data includes And the request data includes

View File

@@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
translation_key="pdfToPDFA.credit" translation_key="pdfToPDFA.credit"
old_value="OCRmyPDF" old_value="qpdf"
new_value="ghostscript" new_value="liibreoffice"
for file in ../src/main/resources/messages_*.properties; do for file in ../src/main/resources/messages_*.properties; do
sed -i "/^$translation_key=/s/$old_value/$new_value/" "$file" sed -i "/^$translation_key=/s/$old_value/$new_value/" "$file"

View File

@@ -3,13 +3,14 @@ package stirling.software.SPDF.EE;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Configuration @Configuration
@Lazy @Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j @Slf4j
public class EEAppConfig { public class EEAppConfig {

View File

@@ -25,9 +25,10 @@ public class LicenseKeyChecker {
KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) { KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) {
this.licenseService = licenseService; this.licenseService = licenseService;
this.applicationProperties = applicationProperties; this.applicationProperties = applicationProperties;
this.checkLicense();
} }
@Scheduled(fixedRate = 604800000, initialDelay = 1000) // 7 days in milliseconds @Scheduled(fixedRate = 604800000) // 7 days in milliseconds
public void checkLicensePeriodically() { public void checkLicensePeriodically() {
checkLicense(); checkLicense();
} }

View File

@@ -188,7 +188,7 @@ public class EndpointConfiguration {
addEndpointToGroup("OpenCV", "extract-image-scans"); addEndpointToGroup("OpenCV", "extract-image-scans");
// LibreOffice // LibreOffice
addEndpointToGroup("LibreOffice", "repair"); addEndpointToGroup("qpdf", "repair");
addEndpointToGroup("LibreOffice", "file-to-pdf"); addEndpointToGroup("LibreOffice", "file-to-pdf");
addEndpointToGroup("LibreOffice", "pdf-to-word"); addEndpointToGroup("LibreOffice", "pdf-to-word");
addEndpointToGroup("LibreOffice", "pdf-to-presentation"); addEndpointToGroup("LibreOffice", "pdf-to-presentation");
@@ -199,10 +199,11 @@ public class EndpointConfiguration {
// Unoconv // Unoconv
addEndpointToGroup("Unoconv", "file-to-pdf"); addEndpointToGroup("Unoconv", "file-to-pdf");
// OCRmyPDF // qpdf
addEndpointToGroup("OCRmyPDF", "compress-pdf"); addEndpointToGroup("qpdf", "compress-pdf");
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa"); addEndpointToGroup("qpdf", "pdf-to-pdfa");
addEndpointToGroup("OCRmyPDF", "ocr-pdf");
addEndpointToGroup("tesseract", "ocr-pdf");
// Java // Java
addEndpointToGroup("Java", "merge-pdfs"); addEndpointToGroup("Java", "merge-pdfs");
@@ -248,10 +249,10 @@ public class EndpointConfiguration {
addEndpointToGroup("Javascript", "compare"); addEndpointToGroup("Javascript", "compare");
addEndpointToGroup("Javascript", "adjust-contrast"); addEndpointToGroup("Javascript", "adjust-contrast");
// Ghostscript dependent endpoints // qpdf dependent endpoints
addEndpointToGroup("Ghostscript", "compress-pdf"); addEndpointToGroup("qpdf", "compress-pdf");
addEndpointToGroup("Ghostscript", "pdf-to-pdfa"); addEndpointToGroup("qpdf", "pdf-to-pdfa");
addEndpointToGroup("Ghostscript", "repair"); addEndpointToGroup("qpdf", "repair");
// Weasyprint dependent endpoints // Weasyprint dependent endpoints
addEndpointToGroup("Weasyprint", "html-to-pdf"); addEndpointToGroup("Weasyprint", "html-to-pdf");

View File

@@ -37,12 +37,12 @@ public class ExternalAppDepConfig {
private final Map<String, List<String>> commandToGroupMapping = private final Map<String, List<String>> commandToGroupMapping =
new HashMap<>() { new HashMap<>() {
{ {
put("gs", List.of("Ghostscript"));
put("soffice", List.of("LibreOffice")); put("soffice", List.of("LibreOffice"));
put("ocrmypdf", List.of("OCRmyPDF"));
put("weasyprint", List.of("Weasyprint")); put("weasyprint", List.of("Weasyprint"));
put("pdftohtml", List.of("Pdftohtml")); put("pdftohtml", List.of("Pdftohtml"));
put("unoconv", List.of("Unoconv")); put("unoconv", List.of("Unoconv"));
put("qpdf", List.of("qpdf"));
put("tesseract", List.of("tesseract"));
} }
}; };
@@ -97,9 +97,9 @@ public class ExternalAppDepConfig {
public void checkDependencies() { public void checkDependencies() {
// Check core dependencies // Check core dependencies
checkDependencyAndDisableGroup("gs"); checkDependencyAndDisableGroup("tesseract");
checkDependencyAndDisableGroup("soffice"); checkDependencyAndDisableGroup("soffice");
checkDependencyAndDisableGroup("ocrmypdf"); checkDependencyAndDisableGroup("qpdf");
checkDependencyAndDisableGroup("weasyprint"); checkDependencyAndDisableGroup("weasyprint");
checkDependencyAndDisableGroup("pdftohtml"); checkDependencyAndDisableGroup("pdftohtml");
checkDependencyAndDisableGroup("unoconv"); checkDependencyAndDisableGroup("unoconv");

View File

@@ -30,6 +30,7 @@ public class InitialSecuritySetup {
initializeAdminUser(); initializeAdminUser();
} else { } else {
databaseBackupHelper.exportDatabase(); databaseBackupHelper.exportDatabase();
userService.migrateOauth2ToSSO();
} }
initializeInternalApiUser(); initializeInternalApiUser();
} }

View File

@@ -1,16 +1,21 @@
package stirling.software.SPDF.config.security; package stirling.software.SPDF.config.security;
import io.github.pixee.security.Newlines;
import java.io.IOException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.*; import java.util.*;
import org.opensaml.saml.saml2.core.AuthnRequest;
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;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -28,19 +33,35 @@ import org.springframework.security.oauth2.client.registration.InMemoryClientReg
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType; import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider; import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
@@ -64,6 +85,7 @@ import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
@EnableWebSecurity @EnableWebSecurity
@EnableMethodSecurity @EnableMethodSecurity
@Slf4j @Slf4j
@DependsOn("runningEE")
public class SecurityConfiguration { public class SecurityConfiguration {
@Autowired private CustomUserDetailsService userDetailsService; @Autowired private CustomUserDetailsService userDetailsService;
@@ -79,6 +101,10 @@ public class SecurityConfiguration {
@Qualifier("loginEnabled") @Qualifier("loginEnabled")
public boolean loginEnabledValue; public boolean loginEnabledValue;
@Autowired
@Qualifier("runningEE")
public boolean runningEE;
@Autowired ApplicationProperties applicationProperties; @Autowired ApplicationProperties applicationProperties;
@Autowired private UserAuthenticationFilter userAuthenticationFilter; @Autowired private UserAuthenticationFilter userAuthenticationFilter;
@@ -90,13 +116,14 @@ public class SecurityConfiguration {
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
if (applicationProperties.getSecurity().getCsrfDisabled()) {
http.csrf(csrf -> csrf.disable());
}
if (loginEnabledValue) { if (loginEnabledValue) {
http.addFilterBefore( http.addFilterBefore(
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
if (applicationProperties.getSecurity().getCsrfDisabled()) { if (!applicationProperties.getSecurity().getCsrfDisabled()) {
http.csrf(csrf -> csrf.disable());
} else {
CookieCsrfTokenRepository cookieRepo = CookieCsrfTokenRepository cookieRepo =
CookieCsrfTokenRepository.withHttpOnlyFalse(); CookieCsrfTokenRepository.withHttpOnlyFalse();
CsrfTokenRequestAttributeHandler requestHandler = CsrfTokenRequestAttributeHandler requestHandler =
@@ -137,11 +164,30 @@ public class SecurityConfiguration {
http.sessionManagement( http.sessionManagement(
sessionManagement -> sessionManagement ->
sessionManagement sessionManagement
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(10) .maximumSessions(10)
.maxSessionsPreventsLogin(false) .maxSessionsPreventsLogin(false)
.sessionRegistry(sessionRegistry) .sessionRegistry(sessionRegistry)
.expiredUrl("/login?logout=true"));
.expiredUrl("/login?logout=true"))
.addFilterBefore(
new ForceEagerSessionCreationFilter(),
SecurityContextHolderFilter.class)
.addFilterBefore(new ForceEagerSessionCreationFilter(), SecurityContextHolderFilter.class);
http.addFilterBefore(new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request.getRequestURI().startsWith("/saml2")) {
response.setHeader("Set-Cookie",
Newlines.stripAll(response.getHeader("Set-Cookie")
.concat(";SameSite=None;Secure")));
}
filterChain.doFilter(request, response);
}
}, SessionManagementFilter.class);
http.authenticationProvider(daoAuthenticationProvider()); http.authenticationProvider(daoAuthenticationProvider());
http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())); http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()));
@@ -245,12 +291,23 @@ public class SecurityConfiguration {
} }
// Handle SAML // Handle SAML
if (applicationProperties.getSecurity().isSaml2Activ() if (applicationProperties.getSecurity().isSaml2Activ()) { // && runningEE
&& applicationProperties.getSystem().getEnableAlphaFunctionality()) { // Configure the authentication provider
http.authenticationProvider(samlAuthenticationProvider()); OpenSaml4AuthenticationProvider authenticationProvider =
http.saml2Login( new OpenSaml4AuthenticationProvider();
saml2 -> authenticationProvider.setResponseAuthenticationConverter(
new CustomSaml2ResponseAuthenticationConverter(userService));
http.authenticationProvider(authenticationProvider)
.saml2Login(
saml2 -> {
try {
saml2.loginPage("/saml2") saml2.loginPage("/saml2")
.relyingPartyRegistrationRepository(
relyingPartyRegistrations())
.authenticationManager(
new ProviderManager(authenticationProvider))
.successHandler( .successHandler(
new CustomSaml2AuthenticationSuccessHandler( new CustomSaml2AuthenticationSuccessHandler(
loginAttemptService, loginAttemptService,
@@ -258,14 +315,18 @@ public class SecurityConfiguration {
userService)) userService))
.failureHandler( .failureHandler(
new CustomSaml2AuthenticationFailureHandler()) new CustomSaml2AuthenticationFailureHandler())
.permitAll()) .authenticationRequestResolver(
.addFilterBefore( authenticationRequestResolver(
userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class); relyingPartyRegistrations()));
} catch (Exception e) {
log.error("Error configuring SAML2 login", e);
throw new RuntimeException(e);
} }
});
}
} else { } else {
if (applicationProperties.getSecurity().getCsrfDisabled()) { if (!applicationProperties.getSecurity().getCsrfDisabled()) {
http.csrf(csrf -> csrf.disable());
} else {
CookieCsrfTokenRepository cookieRepo = CookieCsrfTokenRepository cookieRepo =
CookieCsrfTokenRepository.withHttpOnlyFalse(); CookieCsrfTokenRepository.withHttpOnlyFalse();
CsrfTokenRequestAttributeHandler requestHandler = CsrfTokenRequestAttributeHandler requestHandler =
@@ -282,20 +343,6 @@ public class SecurityConfiguration {
return http.build(); return http.build();
} }
@Bean
@ConditionalOnProperty(
name = "security.saml2.enabled",
havingValue = "true",
matchIfMissing = false)
public AuthenticationProvider samlAuthenticationProvider() {
OpenSaml4AuthenticationProvider authenticationProvider =
new OpenSaml4AuthenticationProvider();
authenticationProvider.setResponseAuthenticationConverter(
new CustomSaml2ResponseAuthenticationConverter(userService));
return authenticationProvider;
}
// Client Registration Repository for OAUTH2 OIDC Login
@Bean @Bean
@ConditionalOnProperty( @ConditionalOnProperty(
value = "security.oauth2.enabled", value = "security.oauth2.enabled",
@@ -426,17 +473,31 @@ public class SecurityConfiguration {
.build()); .build());
} }
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite("None");
serializer.setUseSecureCookie(true); // Required when using SameSite=None
return serializer;
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean @Bean
@ConditionalOnProperty( @ConditionalOnProperty(
name = "security.saml2.enabled", name = "security.saml2.enabled",
havingValue = "true", havingValue = "true",
matchIfMissing = false) matchIfMissing = false)
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception { public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
SAML2 samlConf = applicationProperties.getSecurity().getSaml2(); SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
Resource privateKeyResource = samlConf.getPrivateKey(); X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
Resource privateKeyResource = samlConf.getPrivateKey();
Resource certificateResource = samlConf.getSpCert(); Resource certificateResource = samlConf.getSpCert();
Saml2X509Credential signingCredential = Saml2X509Credential signingCredential =
@@ -445,26 +506,97 @@ public class SecurityConfiguration {
CertificateUtils.readCertificate(certificateResource), CertificateUtils.readCertificate(certificateResource),
Saml2X509CredentialType.SIGNING); Saml2X509CredentialType.SIGNING);
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
RelyingPartyRegistration rp = RelyingPartyRegistration rp =
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId()) RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
.signingX509Credentials((c) -> c.add(signingCredential)) .signingX509Credentials(c -> c.add(signingCredential))
.assertingPartyMetadata( .assertingPartyMetadata(
(details) -> metadata ->
details.entityId(samlConf.getIdpIssuer()) metadata.entityId(samlConf.getIdpIssuer())
.singleSignOnServiceLocation( .singleSignOnServiceLocation(
samlConf.getIdpSingleLoginUrl()) samlConf.getIdpSingleLoginUrl())
.verificationX509Credentials( .verificationX509Credentials(
(c) -> c.add(verificationCredential)) c -> c.add(verificationCredential))
.singleSignOnServiceBinding(
Saml2MessageBinding.POST)
.wantAuthnRequestsSigned(true)) .wantAuthnRequestsSigned(true))
.build(); .build();
return new InMemoryRelyingPartyRegistrationRepository(rp); return new InMemoryRelyingPartyRegistrationRepository(rp);
} }
@Bean @Bean
@ConditionalOnProperty(
name = "security.saml2.enabled",
havingValue = "true",
matchIfMissing = false)
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
OpenSaml4AuthenticationRequestResolver resolver =
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
resolver.setAuthnRequestCustomizer(
customizer -> {
log.debug("Customizing SAML Authentication request");
AuthnRequest authnRequest = customizer.getAuthnRequest();
log.debug("AuthnRequest ID: {}", authnRequest.getID());
if (authnRequest.getID() == null) {
authnRequest.setID("ARQ" + UUID.randomUUID().toString());
}
log.debug("AuthnRequest new ID after set: {}", authnRequest.getID());
log.debug("AuthnRequest IssueInstant: {}", authnRequest.getIssueInstant());
log.debug(
"AuthnRequest Issuer: {}",
authnRequest.getIssuer() != null
? authnRequest.getIssuer().getValue()
: "null");
HttpServletRequest request = customizer.getRequest();
// Log HTTP request details
log.debug("HTTP Request Method: {}", request.getMethod());
log.debug("Request URI: {}", request.getRequestURI());
log.debug("Request URL: {}", request.getRequestURL().toString());
log.debug("Query String: {}", request.getQueryString());
log.debug("Remote Address: {}", request.getRemoteAddr());
// Log headers
Collections.list(request.getHeaderNames())
.forEach(
headerName -> {
log.debug(
"Header - {}: {}",
headerName,
request.getHeader(headerName));
});
// Log SAML specific parameters
log.debug("SAML Request Parameters:");
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
log.debug("RelayState: {}", request.getParameter("RelayState"));
// Log session debugrmation if exists
if (request.getSession(false) != null) {
log.debug("Session ID: {}", request.getSession().getId());
}
// Log any assertions consumer service details if present
if (authnRequest.getAssertionConsumerServiceURL() != null) {
log.debug(
"AssertionConsumerServiceURL: {}",
authnRequest.getAssertionConsumerServiceURL());
}
// Log NameID policy if present
if (authnRequest.getNameIDPolicy() != null) {
log.debug(
"NameIDPolicy Format: {}",
authnRequest.getNameIDPolicy().getFormat());
}
});
return resolver;
}
public DaoAuthenticationProvider daoAuthenticationProvider() { public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService); provider.setUserDetailsService(userDetailsService);

View File

@@ -18,6 +18,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder; 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 org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface; import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
@@ -50,8 +51,19 @@ public class UserService implements UserServiceInterface {
@Autowired ApplicationProperties applicationProperties; @Autowired ApplicationProperties applicationProperties;
@Transactional
public void migrateOauth2ToSSO() {
userRepository
.findByAuthenticationTypeIgnoreCase("OAUTH2")
.forEach(
user -> {
user.setAuthenticationType(AuthenticationType.SSO);
userRepository.save(user);
});
}
// Handle OAUTH2 login and user auto creation. // Handle OAUTH2 login and user auto creation.
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser) public boolean processSSOPostLogin(String username, boolean autoCreateUser)
throws IllegalArgumentException, IOException { throws IllegalArgumentException, IOException {
if (!isUsernameValid(username)) { if (!isUsernameValid(username)) {
return false; return false;
@@ -61,7 +73,7 @@ public class UserService implements UserServiceInterface {
return true; return true;
} }
if (autoCreateUser) { if (autoCreateUser) {
saveUser(username, AuthenticationType.OAUTH2); saveUser(username, AuthenticationType.SSO);
return true; return true;
} }
return false; return false;

View File

@@ -82,8 +82,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
} }
if (userService.usernameExistsIgnoreCase(username) if (userService.usernameExistsIgnoreCase(username)
&& userService.hasPassword(username) && userService.hasPassword(username)
&& !userService.isAuthenticationTypeByUsername( && !userService.isAuthenticationTypeByUsername(username, AuthenticationType.SSO)
username, AuthenticationType.OAUTH2)
&& oAuth.getAutoCreateUser()) { && oAuth.getAutoCreateUser()) {
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true"); response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
return; return;
@@ -95,7 +94,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
return; return;
} }
if (principal instanceof OAuth2User) { if (principal instanceof OAuth2User) {
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser()); userService.processSSOPostLogin(username, oAuth.getAutoCreateUser());
} }
response.sendRedirect(contextPath + "/"); response.sendRedirect(contextPath + "/");
return; return;

View File

@@ -3,12 +3,14 @@ package stirling.software.SPDF.config.security.saml2;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader; import org.bouncycastle.util.io.pem.PemReader;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@@ -28,15 +30,26 @@ public class CertificateUtils {
} }
public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception { public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception {
try (PemReader pemReader = try (PEMParser pemParser =
new PemReader( new PEMParser(
new InputStreamReader( new InputStreamReader(
privateKeyResource.getInputStream(), StandardCharsets.UTF_8))) { privateKeyResource.getInputStream(), StandardCharsets.UTF_8))) {
PemObject pemObject = pemReader.readPemObject();
byte[] decodedKey = pemObject.getContent(); Object object = pemParser.readObject();
return (RSAPrivateKey) JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(decodedKey)); if (object instanceof PEMKeyPair) {
// Handle traditional RSA private key format
PEMKeyPair keypair = (PEMKeyPair) object;
return (RSAPrivateKey) converter.getPrivateKey(keypair.getPrivateKeyInfo());
} else if (object instanceof PrivateKeyInfo) {
// Handle PKCS#8 format
return (RSAPrivateKey) converter.getPrivateKey((PrivateKeyInfo) object);
} else {
throw new IllegalArgumentException(
"Unsupported key format: "
+ (object != null ? object.getClass().getName() : "null"));
}
} }
} }
} }

View File

@@ -12,6 +12,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.LoginAttemptService; import stirling.software.SPDF.config.security.LoginAttemptService;
import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@@ -20,11 +21,11 @@ import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.utils.RequestUriUtils; import stirling.software.SPDF.utils.RequestUriUtils;
@AllArgsConstructor @AllArgsConstructor
@Slf4j
public class CustomSaml2AuthenticationSuccessHandler public class CustomSaml2AuthenticationSuccessHandler
extends SavedRequestAwareAuthenticationSuccessHandler { extends SavedRequestAwareAuthenticationSuccessHandler {
private LoginAttemptService loginAttemptService; private LoginAttemptService loginAttemptService;
private ApplicationProperties applicationProperties; private ApplicationProperties applicationProperties;
private UserService userService; private UserService userService;
@@ -34,10 +35,12 @@ public class CustomSaml2AuthenticationSuccessHandler
throws ServletException, IOException { throws ServletException, IOException {
Object principal = authentication.getPrincipal(); Object principal = authentication.getPrincipal();
log.debug("Starting SAML2 authentication success handling");
if (principal instanceof CustomSaml2AuthenticatedPrincipal) { if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
// Get the saved request log.debug("Authenticated principal found for user: {}", username);
HttpSession session = request.getSession(false); HttpSession session = request.getSession(false);
String contextPath = request.getContextPath(); String contextPath = request.getContextPath();
SavedRequest savedRequest = SavedRequest savedRequest =
@@ -45,46 +48,77 @@ public class CustomSaml2AuthenticationSuccessHandler
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
: null; : null;
log.debug(
"Session exists: {}, Saved request exists: {}",
session != null,
savedRequest != null);
if (savedRequest != null if (savedRequest != null
&& !RequestUriUtils.isStaticResource( && !RequestUriUtils.isStaticResource(
contextPath, savedRequest.getRedirectUrl())) { contextPath, savedRequest.getRedirectUrl())) {
// Redirect to the original destination log.debug(
"Valid saved request found, redirecting to original destination: {}",
savedRequest.getRedirectUrl());
super.onAuthenticationSuccess(request, response, authentication); super.onAuthenticationSuccess(request, response, authentication);
} else { } else {
SAML2 saml2 = applicationProperties.getSecurity().getSaml2(); SAML2 saml2 = applicationProperties.getSecurity().getSaml2();
log.debug(
"Processing SAML2 authentication with autoCreateUser: {}",
saml2.getAutoCreateUser());
if (loginAttemptService.isBlocked(username)) { if (loginAttemptService.isBlocked(username)) {
log.debug("User {} is blocked due to too many login attempts", username);
if (session != null) { if (session != null) {
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST"); session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
} }
throw new LockedException( throw new LockedException(
"Your account has been locked due to too many failed login attempts."); "Your account has been locked due to too many failed login attempts.");
} }
if (userService.usernameExistsIgnoreCase(username)
&& userService.hasPassword(username) boolean userExists = userService.usernameExistsIgnoreCase(username);
&& !userService.isAuthenticationTypeByUsername( boolean hasPassword = userExists && userService.hasPassword(username);
username, AuthenticationType.OAUTH2) boolean isSSOUser =
&& saml2.getAutoCreateUser()) { userExists
&& userService.isAuthenticationTypeByUsername(
username, AuthenticationType.SSO);
log.debug(
"User status - Exists: {}, Has password: {}, Is SSO user: {}",
userExists,
hasPassword,
isSSOUser);
if (userExists && hasPassword && !isSSOUser && saml2.getAutoCreateUser()) {
log.debug(
"User {} exists with password but is not SSO user, redirecting to logout",
username);
response.sendRedirect( response.sendRedirect(
contextPath + "/logout?oauth2AuthenticationErrorWeb=true"); contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
return; return;
} }
try { try {
if (saml2.getBlockRegistration() if (saml2.getBlockRegistration() && !userExists) {
&& !userService.usernameExistsIgnoreCase(username)) { log.debug("Registration blocked for new user: {}", username);
response.sendRedirect( response.sendRedirect(
contextPath + "/login?erroroauth=oauth2_admin_blocked_user"); contextPath + "/login?erroroauth=oauth2_admin_blocked_user");
return; return;
} }
userService.processOAuth2PostLogin(username, saml2.getAutoCreateUser()); log.debug("Processing SSO post-login for user: {}", username);
userService.processSSOPostLogin(username, saml2.getAutoCreateUser());
log.debug("Successfully processed authentication for user: {}", username);
response.sendRedirect(contextPath + "/"); response.sendRedirect(contextPath + "/");
return; return;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
log.debug(
"Invalid username detected for user: {}, redirecting to logout",
username);
response.sendRedirect(contextPath + "/logout?invalidUsername=true"); response.sendRedirect(contextPath + "/logout?invalidUsername=true");
return; return;
} }
} }
} else { } else {
log.debug("Non-SAML2 principal detected, delegating to parent handler");
super.onAuthenticationSuccess(request, response, authentication); super.onAuthenticationSuccess(request, response, authentication);
} }
} }

View File

@@ -3,8 +3,6 @@ package stirling.software.SPDF.config.security.saml2;
import java.util.*; import java.util.*;
import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.schema.XSBoolean;
import org.opensaml.core.xml.schema.XSString;
import org.opensaml.saml.saml2.core.Assertion; import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Attribute; import org.opensaml.saml.saml2.core.Attribute;
import org.opensaml.saml.saml2.core.AttributeStatement; import org.opensaml.saml.saml2.core.AttributeStatement;
@@ -30,15 +28,60 @@ public class CustomSaml2ResponseAuthenticationConverter
this.userService = userService; this.userService = userService;
} }
private Map<String, List<Object>> extractAttributes(Assertion assertion) {
Map<String, List<Object>> attributes = new HashMap<>();
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
for (Attribute attribute : attributeStatement.getAttributes()) {
String attributeName = attribute.getName();
List<Object> values = new ArrayList<>();
for (XMLObject xmlObject : attribute.getAttributeValues()) {
// Get the text content directly
String value = xmlObject.getDOM().getTextContent();
if (value != null && !value.trim().isEmpty()) {
values.add(value);
}
}
if (!values.isEmpty()) {
// Store with both full URI and last part of the URI
attributes.put(attributeName, values);
String shortName = attributeName.substring(attributeName.lastIndexOf('/') + 1);
attributes.put(shortName, values);
}
}
}
return attributes;
}
@Override @Override
public Saml2Authentication convert(ResponseToken responseToken) { public Saml2Authentication convert(ResponseToken responseToken) {
// Extract the assertion from the response
Assertion assertion = responseToken.getResponse().getAssertions().get(0); Assertion assertion = responseToken.getResponse().getAssertions().get(0);
Map<String, List<Object>> attributes = extractAttributes(assertion);
// Extract the NameID // Debug log with actual values
String nameId = assertion.getSubject().getNameID().getValue(); log.debug("Extracted SAML Attributes: " + attributes);
Optional<User> userOpt = userService.findByUsernameIgnoreCase(nameId); // Try to get username/identifier in order of preference
String userIdentifier = null;
if (hasAttribute(attributes, "username")) {
userIdentifier = getFirstAttributeValue(attributes, "username");
} else if (hasAttribute(attributes, "emailaddress")) {
userIdentifier = getFirstAttributeValue(attributes, "emailaddress");
} else if (hasAttribute(attributes, "name")) {
userIdentifier = getFirstAttributeValue(attributes, "name");
} else if (hasAttribute(attributes, "upn")) {
userIdentifier = getFirstAttributeValue(attributes, "upn");
} else if (hasAttribute(attributes, "uid")) {
userIdentifier = getFirstAttributeValue(attributes, "uid");
} else {
userIdentifier = assertion.getSubject().getNameID().getValue();
}
// Rest of your existing code...
Optional<User> userOpt = userService.findByUsernameIgnoreCase(userIdentifier);
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER"); SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
if (userOpt.isPresent()) { if (userOpt.isPresent()) {
User user = userOpt.get(); User user = userOpt.get();
@@ -48,39 +91,27 @@ public class CustomSaml2ResponseAuthenticationConverter
} }
} }
// Extract the SessionIndexes
List<String> sessionIndexes = new ArrayList<>(); List<String> sessionIndexes = new ArrayList<>();
for (AuthnStatement authnStatement : assertion.getAuthnStatements()) { for (AuthnStatement authnStatement : assertion.getAuthnStatements()) {
sessionIndexes.add(authnStatement.getSessionIndex()); sessionIndexes.add(authnStatement.getSessionIndex());
} }
// Extract the Attributes
Map<String, List<Object>> attributes = extractAttributes(assertion);
// Create the custom principal
CustomSaml2AuthenticatedPrincipal principal = CustomSaml2AuthenticatedPrincipal principal =
new CustomSaml2AuthenticatedPrincipal(nameId, attributes, nameId, sessionIndexes); new CustomSaml2AuthenticatedPrincipal(
userIdentifier, attributes, userIdentifier, sessionIndexes);
// Create the Saml2Authentication
return new Saml2Authentication( return new Saml2Authentication(
principal, principal,
responseToken.getToken().getSaml2Response(), responseToken.getToken().getSaml2Response(),
Collections.singletonList(simpleGrantedAuthority)); Collections.singletonList(simpleGrantedAuthority));
} }
private Map<String, List<Object>> extractAttributes(Assertion assertion) { private boolean hasAttribute(Map<String, List<Object>> attributes, String name) {
Map<String, List<Object>> attributes = new HashMap<>(); return attributes.containsKey(name) && !attributes.get(name).isEmpty();
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
for (Attribute attribute : attributeStatement.getAttributes()) {
String attributeName = attribute.getName();
List<Object> values = new ArrayList<>();
for (XMLObject xmlObject : attribute.getAttributeValues()) {
log.info("BOOL: " + ((XSBoolean) xmlObject).getValue());
values.add(((XSString) xmlObject).getValue());
} }
attributes.put(attributeName, values);
} private String getFirstAttributeValue(Map<String, List<Object>> attributes, String name) {
} List<Object> values = attributes.get(name);
return attributes; return values != null && !values.isEmpty() ? values.get(0).toString() : null;
} }
} }

View File

@@ -244,8 +244,8 @@ public class UserController {
return new RedirectView("/addUsers?messageType=invalidRole", true); return new RedirectView("/addUsers?messageType=invalidRole", true);
} }
if (authType.equalsIgnoreCase(AuthenticationType.OAUTH2.toString())) { if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) {
userService.saveUser(username, AuthenticationType.OAUTH2, role); userService.saveUser(username, AuthenticationType.SSO, role);
} else { } else {
if (password.isBlank()) { if (password.isBlank()) {
return new RedirectView("/addUsers?messageType=invalidPassword", true); return new RedirectView("/addUsers?messageType=invalidPassword", true);

View File

@@ -1,12 +1,13 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import java.io.FileOutputStream; import java.io.File;
import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -37,59 +38,90 @@ public class ConvertPDFToPDFA {
@Operation( @Operation(
summary = "Convert a PDF to a PDF/A", summary = "Convert a PDF to a PDF/A",
description = description =
"This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO") "This endpoint converts a PDF file to a PDF/A file using LibreOffice. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> pdfToPdfA(@ModelAttribute PdfToPdfARequest request) public ResponseEntity<byte[]> pdfToPdfA(@ModelAttribute PdfToPdfARequest request)
throws Exception { throws Exception {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String outputFormat = request.getOutputFormat(); String outputFormat = request.getOutputFormat();
// Convert MultipartFile to byte[] // Validate input file type
byte[] pdfBytes = inputFile.getBytes(); if (!"application/pdf".equals(inputFile.getContentType())) {
logger.error("Invalid input file type: {}", inputFile.getContentType());
// Save the uploaded file to a temporary location throw new IllegalArgumentException("Input file must be a PDF");
Path tempInputFile = Files.createTempFile("input_", ".pdf");
try (OutputStream outputStream = new FileOutputStream(tempInputFile.toFile())) {
outputStream.write(pdfBytes);
} }
// Prepare the output file path // Get the original filename without extension
Path tempOutputFile = Files.createTempFile("output_", ".pdf"); String originalFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
if (originalFileName == null || originalFileName.trim().isEmpty()) {
originalFileName = "output.pdf";
}
String baseFileName =
originalFileName.contains(".")
? originalFileName.substring(0, originalFileName.lastIndexOf('.'))
: originalFileName;
// Prepare the ghostscript command Path tempInputFile = null;
List<String> command = new ArrayList<>(); Path tempOutputDir = null;
command.add("gs"); byte[] fileBytes;
command.add("-dPDFA=" + ("pdfa".equals(outputFormat) ? "2" : "1"));
command.add("-dNOPAUSE"); try {
command.add("-dBATCH"); // Save uploaded file to temp location
command.add("-sColorConversionStrategy=sRGB"); tempInputFile = Files.createTempFile("input_", ".pdf");
command.add("-sDEVICE=pdfwrite"); inputFile.transferTo(tempInputFile);
command.add("-dPDFACompatibilityPolicy=2");
command.add("-o"); // Create temp output directory
command.add(tempOutputFile.toString()); tempOutputDir = Files.createTempDirectory("output_");
command.add(tempInputFile.toString());
// 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";
// Prepare LibreOffice command
List<String> command =
new ArrayList<>(
Arrays.asList(
"soffice",
"--headless",
"--nologo",
"--convert-to",
"pdf:" + pdfFilter,
"--outdir",
tempOutputDir.toString(),
tempInputFile.toString()));
ProcessExecutorResult returnCode = ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
if (returnCode.getRc() != 0) { if (returnCode.getRc() != 0) {
logger.info( logger.error("PDF/A conversion failed with return code: {}", returnCode.getRc());
outputFormat + " conversion failed with return code: " + returnCode.getRc()); throw new RuntimeException("PDF/A conversion failed");
} }
try { // Get the output file
byte[] pdfBytesOutput = Files.readAllBytes(tempOutputFile); File[] outputFiles = tempOutputDir.toFile().listFiles();
// Return the optimized PDF as a response if (outputFiles == null || outputFiles.length != 1) {
String outputFilename = throw new RuntimeException(
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) "Expected exactly one output file but found "
.replaceFirst("[.][^.]+$", "") + (outputFiles == null ? "none" : outputFiles.length));
+ "_PDFA.pdf"; }
fileBytes = FileUtils.readFileToByteArray(outputFiles[0]);
String outputFilename = baseFileName + "_PDFA.pdf";
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
pdfBytesOutput, outputFilename, MediaType.APPLICATION_PDF); fileBytes, outputFilename, MediaType.APPLICATION_PDF);
} finally { } finally {
// Clean up the temporary files // Clean up temporary files
if (tempInputFile != null) {
Files.deleteIfExists(tempInputFile); Files.deleteIfExists(tempInputFile);
Files.deleteIfExists(tempOutputFile); }
if (tempOutputDir != null) {
FileUtils.deleteDirectory(tempOutputDir.toFile());
}
} }
} }
} }

View File

@@ -20,7 +20,7 @@ import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.controller.api.CropController;
import stirling.software.SPDF.model.api.extract.PDFFilePage; import stirling.software.SPDF.model.api.extract.PDFFilePage;
import stirling.software.SPDF.pdf.FlexibleCSVWriter; import stirling.software.SPDF.pdf.FlexibleCSVWriter;
import technology.tabula.ObjectExtractor; import technology.tabula.ObjectExtractor;
@@ -37,11 +37,15 @@ public class ExtractCSVController {
private static final Logger logger = LoggerFactory.getLogger(ExtractCSVController.class); private static final Logger logger = LoggerFactory.getLogger(ExtractCSVController.class);
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data") @PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
@Operation(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") @Operation(
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 { public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception {
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) { try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
CSVFormat format = CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build(); CSVFormat format =
CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build();
Writer csvWriter = new FlexibleCSVWriter(format); Writer csvWriter = new FlexibleCSVWriter(format);
SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm(); SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
try (ObjectExtractor extractor = new ObjectExtractor(document)) { try (ObjectExtractor extractor = new ObjectExtractor(document)) {

View File

@@ -10,7 +10,6 @@ import java.util.List;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@@ -53,6 +52,54 @@ public class CompressController {
this.pdfDocumentFactory = pdfDocumentFactory; this.pdfDocumentFactory = pdfDocumentFactory;
} }
private void compressImagesInPDF(Path pdfFile, double initialScaleFactor) throws Exception {
byte[] fileBytes = Files.readAllBytes(pdfFile);
try (PDDocument doc = Loader.loadPDF(fileBytes)) {
double scaleFactor = initialScaleFactor;
for (PDPage page : doc.getPages()) {
PDResources res = page.getResources();
if (res != null && res.getXObjectNames() != null) {
for (COSName name : res.getXObjectNames()) {
PDXObject xobj = res.getXObject(name);
if (xobj instanceof PDImageXObject) {
PDImageXObject image = (PDImageXObject) xobj;
BufferedImage bufferedImage = image.getImage();
int newWidth = (int) (bufferedImage.getWidth() * scaleFactor);
int newHeight = (int) (bufferedImage.getHeight() * scaleFactor);
if (newWidth == 0 || newHeight == 0) {
continue;
}
Image scaledImage =
bufferedImage.getScaledInstance(
newWidth, newHeight, Image.SCALE_SMOOTH);
BufferedImage scaledBufferedImage =
new BufferedImage(
newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
scaledBufferedImage.getGraphics().drawImage(scaledImage, 0, 0, null);
ByteArrayOutputStream compressedImageStream =
new ByteArrayOutputStream();
ImageIO.write(scaledBufferedImage, "jpeg", compressedImageStream);
byte[] imageBytes = compressedImageStream.toByteArray();
compressedImageStream.close();
PDImageXObject compressedImage =
PDImageXObject.createFromByteArray(
doc, imageBytes, image.getCOSObject().toString());
res.put(name, compressedImage);
}
}
}
}
doc.save(pdfFile.toString());
}
}
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf") @PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
@Operation( @Operation(
summary = "Optimize PDF file", summary = "Optimize PDF file",
@@ -75,209 +122,92 @@ public class CompressController {
autoMode = true; autoMode = true;
} }
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf"); Path tempInputFile = Files.createTempFile("input_", ".pdf");
inputFile.transferTo(tempInputFile.toFile()); inputFile.transferTo(tempInputFile.toFile());
long inputFileSize = Files.size(tempInputFile); long inputFileSize = Files.size(tempInputFile);
// Prepare the output file path
Path tempOutputFile = null; Path tempOutputFile = null;
byte[] pdfBytes; byte[] pdfBytes;
try { try {
tempOutputFile = Files.createTempFile("output_", ".pdf"); tempOutputFile = Files.createTempFile("output_", ".pdf");
// Determine initial optimization level based on expected size reduction, only if in
// autoMode
if (autoMode) { if (autoMode) {
double sizeReductionRatio = expectedOutputSize / (double) inputFileSize; double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;
if (sizeReductionRatio > 0.7) { optimizeLevel = determineOptimizeLevel(sizeReductionRatio);
optimizeLevel = 1;
} else if (sizeReductionRatio > 0.5) {
optimizeLevel = 2;
} else if (sizeReductionRatio > 0.35) {
optimizeLevel = 3;
} else {
optimizeLevel = 3;
}
} }
boolean sizeMet = false; boolean sizeMet = false;
while (!sizeMet && optimizeLevel <= 4) { while (!sizeMet && optimizeLevel <= 9) {
// Prepare the Ghostscript command
List<String> command = new ArrayList<>();
command.add("gs");
command.add("-sDEVICE=pdfwrite");
command.add("-dCompatibilityLevel=1.5");
// Apply additional image compression for levels 6-9
if (optimizeLevel >= 6) {
// Calculate scale factor based on optimization level
double scaleFactor =
switch (optimizeLevel) { switch (optimizeLevel) {
case 1: case 6 -> 0.9; // 90% of original size
command.add("-dPDFSETTINGS=/prepress"); case 7 -> 0.8; // 80% of original size
break; case 8 -> 0.65; // 70% of original size
case 2: case 9 -> 0.5; // 60% of original size
command.add("-dPDFSETTINGS=/printer"); default -> 1.0;
break; };
case 3: compressImagesInPDF(tempInputFile, scaleFactor);
command.add("-dPDFSETTINGS=/ebook");
break;
case 4:
command.add("-dPDFSETTINGS=/screen");
break;
default:
command.add("-dPDFSETTINGS=/default");
} }
command.add("-dNOPAUSE"); // Run QPDF optimization
command.add("-dQUIET"); List<String> command = new ArrayList<>();
command.add("-dBATCH"); command.add("qpdf");
command.add("-sOutputFile=" + tempOutputFile.toString()); if (request.getNormalize()) {
command.add("--normalize-content=y");
}
if (request.getLinearize()) {
command.add("--linearize");
}
command.add("--optimize-images");
command.add("--recompress-flate");
command.add("--compression-level=" + optimizeLevel);
command.add("--compress-streams=y");
command.add("--object-streams=generate");
command.add(tempInputFile.toString()); command.add(tempInputFile.toString());
command.add(tempOutputFile.toString());
ProcessExecutorResult returnCode = ProcessExecutorResult returnCode = null;
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) try {
returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.QPDF)
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
} catch (Exception e) {
if (returnCode != null && returnCode.getRc() != 3) {
throw e;
}
}
// Check if file size is within expected size or not auto mode so instantly finish // Check if file size is within expected size or not auto mode
long outputFileSize = Files.size(tempOutputFile); long outputFileSize = Files.size(tempOutputFile);
if (outputFileSize <= expectedOutputSize || !autoMode) { if (outputFileSize <= expectedOutputSize || !autoMode) {
sizeMet = true; sizeMet = true;
} else { } else {
// Increase optimization level for next iteration optimizeLevel =
optimizeLevel++; incrementOptimizeLevel(
if (autoMode && optimizeLevel > 4) { optimizeLevel, outputFileSize, expectedOutputSize);
logger.info("Skipping level 5 due to bad results in auto mode"); if (autoMode && optimizeLevel > 9) {
logger.info("Maximum compression level reached in auto mode");
sizeMet = true; sizeMet = true;
} else {
logger.info(
"Increasing ghostscript optimisation level to " + optimizeLevel);
} }
} }
} }
if (expectedOutputSize != null && autoMode) {
long outputFileSize = Files.size(tempOutputFile);
byte[] fileBytes = Files.readAllBytes(tempOutputFile);
if (outputFileSize > expectedOutputSize) {
try (PDDocument doc = Loader.loadPDF(fileBytes)) {
long previousFileSize = 0;
double scaleFactorConst = 0.9f;
double scaleFactor = 0.9f;
while (true) {
for (PDPage page : doc.getPages()) {
PDResources res = page.getResources();
if (res != null && res.getXObjectNames() != null) {
for (COSName name : res.getXObjectNames()) {
PDXObject xobj = res.getXObject(name);
if (xobj != null && xobj instanceof PDImageXObject) {
PDImageXObject image = (PDImageXObject) xobj;
// Get the image in BufferedImage format
BufferedImage bufferedImage = image.getImage();
// Calculate the new dimensions
int newWidth =
(int)
(bufferedImage.getWidth()
* scaleFactorConst);
int newHeight =
(int)
(bufferedImage.getHeight()
* scaleFactorConst);
// If the new dimensions are zero, skip this iteration
if (newWidth == 0 || newHeight == 0) {
continue;
}
// Otherwise, proceed with the scaling
Image scaledImage =
bufferedImage.getScaledInstance(
newWidth,
newHeight,
Image.SCALE_SMOOTH);
// Convert the scaled image back to a BufferedImage
BufferedImage scaledBufferedImage =
new BufferedImage(
newWidth,
newHeight,
BufferedImage.TYPE_INT_RGB);
scaledBufferedImage
.getGraphics()
.drawImage(scaledImage, 0, 0, null);
// Compress the scaled image
ByteArrayOutputStream compressedImageStream =
new ByteArrayOutputStream();
ImageIO.write(
scaledBufferedImage,
"jpeg",
compressedImageStream);
byte[] imageBytes = compressedImageStream.toByteArray();
compressedImageStream.close();
PDImageXObject compressedImage =
PDImageXObject.createFromByteArray(
doc,
imageBytes,
image.getCOSObject().toString());
// Replace the image in the resources with the
// compressed
// version
res.put(name, compressedImage);
}
}
}
}
// save the document to tempOutputFile again
doc.save(tempOutputFile.toString());
long currentSize = Files.size(tempOutputFile);
// Check if the overall PDF size is still larger than expectedOutputSize
if (currentSize > expectedOutputSize) {
// Log the current file size and scaleFactor
logger.info(
"Current file size: "
+ FileUtils.byteCountToDisplaySize(currentSize));
logger.info("Current scale factor: " + scaleFactor);
// The file is still too large, reduce scaleFactor and try again
scaleFactor *= 0.9f; // reduce scaleFactor by 10%
// Avoid scaleFactor being too small, causing the image to shrink to
// 0
if (scaleFactor < 0.2f || previousFileSize == currentSize) {
throw new RuntimeException(
"Could not reach the desired size without excessively degrading image quality, lowest size recommended is "
+ FileUtils.byteCountToDisplaySize(currentSize)
+ ", "
+ currentSize
+ " bytes");
}
previousFileSize = currentSize;
} else {
// The file is small enough, break the loop
break;
}
}
}
}
}
// Read the optimized PDF file // Read the optimized PDF file
pdfBytes = Files.readAllBytes(tempOutputFile); pdfBytes = Files.readAllBytes(tempOutputFile);
Path finalFile = tempOutputFile; Path finalFile = tempOutputFile;
// Check if optimized file is larger than the original // Check if optimized file is larger than the original
if (pdfBytes.length > inputFileSize) { if (pdfBytes.length > inputFileSize) {
// Log the occurrence
logger.warn( logger.warn(
"Optimized file is larger than the original. Returning the original file instead."); "Optimized file is larger than the original. Returning the original file instead.");
// Read the original file again
finalFile = tempInputFile; finalFile = tempInputFile;
} }
// Return the optimized PDF as a response
String outputFilename = String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "") .replaceFirst("[.][^.]+$", "")
@@ -286,10 +216,31 @@ public class CompressController {
pdfDocumentFactory.load(finalFile.toFile()), outputFilename); pdfDocumentFactory.load(finalFile.toFile()), outputFilename);
} finally { } finally {
// Clean up the temporary files
// deleted by multipart file handler deu to transferTo?
// Files.deleteIfExists(tempInputFile);
Files.deleteIfExists(tempOutputFile); Files.deleteIfExists(tempOutputFile);
} }
} }
private int determineOptimizeLevel(double sizeReductionRatio) {
if (sizeReductionRatio > 0.9) return 1;
if (sizeReductionRatio > 0.8) return 2;
if (sizeReductionRatio > 0.7) return 3;
if (sizeReductionRatio > 0.6) return 4;
if (sizeReductionRatio > 0.5) return 5;
if (sizeReductionRatio > 0.4) return 6;
if (sizeReductionRatio > 0.3) return 7;
if (sizeReductionRatio > 0.2) return 8;
return 9;
}
private int incrementOptimizeLevel(int currentLevel, long currentSize, long targetSize) {
double currentRatio = currentSize / (double) targetSize;
logger.info("Current compression ratio: {}", String.format("%.2f", currentRatio));
if (currentRatio > 2.0) {
return Math.min(9, currentLevel + 3);
} else if (currentRatio > 1.5) {
return Math.min(9, currentLevel + 2);
}
return Math.min(9, currentLevel + 1);
}
} }

View File

@@ -58,7 +58,7 @@ public class FakeScanControllerWIP {
@Operation( @Operation(
summary = "Repair a PDF file", summary = "Repair a PDF file",
description = description =
"This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response.") "This endpoint repairs a given PDF file by running qpdf command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response.")
public ResponseEntity<byte[]> fakeScan(@ModelAttribute PDFFile request) throws IOException { public ResponseEntity<byte[]> fakeScan(@ModelAttribute PDFFile request) throws IOException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();

View File

@@ -1,19 +1,29 @@
package stirling.software.SPDF.controller.api.misc; package stirling.software.SPDF.controller.api.misc;
import java.io.ByteArrayInputStream; import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -23,24 +33,31 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.BoundedLineReader;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest; import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@RequestMapping("/api/v1/misc") @RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "Misc", description = "Miscellaneous APIs")
@Slf4j
public class OCRController { public class OCRController {
@Autowired ApplicationProperties applicationProperties; @Autowired private ApplicationProperties applicationProperties;
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public OCRController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
/** Gets the list of available Tesseract languages from the tessdata directory */
public List<String> getAvailableTesseractLanguages() { public List<String> getAvailableTesseractLanguages() {
String tessdataDir = applicationProperties.getSystem().getTessdataDir(); String tessdataDir = applicationProperties.getSystem().getTessdataDir();
File[] files = new File(tessdataDir).listFiles(); File[] files = new File(tessdataDir).listFiles();
@@ -54,196 +71,163 @@ public class OCRController {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public OCRController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf") @PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
@Operation(
summary = "Process a PDF file with OCR",
description =
"This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options. Input:PDF Output:PDF Type:SI-Conditional")
public ResponseEntity<byte[]> processPdfWithOCR( public ResponseEntity<byte[]> processPdfWithOCR(
@ModelAttribute ProcessPdfWithOcrRequest request) @ModelAttribute ProcessPdfWithOcrRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
List<String> selectedLanguages = request.getLanguages(); List<String> languages = request.getLanguages();
Boolean sidecar = request.isSidecar();
Boolean deskew = request.isDeskew();
Boolean clean = request.isClean();
Boolean cleanFinal = request.isCleanFinal();
String ocrType = request.getOcrType(); String ocrType = request.getOcrType();
String ocrRenderType = request.getOcrRenderType();
Boolean removeImagesAfter = request.isRemoveImagesAfter();
// --output-type pdfa
if (selectedLanguages == null || selectedLanguages.isEmpty()) {
throw new IOException("Please select at least one language.");
}
if (!"hocr".equals(ocrRenderType) && !"sandwich".equals(ocrRenderType)) { Path tempDir = Files.createTempDirectory("ocr_process");
throw new IOException("ocrRenderType wrong"); Path tempInputFile = tempDir.resolve("input.pdf");
} Path tempOutputDir = tempDir.resolve("output");
Path tempImagesDir = tempDir.resolve("images");
Path finalOutputFile = tempDir.resolve("final_output.pdf");
// Get available Tesseract languages Files.createDirectories(tempOutputDir);
List<String> availableLanguages = getAvailableTesseractLanguages(); Files.createDirectories(tempImagesDir);
// Validate selected languages
selectedLanguages =
selectedLanguages.stream().filter(availableLanguages::contains).toList();
if (selectedLanguages.isEmpty()) {
throw new IOException("None of the selected languages are valid.");
}
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf");
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
Path sidecarTextPath = null;
try { try {
// Save input file
inputFile.transferTo(tempInputFile.toFile()); inputFile.transferTo(tempInputFile.toFile());
PDFMergerUtility merger = new PDFMergerUtility();
merger.setDestinationFileName(finalOutputFile.toString());
// Run OCR Command try (PDDocument document = pdfDocumentFactory.load(tempInputFile.toFile())) {
String languageOption = String.join("+", selectedLanguages); PDFRenderer pdfRenderer = new PDFRenderer(document);
int pageCount = document.getNumberOfPages();
List<String> command = for (int pageNum = 0; pageNum < pageCount; pageNum++) {
new ArrayList<>( PDPage page = document.getPage(pageNum);
Arrays.asList( boolean hasText = false;
"ocrmypdf",
"--verbose",
"2",
"--output-type",
"pdf",
"--pdf-renderer",
ocrRenderType));
if (sidecar != null && sidecar) { // Check for existing text
sidecarTextPath = Files.createTempFile("sidecar", ".txt"); try (PDDocument tempDoc = new PDDocument()) {
command.add("--sidecar"); tempDoc.addPage(page);
command.add(sidecarTextPath.toString()); PDFTextStripper stripper = new PDFTextStripper();
hasText = !stripper.getText(tempDoc).trim().isEmpty();
} }
if (deskew != null && deskew) { boolean shouldOcr =
command.add("--deskew"); switch (ocrType) {
} case "skip-text" -> !hasText;
if (clean != null && clean) { case "force-ocr" -> true;
command.add("--clean"); default -> true;
} };
if (cleanFinal != null && cleanFinal) {
command.add("--clean-final");
}
if (ocrType != null && !"".equals(ocrType)) {
if ("skip-text".equals(ocrType)) {
command.add("--skip-text");
} else if ("force-ocr".equals(ocrType)) {
command.add("--force-ocr");
} else if ("Normal".equals(ocrType)) {
Path pageOutputPath =
tempOutputDir.resolve(String.format("page_%d.pdf", pageNum));
if (shouldOcr) {
// Convert page to image
BufferedImage image = pdfRenderer.renderImageWithDPI(pageNum, 300);
Path imagePath =
tempImagesDir.resolve(String.format("page_%d.png", pageNum));
ImageIO.write(image, "png", imagePath.toFile());
// Build OCR command
List<String> command = new ArrayList<>();
command.add("tesseract");
command.add(imagePath.toString());
command.add(
tempOutputDir
.resolve(String.format("page_%d", pageNum))
.toString());
command.add("-l");
command.add(String.join("+", languages));
command.add("pdf"); // Always output PDF
ProcessBuilder pb = new ProcessBuilder(command);
Process process = pb.start();
// Capture any error output
try (BufferedReader reader =
new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = BoundedLineReader.readLine(reader, 5_000_000)) != null) {
log.debug("Tesseract: {}", line);
} }
} }
command.addAll( int exitCode = process.waitFor();
Arrays.asList( if (exitCode != 0) {
"--language", throw new RuntimeException(
languageOption, "Tesseract failed with exit code: " + exitCode);
tempInputFile.toString(),
tempOutputFile.toString()));
// Run CLI command
ProcessExecutorResult result =
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
.runCommandWithOutputHandling(command);
if (result.getRc() != 0
&& result.getMessages().contains("multiprocessing/synchronize.py")
&& result.getMessages()
.contains("OSError: [Errno 38] Function not implemented")) {
command.add("--jobs");
command.add("1");
result =
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
.runCommandWithOutputHandling(command);
} }
// Remove images from the OCR processed PDF if the flag is set to true // Add OCR'd PDF to merger
if (removeImagesAfter != null && removeImagesAfter) { merger.addSource(pageOutputPath.toFile());
Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf"); } else {
// Save original page without OCR
List<String> gsCommand = try (PDDocument pageDoc = new PDDocument()) {
Arrays.asList( pageDoc.addPage(page);
"gs", pageDoc.save(pageOutputPath.toFile());
"-sDEVICE=pdfwrite", merger.addSource(pageOutputPath.toFile());
"-dFILTERIMAGE", }
"-o", }
tempPdfWithoutImages.toString(), }
tempOutputFile.toString());
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
.runCommandWithOutputHandling(gsCommand);
tempOutputFile = tempPdfWithoutImages;
} }
// Read the OCR processed PDF file
byte[] pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile());
// Return the OCR processed PDF as a response // Merge all pages into final PDF
merger.mergeDocuments(null);
// Read the final PDF file
byte[] pdfContent = Files.readAllBytes(finalOutputFile);
String outputFilename = String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "") .replaceFirst("[.][^.]+$", "")
+ "_OCR.pdf"; + "_OCR.pdf";
if (sidecar != null && sidecar) { return ResponseEntity.ok()
// Create a zip file containing both the PDF and the text file .header(
String outputZipFilename = "Content-Disposition",
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) "attachment; filename=\"" + outputFilename + "\"")
.replaceFirst("[.][^.]+$", "") .contentType(MediaType.APPLICATION_PDF)
+ "_OCR.zip"; .body(pdfContent);
Path tempZipFile = Files.createTempFile("output_", ".zip");
} finally {
// Clean up temporary files
deleteDirectory(tempDir);
}
}
private void addFileToZip(File file, String filename, ZipOutputStream zipOut)
throws IOException {
if (!file.exists()) {
log.warn("File {} does not exist, skipping", file);
return;
}
try (FileInputStream fis = new FileInputStream(file)) {
ZipEntry zipEntry = new ZipEntry(filename);
zipOut.putNextEntry(zipEntry);
try (ZipOutputStream zipOut =
new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(outputFilename);
zipOut.putNextEntry(pdfEntry);
try (ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes)) {
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
int length; int length;
while ((length = pdfInputStream.read(buffer)) != -1) { while ((length = fis.read(buffer)) >= 0) {
zipOut.write(buffer, 0, length); zipOut.write(buffer, 0, length);
} }
}
zipOut.closeEntry();
// Add text file to the zip
ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt"));
zipOut.putNextEntry(txtEntry);
Files.copy(sidecarTextPath, zipOut);
zipOut.closeEntry(); zipOut.closeEntry();
} }
byte[] zipBytes = Files.readAllBytes(tempZipFile);
// Clean up the temporary zip file
Files.deleteIfExists(tempZipFile);
Files.deleteIfExists(tempOutputFile);
Files.deleteIfExists(sidecarTextPath);
// Return the zip file containing both the PDF and the text file
return WebResponseUtils.bytesToWebResponse(
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
} else {
// Return the OCR processed PDF as a response
Files.deleteIfExists(tempOutputFile);
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
} }
} finally {
// Clean up the temporary files private void deleteDirectory(Path directory) {
Files.deleteIfExists(tempOutputFile); try {
// Comment out as transferTo makes multipart handle cleanup Files.walk(directory)
// Files.deleteIfExists(tempInputFile); .sorted(Comparator.reverseOrder())
if (sidecarTextPath != null) { .forEach(
Files.deleteIfExists(sidecarTextPath); path -> {
try {
Files.delete(path);
} catch (IOException e) {
log.error("Error deleting {}: {}", path, e.getMessage());
} }
});
} catch (IOException e) {
log.error("Error walking directory {}: {}", directory, e.getMessage());
} }
} }
} }

View File

@@ -44,30 +44,29 @@ public class RepairController {
@Operation( @Operation(
summary = "Repair a PDF file", summary = "Repair a PDF file",
description = description =
"This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO") "This endpoint repairs a given PDF file by running qpdf command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request) public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
// Save the uploaded file to a temporary location // Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf"); Path tempInputFile = Files.createTempFile("input_", ".pdf");
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
byte[] pdfBytes = null; byte[] pdfBytes = null;
inputFile.transferTo(tempInputFile.toFile()); inputFile.transferTo(tempInputFile.toFile());
try { try {
List<String> command = new ArrayList<>(); List<String> command = new ArrayList<>();
command.add("gs"); command.add("qpdf");
command.add("-o"); command.add("--replace-input"); // Automatically fixes problems it can
command.add(tempOutputFile.toString()); command.add("--qdf"); // Linearizes and normalizes PDF structure
command.add("-sDEVICE=pdfwrite"); command.add("--object-streams=disable"); // Can help with some corruptions
command.add(tempInputFile.toString()); command.add(tempInputFile.toString());
ProcessExecutorResult returnCode = ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) ProcessExecutor.getInstance(ProcessExecutor.Processes.QPDF)
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
// Read the optimized PDF file // Read the optimized PDF file
pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile()); pdfBytes = pdfDocumentFactory.loadToBytes(tempInputFile.toFile());
// Return the optimized PDF as a response // Return the optimized PDF as a response
String outputFilename = String outputFilename =
@@ -78,7 +77,6 @@ public class RepairController {
} finally { } finally {
// Clean up the temporary files // Clean up the temporary files
Files.deleteIfExists(tempInputFile); Files.deleteIfExists(tempInputFile);
Files.deleteIfExists(tempOutputFile);
} }
} }
} }

View File

@@ -160,7 +160,8 @@ public class CertSignController {
extState.setNonStrokingAlphaConstant(0.5f); extState.setNonStrokingAlphaConstant(0.5f);
cs.setGraphicsStateParameters(extState); cs.setGraphicsStateParameters(extState);
cs.transform(Matrix.getScaleInstance(0.08f, 0.08f)); cs.transform(Matrix.getScaleInstance(0.08f, 0.08f));
PDImageXObject img = PDImageXObject.createFromFileByExtension(logoFile, doc); PDImageXObject img =
PDImageXObject.createFromFileByExtension(logoFile, doc);
cs.drawImage(img, 100, 0); cs.drawImage(img, 100, 0);
cs.restoreGraphicsState(); cs.restoreGraphicsState();
} }
@@ -208,7 +209,10 @@ public class CertSignController {
} }
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign") @PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
@Operation(summary = "Sign PDF with a Digital Certificate", description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO") @Operation(
summary = "Sign PDF with a Digital Certificate",
description =
"This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
throws Exception { throws Exception {
MultipartFile pdf = request.getFileInput(); MultipartFile pdf = request.getFileInput();
@@ -238,7 +242,7 @@ public class CertSignController {
PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password); PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password);
Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes()); Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes());
ks.setKeyEntry( ks.setKeyEntry(
"alias", privateKey, password.toCharArray(), new Certificate[] { cert }); "alias", privateKey, password.toCharArray(), new Certificate[] {cert});
break; break;
case "PKCS12": case "PKCS12":
ks = KeyStore.getInstance("PKCS12"); ks = KeyStore.getInstance("PKCS12");
@@ -310,17 +314,20 @@ public class CertSignController {
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password) private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
throws IOException, OperatorCreationException, PKCSException { throws IOException, OperatorCreationException, PKCSException {
try (PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) { try (PEMParser pemParser =
new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) {
Object pemObject = pemParser.readObject(); Object pemObject = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
PrivateKeyInfo pkInfo; PrivateKeyInfo pkInfo;
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) { if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder() InputDecryptorProvider decProv =
.build(password.toCharArray()); new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());
pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv); pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv);
} else if (pemObject instanceof PEMEncryptedKeyPair) { } else if (pemObject instanceof PEMEncryptedKeyPair) {
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); PEMDecryptorProvider decProv =
pkInfo = ((PEMEncryptedKeyPair) pemObject) new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
pkInfo =
((PEMEncryptedKeyPair) pemObject)
.decryptKeyPair(decProv) .decryptKeyPair(decProv)
.getPrivateKeyInfo(); .getPrivateKeyInfo();
} else { } else {

View File

@@ -55,6 +55,11 @@ public class HomeWebController {
return "licenses"; return "licenses";
} }
@GetMapping("/releases")
public String getReleaseNotes(Model model) {
return "releases";
}
@GetMapping("/") @GetMapping("/")
public String home(Model model) { public String home(Model model) {
model.addAttribute("currentPage", "home"); model.addAttribute("currentPage", "home");

View File

@@ -320,12 +320,20 @@ public class ApplicationProperties {
public static class SessionLimit { public static class SessionLimit {
private int libreOfficeSessionLimit; private int libreOfficeSessionLimit;
private int pdfToHtmlSessionLimit; private int pdfToHtmlSessionLimit;
private int ocrMyPdfSessionLimit;
private int pythonOpenCvSessionLimit; private int pythonOpenCvSessionLimit;
private int ghostScriptSessionLimit;
private int weasyPrintSessionLimit; private int weasyPrintSessionLimit;
private int installAppSessionLimit; private int installAppSessionLimit;
private int calibreSessionLimit; private int calibreSessionLimit;
private int qpdfSessionLimit;
private int tesseractSessionLimit;
public int getQpdfSessionLimit() {
return qpdfSessionLimit > 0 ? qpdfSessionLimit : 2;
}
public int getTesseractSessionLimit() {
return tesseractSessionLimit > 0 ? tesseractSessionLimit : 1;
}
public int getLibreOfficeSessionLimit() { public int getLibreOfficeSessionLimit() {
return libreOfficeSessionLimit > 0 ? libreOfficeSessionLimit : 1; return libreOfficeSessionLimit > 0 ? libreOfficeSessionLimit : 1;
@@ -335,18 +343,10 @@ public class ApplicationProperties {
return pdfToHtmlSessionLimit > 0 ? pdfToHtmlSessionLimit : 1; return pdfToHtmlSessionLimit > 0 ? pdfToHtmlSessionLimit : 1;
} }
public int getOcrMyPdfSessionLimit() {
return ocrMyPdfSessionLimit > 0 ? ocrMyPdfSessionLimit : 2;
}
public int getPythonOpenCvSessionLimit() { public int getPythonOpenCvSessionLimit() {
return pythonOpenCvSessionLimit > 0 ? pythonOpenCvSessionLimit : 8; return pythonOpenCvSessionLimit > 0 ? pythonOpenCvSessionLimit : 8;
} }
public int getGhostScriptSessionLimit() {
return ghostScriptSessionLimit > 0 ? ghostScriptSessionLimit : 16;
}
public int getWeasyPrintSessionLimit() { public int getWeasyPrintSessionLimit() {
return weasyPrintSessionLimit > 0 ? weasyPrintSessionLimit : 16; return weasyPrintSessionLimit > 0 ? weasyPrintSessionLimit : 16;
} }
@@ -364,12 +364,20 @@ public class ApplicationProperties {
public static class TimeoutMinutes { public static class TimeoutMinutes {
private long libreOfficeTimeoutMinutes; private long libreOfficeTimeoutMinutes;
private long pdfToHtmlTimeoutMinutes; private long pdfToHtmlTimeoutMinutes;
private long ocrMyPdfTimeoutMinutes;
private long pythonOpenCvTimeoutMinutes; private long pythonOpenCvTimeoutMinutes;
private long ghostScriptTimeoutMinutes;
private long weasyPrintTimeoutMinutes; private long weasyPrintTimeoutMinutes;
private long installAppTimeoutMinutes; private long installAppTimeoutMinutes;
private long calibreTimeoutMinutes; private long calibreTimeoutMinutes;
private long tesseractTimeoutMinutes;
private long qpdfTimeoutMinutes;
public long getTesseractTimeoutMinutes() {
return tesseractTimeoutMinutes > 0 ? tesseractTimeoutMinutes : 30;
}
public long getQpdfTimeoutMinutes() {
return qpdfTimeoutMinutes > 0 ? qpdfTimeoutMinutes : 30;
}
public long getLibreOfficeTimeoutMinutes() { public long getLibreOfficeTimeoutMinutes() {
return libreOfficeTimeoutMinutes > 0 ? libreOfficeTimeoutMinutes : 30; return libreOfficeTimeoutMinutes > 0 ? libreOfficeTimeoutMinutes : 30;
@@ -379,18 +387,10 @@ public class ApplicationProperties {
return pdfToHtmlTimeoutMinutes > 0 ? pdfToHtmlTimeoutMinutes : 20; return pdfToHtmlTimeoutMinutes > 0 ? pdfToHtmlTimeoutMinutes : 20;
} }
public long getOcrMyPdfTimeoutMinutes() {
return ocrMyPdfTimeoutMinutes > 0 ? ocrMyPdfTimeoutMinutes : 30;
}
public long getPythonOpenCvTimeoutMinutes() { public long getPythonOpenCvTimeoutMinutes() {
return pythonOpenCvTimeoutMinutes > 0 ? pythonOpenCvTimeoutMinutes : 30; return pythonOpenCvTimeoutMinutes > 0 ? pythonOpenCvTimeoutMinutes : 30;
} }
public long getGhostScriptTimeoutMinutes() {
return ghostScriptTimeoutMinutes > 0 ? ghostScriptTimeoutMinutes : 30;
}
public long getWeasyPrintTimeoutMinutes() { public long getWeasyPrintTimeoutMinutes() {
return weasyPrintTimeoutMinutes > 0 ? weasyPrintTimeoutMinutes : 30; return weasyPrintTimeoutMinutes > 0 ? weasyPrintTimeoutMinutes : 30;
} }

View File

@@ -2,5 +2,5 @@ package stirling.software.SPDF.model;
public enum AuthenticationType { public enum AuthenticationType {
WEB, WEB,
OAUTH2 SSO
} }

View File

@@ -18,4 +18,15 @@ public class OptimizePdfRequest extends PDFFile {
@Schema(description = "The expected output size, e.g. '100MB', '25KB', etc.") @Schema(description = "The expected output size, e.g. '100MB', '25KB', etc.")
private String expectedOutputSize; private String expectedOutputSize;
@Schema(
description = "Whether to linearize the PDF for faster web viewing. Default is false.",
defaultValue = "false")
private Boolean linearize = false;
@Schema(
description =
"Whether to normalize the PDF content for better compatibility. Default is true.",
defaultValue = "true")
private Boolean normalize = true;
} }

View File

@@ -15,18 +15,6 @@ public class ProcessPdfWithOcrRequest extends PDFFile {
@Schema(description = "List of languages to use in OCR processing") @Schema(description = "List of languages to use in OCR processing")
private List<String> languages; private List<String> languages;
@Schema(description = "Include OCR text in a sidecar text file if set to true")
private boolean sidecar;
@Schema(description = "Deskew the input file if set to true")
private boolean deskew;
@Schema(description = "Clean the input file if set to true")
private boolean clean;
@Schema(description = "Clean the final output if set to true")
private boolean cleanFinal;
@Schema( @Schema(
description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'", description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'",
allowableValues = {"skip-text", "force-ocr", "Normal"}) allowableValues = {"skip-text", "force-ocr", "Normal"})
@@ -37,7 +25,4 @@ public class ProcessPdfWithOcrRequest extends PDFFile {
allowableValues = {"hocr", "sandwich"}, allowableValues = {"hocr", "sandwich"},
defaultValue = "hocr") defaultValue = "hocr")
private String ocrRenderType = "hocr"; private String ocrRenderType = "hocr";
@Schema(description = "Remove images from the output PDF if set to true")
private boolean removeImagesAfter;
} }

View File

@@ -1,5 +1,6 @@
package stirling.software.SPDF.repository; package stirling.software.SPDF.repository;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
@@ -19,4 +20,6 @@ public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username); Optional<User> findByUsername(String username);
Optional<User> findByApiKey(String apiKey); Optional<User> findByApiKey(String apiKey);
List<User> findByAuthenticationTypeIgnoreCase(String authenticationType);
} }

View File

@@ -39,12 +39,17 @@ public class MetricsAggregatorService {
if (method == null || uri == null) { if (method == null || uri == null) {
return; return;
} }
if (!method.equals("GET") && !method.equals("POST")) {
return;
}
// Skip URIs that are 2 characters or shorter
if (uri.length() <= 2) {
return;
}
String key = String.format( String key =
"http_requests_%s_%s", String.format(
method, "http_requests_%s_%s", method, uri.replace("/", "_"));
uri.replace("/", "_")
);
double currentCount = counter.count(); double currentCount = counter.count();
double lastCount = lastSentMetrics.getOrDefault(key, 0.0); double lastCount = lastSentMetrics.getOrDefault(key, 0.0);

View File

@@ -17,15 +17,18 @@ public class PdfMetadataService {
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private final String stirlingPDFLabel; private final String stirlingPDFLabel;
private final UserServiceInterface userService; private final UserServiceInterface userService;
private final boolean runningEE;
@Autowired @Autowired
public PdfMetadataService( public PdfMetadataService(
ApplicationProperties applicationProperties, ApplicationProperties applicationProperties,
@Qualifier("StirlingPDFLabel") String stirlingPDFLabel, @Qualifier("StirlingPDFLabel") String stirlingPDFLabel,
@Qualifier("runningEE") boolean runningEE,
@Autowired(required = false) UserServiceInterface userService) { @Autowired(required = false) UserServiceInterface userService) {
this.applicationProperties = applicationProperties; this.applicationProperties = applicationProperties;
this.stirlingPDFLabel = stirlingPDFLabel; this.stirlingPDFLabel = stirlingPDFLabel;
this.userService = userService; this.userService = userService;
this.runningEE = runningEE;
} }
public PdfMetadata extractMetadataFromPdf(PDDocument pdf) { public PdfMetadata extractMetadataFromPdf(PDDocument pdf) {
@@ -61,10 +64,8 @@ public class PdfMetadataService {
String creator = stirlingPDFLabel; String creator = stirlingPDFLabel;
if (applicationProperties if (applicationProperties.getEnterpriseEdition().getCustomMetadata().isAutoUpdateMetadata()
.getEnterpriseEdition() && runningEE) {
.getCustomMetadata()
.isAutoUpdateMetadata()) {
creator = applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator(); creator = applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator();
pdf.getDocumentInformation().setProducer(stirlingPDFLabel); pdf.getDocumentInformation().setProducer(stirlingPDFLabel);
@@ -83,10 +84,8 @@ public class PdfMetadataService {
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance()); pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
String author = pdfMetadata.getAuthor(); String author = pdfMetadata.getAuthor();
if (applicationProperties if (applicationProperties.getEnterpriseEdition().getCustomMetadata().isAutoUpdateMetadata()
.getEnterpriseEdition() && runningEE) {
.getCustomMetadata()
.isAutoUpdateMetadata()) {
author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor(); author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor();
if (userService != null) { if (userService != null) {

View File

@@ -71,7 +71,7 @@ public class PostHogService {
Map<String, Object> metrics = new HashMap<>(); Map<String, Object> metrics = new HashMap<>();
try { try {
//Application version // Application version
metrics.put("app_version", appVersion); metrics.put("app_version", appVersion);
String deploymentType = "JAR"; // default String deploymentType = "JAR"; // default
if ("true".equalsIgnoreCase(env.getProperty("BROWSER_OPEN"))) { if ("true".equalsIgnoreCase(env.getProperty("BROWSER_OPEN"))) {

View File

@@ -105,7 +105,7 @@ public class FileToPdf {
new ByteArrayInputStream(Files.readAllBytes(zipFilePath)))) { new ByteArrayInputStream(Files.readAllBytes(zipFilePath)))) {
ZipEntry entry = zipIn.getNextEntry(); ZipEntry entry = zipIn.getNextEntry();
while (entry != null) { while (entry != null) {
Path filePath = tempUnzippedDir.resolve(entry.getName()); Path filePath = tempUnzippedDir.resolve(sanitizeZipFilename(entry.getName()));
if (!entry.isDirectory()) { if (!entry.isDirectory()) {
Files.createDirectories(filePath.getParent()); Files.createDirectories(filePath.getParent());
if (entry.getName().toLowerCase().endsWith(".html") if (entry.getName().toLowerCase().endsWith(".html")
@@ -175,7 +175,7 @@ public class FileToPdf {
ZipSecurity.createHardenedInputStream(new ByteArrayInputStream(fileBytes))) { ZipSecurity.createHardenedInputStream(new ByteArrayInputStream(fileBytes))) {
ZipEntry entry = zipIn.getNextEntry(); ZipEntry entry = zipIn.getNextEntry();
while (entry != null) { while (entry != null) {
Path filePath = tempDirectory.resolve(entry.getName()); Path filePath = tempDirectory.resolve(sanitizeZipFilename(entry.getName()));
if (entry.isDirectory()) { if (entry.isDirectory()) {
Files.createDirectories(filePath); // Explicitly create the directory structure Files.createDirectories(filePath); // Explicitly create the directory structure
} else { } else {
@@ -241,4 +241,14 @@ public class FileToPdf {
Files.deleteIfExists(tempOutputFile); Files.deleteIfExists(tempOutputFile);
} }
} }
static String sanitizeZipFilename(String entryName) {
if (entryName == null || entryName.trim().isEmpty()) {
return entryName;
}
while (entryName.contains("../") || entryName.contains("..\\")) {
entryName = entryName.replace("../", "").replace("..\\", "");
}
return entryName;
}
} }

View File

@@ -29,12 +29,12 @@ public class ProcessExecutor {
public enum Processes { public enum Processes {
LIBRE_OFFICE, LIBRE_OFFICE,
PDFTOHTML, PDFTOHTML,
OCR_MY_PDF,
PYTHON_OPENCV, PYTHON_OPENCV,
GHOSTSCRIPT,
WEASYPRINT, WEASYPRINT,
INSTALL_APP, INSTALL_APP,
CALIBRE CALIBRE,
TESSERACT,
QPDF
} }
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>(); private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
@@ -59,21 +59,11 @@ public class ProcessExecutor {
.getProcessExecutor() .getProcessExecutor()
.getSessionLimit() .getSessionLimit()
.getPdfToHtmlSessionLimit(); .getPdfToHtmlSessionLimit();
case OCR_MY_PDF ->
applicationProperties
.getProcessExecutor()
.getSessionLimit()
.getOcrMyPdfSessionLimit();
case PYTHON_OPENCV -> case PYTHON_OPENCV ->
applicationProperties applicationProperties
.getProcessExecutor() .getProcessExecutor()
.getSessionLimit() .getSessionLimit()
.getPythonOpenCvSessionLimit(); .getPythonOpenCvSessionLimit();
case GHOSTSCRIPT ->
applicationProperties
.getProcessExecutor()
.getSessionLimit()
.getGhostScriptSessionLimit();
case WEASYPRINT -> case WEASYPRINT ->
applicationProperties applicationProperties
.getProcessExecutor() .getProcessExecutor()
@@ -84,6 +74,16 @@ public class ProcessExecutor {
.getProcessExecutor() .getProcessExecutor()
.getSessionLimit() .getSessionLimit()
.getInstallAppSessionLimit(); .getInstallAppSessionLimit();
case TESSERACT ->
applicationProperties
.getProcessExecutor()
.getSessionLimit()
.getTesseractSessionLimit();
case QPDF ->
applicationProperties
.getProcessExecutor()
.getSessionLimit()
.getQpdfSessionLimit();
case CALIBRE -> case CALIBRE ->
applicationProperties applicationProperties
.getProcessExecutor() .getProcessExecutor()
@@ -103,21 +103,11 @@ public class ProcessExecutor {
.getProcessExecutor() .getProcessExecutor()
.getTimeoutMinutes() .getTimeoutMinutes()
.getPdfToHtmlTimeoutMinutes(); .getPdfToHtmlTimeoutMinutes();
case OCR_MY_PDF ->
applicationProperties
.getProcessExecutor()
.getTimeoutMinutes()
.getOcrMyPdfTimeoutMinutes();
case PYTHON_OPENCV -> case PYTHON_OPENCV ->
applicationProperties applicationProperties
.getProcessExecutor() .getProcessExecutor()
.getTimeoutMinutes() .getTimeoutMinutes()
.getPythonOpenCvTimeoutMinutes(); .getPythonOpenCvTimeoutMinutes();
case GHOSTSCRIPT ->
applicationProperties
.getProcessExecutor()
.getTimeoutMinutes()
.getGhostScriptTimeoutMinutes();
case WEASYPRINT -> case WEASYPRINT ->
applicationProperties applicationProperties
.getProcessExecutor() .getProcessExecutor()
@@ -128,6 +118,16 @@ public class ProcessExecutor {
.getProcessExecutor() .getProcessExecutor()
.getTimeoutMinutes() .getTimeoutMinutes()
.getInstallAppTimeoutMinutes(); .getInstallAppTimeoutMinutes();
case TESSERACT ->
applicationProperties
.getProcessExecutor()
.getTimeoutMinutes()
.getTesseractTimeoutMinutes();
case QPDF ->
applicationProperties
.getProcessExecutor()
.getTimeoutMinutes()
.getQpdfTimeoutMinutes();
case CALIBRE -> case CALIBRE ->
applicationProperties applicationProperties
.getProcessExecutor() .getProcessExecutor()

View File

@@ -3,6 +3,10 @@ multipart.enabled=true
logging.level.org.springframework=WARN logging.level.org.springframework=WARN
logging.level.org.hibernate=WARN logging.level.org.hibernate=WARN
logging.level.org.eclipse.jetty=WARN logging.level.org.eclipse.jetty=WARN
#logging.level.org.springframework.security.saml2=TRACE
#logging.level.org.springframework.security=DEBUG
#logging.level.org.opensaml=DEBUG
#logging.level.stirling.software.SPDF.config.security: DEBUG
logging.level.com.zaxxer.hikari=WARN logging.level.com.zaxxer.hikari=WARN
spring.jpa.open-in-view=false spring.jpa.open-in-view=false
@@ -27,6 +31,8 @@ server.servlet.context-path=${SYSTEM_ROOTURIPATH:/}
spring.devtools.restart.enabled=true spring.devtools.restart.enabled=true
spring.devtools.livereload.enabled=true spring.devtools.livereload.enabled=true
spring.devtools.restart.exclude=stirling.software.SPDF.config.security/**
spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.encoding=UTF-8
spring.web.resources.mime-mappings.webmanifest=application/manifest+json spring.web.resources.mime-mappings.webmanifest=application/manifest+json

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=وضع التعرف الضوئي على الحروف
ocr.selectText.11=إزالة الصور بعد التعرف الضوئي على الحروف (يزيل كل الصور، يكون مفيدًا فقط إذا كان جزءًا من خطوة التحويل) ocr.selectText.11=إزالة الصور بعد التعرف الضوئي على الحروف (يزيل كل الصور، يكون مفيدًا فقط إذا كان جزءًا من خطوة التحويل)
ocr.selectText.12=نوع العرض (متقدم) ocr.selectText.12=نوع العرض (متقدم)
ocr.help=يرجى قراءة هذه الوثائق حول كيفية استخدام هذا للغات أخرى و/أو الاستخدام ليس في Docker ocr.help=يرجى قراءة هذه الوثائق حول كيفية استخدام هذا للغات أخرى و/أو الاستخدام ليس في Docker
ocr.credit=تستخدم هذه الخدمة OCRmyPDF و Tesseract للتعرف الضوئي على الحروف. ocr.credit=تستخدم هذه الخدمة qpdf و Tesseract للتعرف الضوئي على الحروف.
ocr.submit=معالجة PDF باستخدام OCR ocr.submit=معالجة PDF باستخدام OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=تحويل إلى PDF
#compress #compress
compress.title=ضغط compress.title=ضغط
compress.header=ضغط ملف PDF compress.header=ضغط ملف PDF
compress.credit=تستخدم هذه الخدمة OCRmyPDF لضغط / تحسين PDF. compress.credit=تستخدم هذه الخدمة qpdf لضغط / تحسين PDF.
compress.selectText.1=الوضع اليدوي - من 1 إلى 4 compress.selectText.1=الوضع اليدوي - من 1 إلى 4
compress.selectText.2=مستوى التحسين: compress.selectText.2=مستوى التحسين:
compress.selectText.3=4 (رهيب للصور النصية) compress.selectText.3=4 (رهيب للصور النصية)
@@ -958,6 +958,8 @@ multiTool.moveLeft=تحريك إلى اليسار
multiTool.moveRight=تحريك إلى اليمين multiTool.moveRight=تحريك إلى اليمين
multiTool.delete=حذف multiTool.delete=حذف
multiTool.dragDropMessage=الصفحات المحددة multiTool.dragDropMessage=الصفحات المحددة
multiTool.undo=تراجع
multiTool.redo=إعادة إجراء
#multiTool-advert #multiTool-advert
multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية! multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=تغيير
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF إلى PDF/A pdfToPDFA.title=PDF إلى PDF/A
pdfToPDFA.header=PDF إلى PDF/A pdfToPDFA.header=PDF إلى PDF/A
pdfToPDFA.credit=تستخدم هذه الخدمة ghostscript لتحويل PDF/A. pdfToPDFA.credit=تستخدم هذه الخدمة qpdf لتحويل PDF/A.
pdfToPDFA.submit=تحويل pdfToPDFA.submit=تحويل
pdfToPDFA.tip=لا يعمل حاليًا لمدخلات متعددة في وقت واحد pdfToPDFA.tip=لا يعمل حاليًا لمدخلات متعددة في وقت واحد
pdfToPDFA.outputFormat=تنسيق الإخراج pdfToPDFA.outputFormat=تنسيق الإخراج
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=مستوى الإشارة المرجعية: اختر مس
splitByChapters.desc.3=تمثيل البيانات الأصلية: إذا تم اختيارها، سترمز البيانات المرجعية الأصلية إلى كل PDF مجزأ. splitByChapters.desc.3=تمثيل البيانات الأصلية: إذا تم اختيارها، سترمز البيانات المرجعية الأصلية إلى كل PDF مجزأ.
splitByChapters.desc.4=سماح بالتكرار: إذا تم اختياره، يسمح بوجود معاينات متعددة في الصفحة نفسها لخلق ملفات PDF منفصلة. splitByChapters.desc.4=سماح بالتكرار: إذا تم اختياره، يسمح بوجود معاينات متعددة في الصفحة نفسها لخلق ملفات PDF منفصلة.
splitByChapters.submit=تقطيع ملف PDF splitByChapters.submit=تقطيع ملف PDF
#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

View File

@@ -34,10 +34,10 @@ sizes.small=Kiçik
sizes.medium=Orta sizes.medium=Orta
sizes.large=Böyük sizes.large=Böyük
sizes.x-large=Ekstra Böyük sizes.x-large=Ekstra Böyük
error.pdfPassword=PDF sənədi şifrələnmişdir və şifrə təmin edilməmişdir və ya yanlışdır. error.pdfPassword=PDF sənədi şifrlənmişdir və şifr təmin edilməmişdir və ya yanlışdır.
delete=Sil delete=Sil
username=İstifadəçi Adı username=İstifadəçi Adı
password=Şifrə password=Şifr
welcome=Xoş gəldiniz welcome=Xoş gəldiniz
property=Xüsusiyyət property=Xüsusiyyət
black=Qara black=Qara
@@ -53,11 +53,11 @@ no=Xeyr
changedCredsMessage=Etibarnamələr dəyişdirildi! changedCredsMessage=Etibarnamələr dəyişdirildi!
notAuthenticatedMessage=İstifadəçinin kimliyi təsdiqlənməyib. notAuthenticatedMessage=İstifadəçinin kimliyi təsdiqlənməyib.
userNotFoundMessage=İstifadəçi tapılmadı. userNotFoundMessage=İstifadəçi tapılmadı.
incorrectPasswordMessage=Cari şifrə yanlışdır. incorrectPasswordMessage=Cari şifr yanlışdır.
usernameExistsMessage=İstifadəçi adı mövcuddur. usernameExistsMessage=İstifadəçi adı mövcuddur.
invalidUsernameMessage=Yanlış istifadəçi adı, istifadəçi adı sadəcə hərflərdən, rəqəmlərdən və @._+- xüsusi simvollarından ibarət ola bilər və ya düzgün email ünvanı olmalıdır. invalidUsernameMessage=Yanlış istifadəçi adı, istifadəçi adı sadəcə hərflərdən, rəqəmlərdən və @._+- xüsusi simvollarından ibarət ola bilər və ya düzgün email ünvanı olmalıdır.
invalidPasswordMessage=Şifrə boş olmamalıdır, başlanğıc və sonunda boşluqdan istifadə edilməməlidir. invalidPasswordMessage=Şifr boş olmamalıdır, başlanğıc və sonunda boşluqdan istifadə edilməməlidir.
confirmPasswordErrorMessage=Yeni Şifrə və Yeni Şifrəni Doğrula uyğun olmalıdır. confirmPasswordErrorMessage=Yeni Şifr və Yeni Şifri Doğrula uyğun olmalıdır.
deleteCurrentUserMessage=Hazırda daxil olmuş istifadəçini silmək mümkün deyil. deleteCurrentUserMessage=Hazırda daxil olmuş istifadəçini silmək mümkün deyil.
deleteUsernameExistsMessage=İstifadəçi adı mövcud deyildir və silinə bilməz. deleteUsernameExistsMessage=İstifadəçi adı mövcud deyildir və silinə bilməz.
downgradeCurrentUserMessage=Cari istifadəçinin rolunu aşağı salmaq mümkün deyil downgradeCurrentUserMessage=Cari istifadəçinin rolunu aşağı salmaq mümkün deyil
@@ -70,8 +70,8 @@ oops=Oops!
help=Yardım help=Yardım
goHomepage=Ana səhifəyə get goHomepage=Ana səhifəyə get
joinDiscord=Discord serverimizə qatıl joinDiscord=Discord serverimizə qatıl
seeDockerHub=Docker Hub'a bax seeDockerHub=Docker Hub-a bax
visitGithub=Github Repository'ə Baş Çək visitGithub=Github Repository-ə Baş Çək
donate=İanə Ver donate=İanə Ver
color=Rəng color=Rəng
sponsor=Sponsor sponsor=Sponsor
@@ -126,9 +126,9 @@ enterpriseEdition.ssoAdvert=Daha çox istifadəçi-idarəetmə xüsusiyyətləri
################# #################
# Analytics # # Analytics #
################# #################
analytics.title=Stirling PDF'i daha yaxşı etmək istəyirsinizmi? analytics.title=Stirling PDF-i daha yaxşı etmək istəyirsinizmi?
analytics.paragraph1=Stirling PDF bizə məhsulu inkişaf etdirməyə kömək etmək üçün analitikaya üstünlük verib. Biz heç bir şəxsi məlumatı və ya fayl məzmununu izləmirik. analytics.paragraph1=Stirling PDF bizə məhsulu inkişaf etdirməyə kömək etmək üçün analitikaya üstünlük verib. Biz heç bir şəxsi məlumatı və ya fayl məzmununu izləmirik.
analytics.paragraph2=Zəhmət olmasa, Stringling-PDF'ə inkişaf etməkdə və istifadəçilərimizi daha yaxşı anlamaqda yardım etmək üçün analitikanı aktivləşdirməyi nəzərə alın. analytics.paragraph2=Zəhmət olmasa, Stringling-PDF-ə inkişaf etməkdə və istifadəçilərimizi daha yaxşı anlamaqda yardım etmək üçün analitikanı aktivləşdirməyi nəzərə alın.
analytics.enable=Analitikanı aktivləşdir analytics.enable=Analitikanı aktivləşdir
analytics.disable=Analitikanı deaktivləşdir analytics.disable=Analitikanı deaktivləşdir
analytics.settings=Analitikanın parametrlərini config/settings.yml faylından dəyişə bilərsiniz. analytics.settings=Analitikanın parametrlərini config/settings.yml faylından dəyişə bilərsiniz.
@@ -141,11 +141,11 @@ navbar.darkmode=Qaranlıq Tema
navbar.language=Dillər navbar.language=Dillər
navbar.settings=Parametrlər navbar.settings=Parametrlər
navbar.allTools=Alətlər navbar.allTools=Alətlər
navbar.multiTool=Çox Alət navbar.multiTool=Multi-Alət
navbar.search=Axtar navbar.search=Axtar
navbar.sections.organize=Təşkil et navbar.sections.organize=Təşkil et
navbar.sections.convertTo=PDF'ə Çevir navbar.sections.convertTo=PDF-ə Çevir
navbar.sections.convertFrom=PDF'dən Çevir navbar.sections.convertFrom=PDF-dən Çevir
navbar.sections.security=İmza & Təhlükəsizlik navbar.sections.security=İmza & Təhlükəsizlik
navbar.sections.advance=Qabaqcıl navbar.sections.advance=Qabaqcıl
navbar.sections.edit=Bax & Redaktə et navbar.sections.edit=Bax & Redaktə et
@@ -171,11 +171,11 @@ settings.cacheInputs.help=Gələcək əməliyyatlar üçün əvvəllər istifad
changeCreds.title=Məlumatları dəyişdirin changeCreds.title=Məlumatları dəyişdirin
changeCreds.header=Hesab Məlumatlarınızı Yeniləyin changeCreds.header=Hesab Məlumatlarınızı Yeniləyin
changeCreds.changePassword=Siz standart giriş məlumatlarından istifadə edirsiniz. Zəhmət olmasa, yeni şifrə daxil edin changeCreds.changePassword=Siz standart giriş məlumatlarından istifadə edirsiniz. Zəhmət olmasa, yeni şifr daxil edin
changeCreds.newUsername=Yeni İstifadəçi Adı changeCreds.newUsername=Yeni İstifadəçi Adı
changeCreds.oldPassword=Cari Şifrə changeCreds.oldPassword=Cari Şifr
changeCreds.newPassword=Yeni Şifrə changeCreds.newPassword=Yeni Şifr
changeCreds.confirmNewPassword=Yeni Şifrəni Təsdiqləyin changeCreds.confirmNewPassword=Yeni Şifri Təsdiqləyin
changeCreds.submit=Dəyişiklikləri Təsdiqlə changeCreds.submit=Dəyişiklikləri Təsdiqlə
@@ -186,11 +186,11 @@ account.adminSettings=Admin Paramterləri - İstifadəçilər Əlavə Et və Onl
account.userControlSettings=İstifadəçi İdarəetmə Parametrləri account.userControlSettings=İstifadəçi İdarəetmə Parametrləri
account.changeUsername=İstifadəçi Adını Dəyiş account.changeUsername=İstifadəçi Adını Dəyiş
account.newUsername=Yeni İstifadəçi Adı account.newUsername=Yeni İstifadəçi Adı
account.password=Təsdiqləmə Şifrəsi account.password=Təsdiqləmə Şifri
account.oldPassword=Keçmiş Şifrə account.oldPassword=Keçmiş Şifr
account.newPassword=Yeni Şifrə account.newPassword=Yeni Şifr
account.changePassword=Şifrəni Dəyiş account.changePassword=Şifri Dəyiş
account.confirmNewPassword=Yeni Şifrəni Təsdiqlə account.confirmNewPassword=Yeni Şifri Təsdiqlə
account.signOut=Çıxış account.signOut=Çıxış
account.yourApiKey=Sizin API Açarınız account.yourApiKey=Sizin API Açarınız
account.syncTitle=Brauzer parametrlərini hesabla sinxronlaşdırın account.syncTitle=Brauzer parametrlərini hesabla sinxronlaşdırın
@@ -543,7 +543,7 @@ login.title=Daxil olun
login.header=Daxil olun login.header=Daxil olun
login.signin=Daxil olun login.signin=Daxil olun
login.rememberme=Məni xatırla login.rememberme=Məni xatırla
login.invalid=Etibarsız istifadəçi adı və ya şifrə. login.invalid=Etibarsız istifadəçi adı və ya şifr.
login.locked=Sizin hesabınız kilidlənmişdir. login.locked=Sizin hesabınız kilidlənmişdir.
login.signinTitle=Zəhmət olmasa, daxil olun login.signinTitle=Zəhmət olmasa, daxil olun
login.ssoSignIn=Single Sign-on vasitəsilə daxil olun login.ssoSignIn=Single Sign-on vasitəsilə daxil olun
@@ -581,8 +581,8 @@ showJS.submit=Göstər
#pdfToSinglePage #pdfToSinglePage
pdfToSinglePage.title=PDF'dən Tək Səhifəyə pdfToSinglePage.title=PDF-dən Tək Səhifəyə
pdfToSinglePage.header=PDF'dən Tək Səhifəyə pdfToSinglePage.header=PDF-dən Tək Səhifəyə
pdfToSinglePage.submit=Tək Səhifəyə Çevir pdfToSinglePage.submit=Tək Səhifəyə Çevir
@@ -601,8 +601,8 @@ getPdfInfo.downloadJson=JSON yüklə
#markdown-to-pdf #markdown-to-pdf
MarkdownToPDF.title=Markdown'dan PDF'ə MarkdownToPDF.title=Markdown-dan PDF-ə
MarkdownToPDF.header=Markdown'dan PDF'ə MarkdownToPDF.header=Markdown-dan PDF-ə
MarkdownToPDF.submit=Çevir MarkdownToPDF.submit=Çevir
MarkdownToPDF.help=İş davam edir MarkdownToPDF.help=İş davam edir
MarkdownToPDF.credit=WeasyPrint İstifadə Edir MarkdownToPDF.credit=WeasyPrint İstifadə Edir
@@ -617,8 +617,8 @@ URLToPDF.credit=WeasyPrint İstifadə Edir
#html-to-pdf #html-to-pdf
HTMLToPDF.title=HTML'dən PDF'ə HTMLToPDF.title=HTML-dən PDF-ə
HTMLToPDF.header=HTML'dən PDF'ə HTMLToPDF.header=HTML-dən PDF-ə
HTMLToPDF.help=HTML fayllarını və tərkibində mütləq html/css/images və s. olan ZIP fayllarını qəbul edir HTMLToPDF.help=HTML fayllarını və tərkibində mütləq html/css/images və s. olan ZIP fayllarını qəbul edir
HTMLToPDF.submit=Çevir HTMLToPDF.submit=Çevir
HTMLToPDF.credit=WeasyPrint İstifadə Edir HTMLToPDF.credit=WeasyPrint İstifadə Edir
@@ -656,14 +656,14 @@ AddStampRequest.submit=Təsdiqlə
#sanitizePDF #sanitizePDF
sanitizePDF.title=PDF'i Təmizlə sanitizePDF.title=PDF-i Təmizlə
sanitizePDF.header=PDF Faylını Təmizlə sanitizePDF.header=PDF Faylını Təmizlə
sanitizePDF.selectText.1=JavaScript Fəaliyyətlərini Sil sanitizePDF.selectText.1=JavaScript Fəaliyyətlərini Sil
sanitizePDF.selectText.2=Daxil Edilmiş Faylları Sil sanitizePDF.selectText.2=Daxil Edilmiş Faylları Sil
sanitizePDF.selectText.3=Metadatanı Sil sanitizePDF.selectText.3=Metadatanı Sil
sanitizePDF.selectText.4=Linkləri Sil sanitizePDF.selectText.4=Linkləri Sil
sanitizePDF.selectText.5=Şriftləri Sil sanitizePDF.selectText.5=Şriftləri Sil
sanitizePDF.submit=PDF'i Təmizlə sanitizePDF.submit=PDF-i Təmizlə
#addPageNumbers #addPageNumbers
@@ -683,7 +683,7 @@ addPageNumbers.submit=Səhifə Nömrələri əlavə edin
#auto-rename #auto-rename
auto-rename.title=Avtomatik Yenidən Adlandır auto-rename.title=Avtomatik Yenidən Adlandır
auto-rename.header=Pdf'in Adını Avtomatik Yenidən Adlandır auto-rename.header=Pdf-in Adını Avtomatik Yenidən Adlandır
auto-rename.submit=Avtomatik Yenidən Adlandır auto-rename.submit=Avtomatik Yenidən Adlandır
@@ -698,7 +698,7 @@ adjustContrast.download=Yüklə
#crop #crop
crop.title=Kəs crop.title=Kəs
crop.header=Pdf'ləri Kəs crop.header=Pdf-ləri Kəs
crop.submit=Təsdiq Et crop.submit=Təsdiq Et
@@ -722,8 +722,8 @@ pipeline.title=Pipeline
#pageLayout #pageLayout
pageLayout.title=Çoxsəhifəli Sxem pageLayout.title=Çoxsəhifəli Tərtibat
pageLayout.header=Çoxsəhifəli Sxem pageLayout.header=Çoxsəhifəli Tərtibat
pageLayout.pagesPerSheet=Vərəqdəki Səhifə Sayı: pageLayout.pagesPerSheet=Vərəqdəki Səhifə Sayı:
pageLayout.addBorder=Çərçivə Əlavə Et pageLayout.addBorder=Çərçivə Əlavə Et
pageLayout.submit=Təsdiq et pageLayout.submit=Təsdiq et
@@ -739,22 +739,22 @@ scalePages.submit=Təsdiq edin
#certSign #certSign
certSign.title=Certificate Signing certSign.title=Sertifikatla İmzala
certSign.header=Sign a PDF with your certificate (Work in progress) certSign.header=PDF-i Sertifikatınızla İmzalayın (İşlənilir)
certSign.selectPDF=Select a PDF File for Signing: certSign.selectPDF=İmzalamaq üçün PDF Faylı seçin:
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below. certSign.jksNote=Note: Əgər sertifikatınızın tipi aşağıda göstərilməyibsə, zəhmət olmasa "Keytool command line tool" istifadə edərək onu "Java Keystroke" (.jks) faylına çevirin. Sonra, aşağıdan .jks faylını seçin.
certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der): certSign.selectKey=Şəxsi Açar faylınızı seçin (PKCS#8 format, .pem və ya .der ola bilər):
certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der): certSign.selectCert=Sertifikat faylınızı seçin (X.509 format, .pem və ya .der ola bilər):
certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate): certSign.selectP12=PKCS#12 Keystore Faylınızı seçin (.p12 və ya .pfx) (İstəyə bağlı, əgər təmin olunarsa, şəxsi açar və sertifikatınızı ehtiva etməlidir):
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore): certSign.selectJKS=Java Keystore Faylınızı seçin (.jks və ya .keystore):
certSign.certType=Certificate Type certSign.certType=Sertifikat Tipi
certSign.password=Enter Your Keystore or Private Key Password (If Any): certSign.password=Keystore və ya Şəxsi Açar daxil edin (Əgər varsa):
certSign.showSig=Show Signature certSign.showSig=İmzanı Göstər
certSign.reason=Reason certSign.reason=Səbəb
certSign.location=Location certSign.location=Məkan
certSign.name=Name certSign.name=Ad
certSign.showLogo=Show Logo certSign.showLogo=Loqonu Göstər
certSign.submit=Sign PDF certSign.submit=PDF-i İmzala
#removeCertSign #removeCertSign
@@ -765,13 +765,13 @@ removeCertSign.submit=İmzanı silin
#removeBlanks #removeBlanks
removeBlanks.title=Remove Blanks removeBlanks.title=Boş Səhifələri Sil
removeBlanks.header=Remove Blank Pages removeBlanks.header=Boş SƏhifələri Silir
removeBlanks.threshold=Pixel Whiteness Threshold: removeBlanks.threshold=Minimal Piksel Bəyazlığı:
removeBlanks.thresholdDesc=Threshold for determining how white a white pixel must be to be classed as 'White'. 0 = Black, 255 pure white. removeBlanks.thresholdDesc=Pikselin "Ağ" hesab olunması üçün minimal nə qədər bəyaz olmalı olduğunu təyin edin. 0 = Qara, 255 Ağappaq.
removeBlanks.whitePercent=White Percent (%): removeBlanks.whitePercent=Bəyaz Faizi (%):
removeBlanks.whitePercentDesc=Percent of page that must be 'white' pixels to be removed removeBlanks.whitePercentDesc=Silinmək üçün səhifənin neçə faizi "ağ" piksellərdən təşkil olunmalıdır
removeBlanks.submit=Remove Blanks removeBlanks.submit=Boş Səhifələri Sil
#removeAnnotations #removeAnnotations
@@ -781,16 +781,16 @@ removeAnnotations.submit=Sil
#compare #compare
compare.title=Compare compare.title=Müqayisə Et
compare.header=Compare PDFs compare.header=PDF-ləri Müqayisə Et
compare.highlightColor.1=Highlight Color 1: compare.highlightColor.1=Önə Çıxarma Rəngi 1:
compare.highlightColor.2=Highlight Color 2: compare.highlightColor.2=Önə Çıxarma Rəngi 2:
compare.document.1=Document 1 compare.document.1=Sənəd 1
compare.document.2=Document 2 compare.document.2=Sənəd 2
compare.submit=Compare compare.submit=Müqayisə Et
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced compare.complex.message=Fayllardan biri və ya ikisi də böyük fayldır. Müqayisə effektivliyi azala bilər.
compare.large.file.message=One or Both of the provided documents are too large to process compare.large.file.message=Fayllardan biri və ya ikisi də işləmək üçün çox böyükdür.
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. compare.no.text.message=Fayllardan birində və ya ikisində də mətn məzmunu yoxdur. Zəhmət olmasa, müqayisə üçün mətn məzmunlu PDF seçin.
#BookToPDF #BookToPDF
BookToPDF.title=Kitabları və Komiksləri PDF-ə BookToPDF.title=Kitabları və Komiksləri PDF-ə
@@ -818,12 +818,12 @@ sign.save=İmzanı yadda Saxla
sign.personalSigs=Şəxsi İmzalar sign.personalSigs=Şəxsi İmzalar
sign.sharedSigs=Paylaşılan İmzalar sign.sharedSigs=Paylaşılan İmzalar
sign.noSavedSigs=Saxlanmış imza tapılmadı sign.noSavedSigs=Saxlanmış imza tapılmadı
sign.addToAll=Add to all pages sign.addToAll=Bütün səhiflərə əlavə et
sign.delete=Delete sign.delete=Sil
sign.first=First page sign.first=İlk səhifə
sign.last=Last page sign.last=Son səhifə
sign.next=Next page sign.next=Növbəti səhifə
sign.previous=Previous page sign.previous=Əvvəlki səhifə
#repair #repair
repair.title=Bərpa Et repair.title=Bərpa Et
@@ -839,37 +839,37 @@ flatten.submit=Düzləşdirin
#ScannerImageSplit #ScannerImageSplit
ScannerImageSplit.selectText.1=Angle Threshold: ScannerImageSplit.selectText.1=Bucaq Aşağı Limiti:
ScannerImageSplit.selectText.2=Sets the minimum absolute angle required for the image to be rotated (default: 10). ScannerImageSplit.selectText.2=Şəklin fırladılması üçün lazım olan minimal mütləq bucağı təyin edir (defolt: 10).
ScannerImageSplit.selectText.3=Tolerance: ScannerImageSplit.selectText.3=Rəng Toleransı:
ScannerImageSplit.selectText.4=Determines the range of color variation around the estimated background color (default: 30). ScannerImageSplit.selectText.4=Təxmin olunan arxaplan rənginin ətrafındakı rəng fərqliliyi intervalını təyin edir (defolt: 30).
ScannerImageSplit.selectText.5=Minimum Area: ScannerImageSplit.selectText.5=Minimal Sahə:
ScannerImageSplit.selectText.6=Sets the minimum area threshold for a photo (default: 10000). ScannerImageSplit.selectText.6=Foto üçün minimal sahənin aşağı limitini təyin edir (defolt: 10000).
ScannerImageSplit.selectText.7=Minimum Contour Area: ScannerImageSplit.selectText.7=Minimal Kontur Sahəsi:
ScannerImageSplit.selectText.8=Sets the minimum contour area threshold for a photo ScannerImageSplit.selectText.8=Fotonun kontur sahəsi üçün minimal aşağı limiti təyin edir
ScannerImageSplit.selectText.9=Border Size: ScannerImageSplit.selectText.9=Sərhəd Ölçüsü:
ScannerImageSplit.selectText.10=Sets the size of the border added and removed to prevent white borders in the output (default: 1). ScannerImageSplit.selectText.10=Faylda ağ sərhədlərin olmasının qarşısını almaq üçün əlavə ediləcək sərhədin ölçüsünü təyin edir (defolt: 1).
ScannerImageSplit.info=Python is not installed. It is required to run. ScannerImageSplit.info=Python yüklənməyib. İşə salmaq üçün Python lazımdır.
#OCR #OCR
ocr.title=OCR / Scan Cleanup ocr.title=OST (OCR) / Skan Təmizləmə
ocr.header=Cleanup Scans / OCR (Optical Character Recognition) ocr.header=Skanları Təmizlə / OST (Optik Simvol Tanınması)
ocr.selectText.1=Select languages that are to be detected within the PDF (Ones listed are the ones currently detected): ocr.selectText.1=PDF-də aşkar olunacaq dilləri seçin (Göstərilmiş dillər hazırda aşkar olunmuşlardır):
ocr.selectText.2=Produce text file containing OCR text alongside the OCR'ed PDF ocr.selectText.2=OST-lənmiş PDF ilə yanaşı daxilində OST edilmiş mətn olan PDF yaradın
ocr.selectText.3=Correct pages were scanned at a skewed angle by rotating them back into place ocr.selectText.3=Əyri skan olunmuş səhifələri yerinə fırladaraq düzəldin
ocr.selectText.4=Clean page so its less likely that OCR will find text in background noise. (No output change) ocr.selectText.4=OST-in arxaplandakı artıq mətni aşkar etməsinin qarşısını almaq üçün səhifəni təmizləyin. (Çıxış dəyişmir)
ocr.selectText.5=Clean page so its less likely that OCR will find text in background noise, maintains cleanup in output. ocr.selectText.5=OST-in arxaplandakı artıq mətni aşkar etməsinin qarşısını almaq üçün səhifəni təmizləyin, təmizləməni çıxışa verilən faylda saxlayır.
ocr.selectText.6=Ignores pages that have interactive text on them, only OCRs pages that are images ocr.selectText.6=Üzərində interaktiv yazı olan səhifələri nəzərə almır, yalnız şəkil olan səhifələri OST edir.
ocr.selectText.7=Force OCR, will OCR Every page removing all original text elements ocr.selectText.7=OST-ə məcbur et, bütün orijinal mətn elementlərini silərək hər səhifəni OST edir
ocr.selectText.8=Normal (Will error if PDF contains text) ocr.selectText.8=Normal (PDF-də mətn varsa, xəta verəcək)
ocr.selectText.9=Additional Settings ocr.selectText.9=Əlavə Parametrlər
ocr.selectText.10=OCR Mode ocr.selectText.10=OST (OCR) Rejimi
ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step) ocr.selectText.11=OST-dən sonra şəkilləri sil (BÜTÜN şəkilləri silir, ancaq çevirmə prosesinin bir hissəsi olduqda işə yarayır)
ocr.selectText.12=Render Type (Advanced) ocr.selectText.12=Render Tipi (Qabaqcıl)
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker ocr.help=Bunu digər dillər üçün necə istifadə etmək və/və ya docker-də istifadə etməmək üçün bu dokumentasiyanı oxuyun
ocr.credit=This service uses OCRmyPDF and Tesseract for OCR. ocr.credit=Bu servis OST (OCR) üçün "OCRmyPDF" və "Tesseract" istifadə edir.
ocr.submit=Process PDF with OCR ocr.submit=PDF-i OST ilə işlə
#extractImages #extractImages
@@ -890,15 +890,15 @@ fileToPDF.submit=PDF-ə Çevir
#compress #compress
compress.title=Compress compress.title=Sıxışdır
compress.header=Compress PDF compress.header=PDF-i Sıxışdır
compress.credit=This service uses Ghostscript for PDF Compress/Optimisation. compress.credit=Bu servis PDF sıxışdırılması/Optimizasiyası üçün Ghostscript istifadə edir.
compress.selectText.1=Manual Mode - From 1 to 4 compress.selectText.1=Manual Mod - 1-dən 4-ə
compress.selectText.2=Optimization level: compress.selectText.2=Optimizasiya səviyyəsi:
compress.selectText.3=4 (Terrible for text images) compress.selectText.3=4 (Mətn şəkilləri üçün yaxşı deyil)
compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size compress.selectText.4=Avto mod - PDF-in dəqiq ölçüsünü əldə etmək üçün keyfiyyəti avtomatik tənzimləyir
compress.selectText.5=Expected PDF Size (e.g. 25MB, 10.8MB, 25KB) compress.selectText.5=Gözlənilən PDF Ölçüsü (məsələn, 25MB, 10.8MB, 25KB)
compress.submit=Compress compress.submit=Sıxışdır
#Add image #Add image
@@ -919,35 +919,35 @@ merge.submit=Birləşdirin
#pdfOrganiser #pdfOrganiser
pdfOrganiser.title=Page Organiser pdfOrganiser.title=Səhifə Tənzimləyicisi
pdfOrganiser.header=PDF Page Organiser pdfOrganiser.header=PDF Səhifə Tənzimləyicisi
pdfOrganiser.submit=Rearrange Pages pdfOrganiser.submit=Səhifələri Yenidən Təşkil Edin
pdfOrganiser.mode=Mode pdfOrganiser.mode=Rejim
pdfOrganiser.mode.1=Custom Page Order pdfOrganiser.mode.1=Fərdi Səhifə Düzülüşü
pdfOrganiser.mode.2=Reverse Order pdfOrganiser.mode.2=Tərs Düzülüş
pdfOrganiser.mode.3=Duplex Sort pdfOrganiser.mode.3=İkitərəfli Çeşidləmə
pdfOrganiser.mode.4=Booklet Sort pdfOrganiser.mode.4=Kitabça Çeşidləmə
pdfOrganiser.mode.5=Side Stitch Booklet Sort pdfOrganiser.mode.5=Yan Tikiş Kitabçasının Çeşidlənməsi
pdfOrganiser.mode.6=Odd-Even Split pdfOrganiser.mode.6=Tək-Cüt Bölünmə
pdfOrganiser.mode.7=Remove First pdfOrganiser.mode.7=Birincini Sil
pdfOrganiser.mode.8=Remove Last pdfOrganiser.mode.8=Sonuncunu Sil
pdfOrganiser.mode.9=Remove First and Last pdfOrganiser.mode.9=Birinci və Sonuncunu Sil
pdfOrganiser.mode.10=Odd-Even Merge pdfOrganiser.mode.10=Tək-Cüt Birləşdirmə
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1) pdfOrganiser.placeholder=(məs., 1,3,2 və ya 4-8,2,10-12 və ya 2n-1)
#multiTool #multiTool
multiTool.title=PDF Multi Tool multiTool.title=PDF Multi-Alət
multiTool.header=PDF Multi Tool multiTool.header=PDF Multi-Alət
multiTool.uploadPrompts=File Name multiTool.uploadPrompts=Fayl Adı
multiTool.selectAll=Select All multiTool.selectAll=Hamısını Seç
multiTool.deselectAll=Deselect All multiTool.deselectAll=Hamısını Seçməni Ləğv Et
multiTool.selectPages=Page Select multiTool.selectPages=Səhifə Seçimi
multiTool.selectedPages=Selected Pages multiTool.selectedPages=Seçilmiş Səhifələr
multiTool.page=Page multiTool.page=Səhifə
multiTool.deleteSelected=Delete Selected multiTool.deleteSelected=Seçilmişi Sil
multiTool.downloadAll=Export multiTool.downloadAll=İxrac Et
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=Seçilmişi İxrac Et
multiTool.insertPageBreak=Insert Page Break multiTool.insertPageBreak=Insert Page Break
multiTool.addFile=Add File multiTool.addFile=Add File
@@ -957,10 +957,12 @@ multiTool.split=Split
multiTool.moveLeft=Move Left multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Seçilmiş Səhifə(lər)
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=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 #view pdf
viewPdf.title=PDF-ə baxın viewPdf.title=PDF-ə baxın
@@ -982,32 +984,32 @@ rotate.submit=Fırladın
#split-pdfs #split-pdfs
split.title=Split PDF split.title=PDF-i Bölün
split.header=Split PDF split.header=PDF-i Bölün
split.desc.1=The numbers you select are the page number you wish to do a split on split.desc.1=Seçdiyiniz Nömrələr Bölmək İstədiyiniz Səhifə Nömrəsidir
split.desc.2=As such selecting 1,3,7-9 would split a 10 page document into 6 separate PDFS with: split.desc.2=Beləliklə, 1,3,7-9 Seçimi 10 Səhifəlik Sənədi 6 Ayrı PDF-ə Böləcək:
split.desc.3=Document #1: Page 1 split.desc.3=Sənəd #1: Səhifə 1
split.desc.4=Document #2: Page 2 and 3 split.desc.4=Sənəd #2: Səhifə 2 və 3
split.desc.5=Document #3: Page 4, 5, 6 and 7 split.desc.5=Sənəd #3: Səhifə 4, 5, 6 7
split.desc.6=Document #4: Page 8 split.desc.6=Sənəd #4: Səhifə 8
split.desc.7=Document #5: Page 9 split.desc.7=Sənəd #5: Səhifə 9
split.desc.8=Document #6: Page 10 split.desc.8=Sənəd #6: Səhifə 10
split.splitPages=Enter pages to split on: split.splitPages=Bölünəcək Səhifələri Daxil Edin:
split.submit=Split split.submit=Bölün
#merge #merge
imageToPDF.title=Image to PDF imageToPDF.title=Şəkli PDF
imageToPDF.header=Image to PDF imageToPDF.header=Şəkli PDF
imageToPDF.submit=Convert imageToPDF.submit=Çevir
imageToPDF.selectLabel=Image Fit Options imageToPDF.selectLabel=Şəkil Uyğunluğu Seçimləri
imageToPDF.fillPage=Fill Page imageToPDF.fillPage=Səhifəni Doldur
imageToPDF.fitDocumentToImage=Fit Page to Image imageToPDF.fitDocumentToImage=Şəklə Uyğun Səhifə
imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.maintainAspectRatio=Aspekt Nisbətlərini Qoruyun
imageToPDF.selectText.2=Auto rotate PDF imageToPDF.selectText.2=PDF-i Avtomatik Fırlat
imageToPDF.selectText.3=Multi file logic (Only enabled if working with multiple images) imageToPDF.selectText.3=Çoxsaylı Fayl Məntiqi (Yalnız Birdən Çox Şəkil İlə İşləyərkən Aktivdir)
imageToPDF.selectText.4=Merge into single PDF imageToPDF.selectText.4=Tək Bir PDF-ə Birləşdir
imageToPDF.selectText.5=Convert to separate PDFs imageToPDF.selectText.5=Ayrı PDF-lərə Çevirin
#pdfToImage #pdfToImage
@@ -1026,97 +1028,97 @@ pdfToImage.info=Python Yüklü Deyil.WebP Çevirməsi Üçün Vacibdir
#addPassword #addPassword
addPassword.title=Add Password addPassword.title=Şifr Əlavə Et
addPassword.header=Add password (Encrypt) addPassword.header=Şifr Əlavə Et (Şifrləmə)
addPassword.selectText.1=Select PDF to encrypt addPassword.selectText.1=Şifrlənəcək PDF-i seç
addPassword.selectText.2=User Password addPassword.selectText.2=İstifadəçi Şifri
addPassword.selectText.3=Encryption Key Length addPassword.selectText.3=Şifrləmə Açarı Uzunluğu
addPassword.selectText.4=Higher values are stronger, but lower values have better compatibility. addPassword.selectText.4=Böyük dəyərlər daha güclüdür, lakin kiçik dəyərlərin uyğunluğu yüksəkdir.
addPassword.selectText.5=Permissions to set (Recommended to be used along with Owner password) addPassword.selectText.5=Təyin olunacaq icazə (Sahib (Owner) Şifri ilə birgə istifadə olunması tövsiyə olunur.)
addPassword.selectText.6=Prevent assembly of document addPassword.selectText.6=Sənədin strukturunun dəyişilməsinin qarşısını al
addPassword.selectText.7=Prevent content extraction addPassword.selectText.7=Məzmun xaric edilməsinin qarşısını al
addPassword.selectText.8=Prevent extraction for accessibility addPassword.selectText.8=Əlçatanlıq üçün xaricetmənin qarşısını al
addPassword.selectText.9=Prevent filling in form addPassword.selectText.9=Anketin doldurulmasının qarşısını al
addPassword.selectText.10=Prevent modification addPassword.selectText.10=Modifikasiyanın qarşısını al
addPassword.selectText.11=Prevent annotation modification addPassword.selectText.11=Sitat modifikasiyasının qarşısını al
addPassword.selectText.12=Prevent printing addPassword.selectText.12=Çap etmənin qarşısını al
addPassword.selectText.13=Prevent printing different formats addPassword.selectText.13=Müxtəlif formatların çap edilməsinin qarşısını al
addPassword.selectText.14=Owner Password addPassword.selectText.14=Sahib Şifri
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) addPassword.selectText.15=Sənəd açıldıqdan sonra onunla nə edilə biləcəyini limitləndir (Bütün oxuyucular dəstəkləmir)
addPassword.selectText.16=Restricts the opening of the document itself addPassword.selectText.16=Sənədin özünün açılmağını limitləndirir
addPassword.submit=Encrypt addPassword.submit=Şifrlə
#watermark #watermark
watermark.title=Add Watermark watermark.title=Watermark Əlavə Et
watermark.header=Add Watermark watermark.header=Watermark Əlavə Et
watermark.selectText.1=Select PDF to add watermark to: watermark.selectText.1=Watermark əlavə olunacaq PDF-i seç
watermark.selectText.2=Watermark Text: watermark.selectText.2=Watermark Mətni:
watermark.selectText.3=Font Size: watermark.selectText.3=Şrift Ölçüsü:
watermark.selectText.4=Rotation (0-360): watermark.selectText.4=Fırlatma (0-360):
watermark.selectText.5=widthSpacer (Space between each watermark horizontally): watermark.selectText.5=enBoşluq (Üfuqi olaraq watermark-lar arasındakı məsafə):
watermark.selectText.6=heightSpacer (Space between each watermark vertically): watermark.selectText.6=uzunluqBoşluq (Şaquli olaraq watermark-lar arasındakı məsafə):
watermark.selectText.7=Opacity (0% - 100%): watermark.selectText.7=Şəffaflıq (0% - 100%):
watermark.selectText.8=Watermark Type: watermark.selectText.8=Watermark Tipi:
watermark.selectText.9=Watermark Image: watermark.selectText.9=Watermark Şəkili:
watermark.selectText.10=Convert PDF to PDF-Image watermark.selectText.10=PDF-i PDF-Şəkil-ə çevir
watermark.submit=Add Watermark watermark.submit=Watermark Əlavə Et
watermark.type.1=Text watermark.type.1=Mətn
watermark.type.2=Image watermark.type.2=Şəkil
#Change permissions #Change permissions
permissions.title=Change Permissions permissions.title=İcazələri Dəyişdir
permissions.header=Change Permissions permissions.header=İcazələri Dəyişdir
permissions.warning=Warning to have these permissions be unchangeable it is recommended to set them with a password via the add-password page permissions.warning=Bu İcazələrin Dəyişməz Olması İlə Bağlı Xəbərdarlıq Edərək, Onları Parol Əlavə Et Səhifəsi Vasitəsilə Parolla Təyin Etmək Tövsiyə Olunur.
permissions.selectText.1=Select PDF to change permissions permissions.selectText.1=İcazələri Dəyişdirmək Üçün PDF-i Seç
permissions.selectText.2=Permissions to set permissions.selectText.2=Tənzimlənmiş İcazələr
permissions.selectText.3=Prevent assembly of document permissions.selectText.3=Sənədin Yığılmasının Qarşısını Al
permissions.selectText.4=Prevent content extraction permissions.selectText.4=Məzmunun Çıxarılmasının Qarşısını Al
permissions.selectText.5=Prevent extraction for accessibility permissions.selectText.5=Əlçatanlıq Üçün Çıxarılmasının Qarşısını Alın
permissions.selectText.6=Prevent filling in form permissions.selectText.6=Formanın Doldurulmasının Qarşısını Alır
permissions.selectText.7=Prevent modification permissions.selectText.7=Modifikasiyanın Qarşısını Al
permissions.selectText.8=Prevent annotation modification permissions.selectText.8=Annotasiyanın Dəyişdirilməsinin Qarşısını Almaq
permissions.selectText.9=Prevent printing permissions.selectText.9=Çapın Qarşısını Al
permissions.selectText.10=Prevent printing different formats permissions.selectText.10=Fərqli Formatlarda Çapın Qarşısını Al
permissions.submit=Change permissions.submit=Dəyiş
#remove password #remove password
removePassword.title=Remove password removePassword.title=Şifri Sil
removePassword.header=Remove password (Decrypt) removePassword.header=Şifri Sil (Deşifr)
removePassword.selectText.1=Select PDF to Decrypt removePassword.selectText.1=Deşifr Üçün PDF-i Seç
removePassword.selectText.2=Password removePassword.selectText.2=Şifr
removePassword.submit=Remove removePassword.submit=Sil
#changeMetadata #changeMetadata
changeMetadata.title=Change Metadata changeMetadata.title=Metadata-nı Dəyiş
changeMetadata.header=Change Metadata changeMetadata.header=Metadata-nı Dəyiş
changeMetadata.selectText.1=Please edit the variables you wish to change changeMetadata.selectText.1=Dəyişmək istədiyiniz dəyişənləri redaktə edin
changeMetadata.selectText.2=Delete all metadata changeMetadata.selectText.2=Bütün Metadata-nı Sil
changeMetadata.selectText.3=Show Custom Metadata: changeMetadata.selectText.3=Fərdi Metadatanı göstərin:
changeMetadata.author=Author: changeMetadata.author=Müəllif:
changeMetadata.creationDate=Creation Date (yyyy/MM/dd HH:mm:ss): changeMetadata.creationDate=Yaradılma Tarixi (yyyy/MM/dd HH:mm:ss):
changeMetadata.creator=Creator: changeMetadata.creator=Yaradıcı:
changeMetadata.keywords=Keywords: changeMetadata.keywords=Açar Sözlər:
changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss): changeMetadata.modDate=Dəyişiklik Tarixi (yyyy/MM/dd HH:mm:ss):
changeMetadata.producer=Producer: changeMetadata.producer=İstehsalçı:
changeMetadata.subject=Subject: changeMetadata.subject=Mövzu:
changeMetadata.trapped=Trapped: changeMetadata.trapped=Tələ:
changeMetadata.selectText.4=Other Metadata: changeMetadata.selectText.4=Digər Metadata:
changeMetadata.selectText.5=Add Custom Metadata Entry changeMetadata.selectText.5=Xüsusi Metadata girişi əlavə edin
changeMetadata.submit=Change changeMetadata.submit=Dəyiş
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF To PDF/A pdfToPDFA.title=PDF-i PDF/A-ya
pdfToPDFA.header=PDF To PDF/A pdfToPDFA.header=PDF-i PDF/A-ya
pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion pdfToPDFA.credit=Bu Servis PDF/A Çevirmək Üçün ghostscript İşlədir
pdfToPDFA.submit=Convert pdfToPDFA.submit=Çevir
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Hazırda Birdən Çox Giriş Üçün İşləmir
pdfToPDFA.outputFormat=Output format pdfToPDFA.outputFormat=Çıxış Formatı
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step. pdfToPDFA.pdfWithDigitalSignature=PDF Rəqəmsal İmza Ehtiva Edir.Bu, növbəti addımda silinəcək.
#PDFToWord #PDFToWord
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Səviyyəsi: Bölmə üçün istifadə ediləcə
splitByChapters.desc.3=Metadatanı daxil edin: Əgər yoxlanılıbsa, orijinal PDF-in metadatası hər bir bölünmüş PDF-ə daxil ediləcək. splitByChapters.desc.3=Metadatanı daxil edin: Əgər yoxlanılıbsa, orijinal PDF-in metadatası hər bir bölünmüş PDF-ə daxil ediləcək.
splitByChapters.desc.4=Allow Duplicates: Dublikatlara icazə verin: Əgər işarələnərsə, eyni səhifədə birdən çox bookmarka ayrı-ayrı PDF sənədləri yaratmağa icazə verin. splitByChapters.desc.4=Allow Duplicates: Dublikatlara icazə verin: Əgər işarələnərsə, eyni səhifədə birdən çox bookmarka ayrı-ayrı PDF sənədləri yaratmağa icazə verin.
splitByChapters.submit=PDF-i Ayır splitByChapters.submit=PDF-i Ayır
#release notes
releases.footer=Buraxılışlar
releases.title=Buraxılış Qeydləri
releases.header=Buraxılış Qeydləri
releases.current.version=Hazırki Buraxılış
releases.note=Buraxılış Qeydləri yalnız ingiliscə mövcuddur

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR режим
ocr.selectText.11=Премахване на изображения след OCR (Премахва ВСИЧКИ изображения, полезно само ако е част от стъпката на преобразуване) ocr.selectText.11=Премахване на изображения след OCR (Премахва ВСИЧКИ изображения, полезно само ако е част от стъпката на преобразуване)
ocr.selectText.12=Тип изобразяване (Разширен) ocr.selectText.12=Тип изобразяване (Разширен)
ocr.help=Моля, прочетете тази документация за това как да използвате това за други езици и/или да не използвате в docker ocr.help=Моля, прочетете тази документация за това как да използвате това за други езици и/или да не използвате в docker
ocr.credit=Тази услуга използва OCRmyPDF и Tesseract за OCR. ocr.credit=Тази услуга използва qpdf и Tesseract за OCR.
ocr.submit=Обработка на PDF чрез OCR ocr.submit=Обработка на PDF чрез OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Преобразуване към PDF
#compress #compress
compress.title=Компресиране compress.title=Компресиране
compress.header=Компресиране на PDF compress.header=Компресиране на PDF
compress.credit=Тази услуга използва Ghostscript за PDF компресиране/оптимизиране. compress.credit=Тази услуга използва qpdf за PDF компресиране/оптимизиране.
compress.selectText.1=Ръчен режим - от 1 до 4 compress.selectText.1=Ръчен режим - от 1 до 4
compress.selectText.2=Ниво на оптимизация: compress.selectText.2=Ниво на оптимизация:
compress.selectText.3=4 (Ужасно за текстови изображения) compress.selectText.3=4 (Ужасно за текстови изображения)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Промени
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF към PDF/A pdfToPDFA.title=PDF към PDF/A
pdfToPDFA.header=PDF към PDF/A pdfToPDFA.header=PDF към PDF/A
pdfToPDFA.credit=Тази услуга използва ghostscript за PDF/A преобразуване. pdfToPDFA.credit=Тази услуга използва qpdf за PDF/A преобразуване.
pdfToPDFA.submit=Преобразуване pdfToPDFA.submit=Преобразуване
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
pdfToPDFA.outputFormat=Изходен формат pdfToPDFA.outputFormat=Изходен формат
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Ниво на отметка: Изберете нивот
splitByChapters.desc.3=Включване на метаданни: Ако е отметнато, метаданните на оригиналния PDF ще бъдат включени във всеки разделен PDF. splitByChapters.desc.3=Включване на метаданни: Ако е отметнато, метаданните на оригиналния PDF ще бъдат включени във всеки разделен PDF.
splitByChapters.desc.4=Разрешаване на дубликати: Ако е отметнато, позволява множество отметки на една и съща страница за създаване на отделни PDF файлове. splitByChapters.desc.4=Разрешаване на дубликати: Ако е отметнато, позволява множество отметки на една и съща страница за създаване на отделни PDF файлове.
splitByChapters.submit=Разделяне на PDF splitByChapters.submit=Разделяне на PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Mode OCR
ocr.selectText.11=Elimina Imatges després de l'OCR (Elimina TOTES les imatges, útil si forma part d'un procés de conversió) ocr.selectText.11=Elimina Imatges després de l'OCR (Elimina TOTES les imatges, útil si forma part d'un procés de conversió)
ocr.selectText.12=Tipus de Renderització (Avançat) ocr.selectText.12=Tipus de Renderització (Avançat)
ocr.help=Llegeix aquesta documentació sobre com utilitzar-la per a altres idiomes i/o no utilitzar-la a Docker ocr.help=Llegeix aquesta documentació sobre com utilitzar-la per a altres idiomes i/o no utilitzar-la a Docker
ocr.credit=Aquest servei fa servir OCRmyPDF i Tesseract per a OCR. ocr.credit=Aquest servei fa servir qpdf i Tesseract per a OCR.
ocr.submit=Processa PDF amb OCR ocr.submit=Processa PDF amb OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Converteix a PDF
#compress #compress
compress.title=Comprimir compress.title=Comprimir
compress.header=Comprimir PDF compress.header=Comprimir PDF
compress.credit=Aquest servei utilitza Ghostscript per a la compressió/optimització de PDF. compress.credit=Aquest servei utilitza qpdf per a la compressió/optimització de PDF.
compress.selectText.1=Mode manual: de l'1 al 4 compress.selectText.1=Mode manual: de l'1 al 4
compress.selectText.2=Nivell d'optimització: compress.selectText.2=Nivell d'optimització:
compress.selectText.3=4 (terrible per a imatges de text) compress.selectText.3=4 (terrible per a imatges de text)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Canvia
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF a PDF/A pdfToPDFA.title=PDF a PDF/A
pdfToPDFA.header=PDF a PDF/A pdfToPDFA.header=PDF a PDF/A
pdfToPDFA.credit=Utilitza Ghostscript per a la conversió a PDF/A pdfToPDFA.credit=Utilitza qpdf per a la conversió a PDF/A
pdfToPDFA.submit=Converteix pdfToPDFA.submit=Converteix
pdfToPDFA.tip=Actualment no funciona per a múltiples entrades al mateix temps pdfToPDFA.tip=Actualment no funciona per a múltiples entrades al mateix temps
pdfToPDFA.outputFormat=Format de sortida pdfToPDFA.outputFormat=Format de sortida
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Nivell de Marcadors: Tria el nivell de marcadors que s'ut
splitByChapters.desc.3=Incloure Metadades: Si està marcat, les metadades del PDF original s'inclouran en cada PDF dividit. splitByChapters.desc.3=Incloure Metadades: Si està marcat, les metadades del PDF original s'inclouran en cada PDF dividit.
splitByChapters.desc.4=Permetre Duplicats: Si està marcat, permet diversos marcadors a la mateixa pàgina per crear PDFs separats. splitByChapters.desc.4=Permetre Duplicats: Si està marcat, permet diversos marcadors a la mateixa pàgina per crear PDFs separats.
splitByChapters.submit=Divideix PDF splitByChapters.submit=Divideix PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Režim OCR
ocr.selectText.11=Odstranit obrázky po OCR (Odstraní VŠECHNY obrázky, užitečné pouze jako součást kroku konverze) ocr.selectText.11=Odstranit obrázky po OCR (Odstraní VŠECHNY obrázky, užitečné pouze jako součást kroku konverze)
ocr.selectText.12=Typ vykreslení (Pokročilé) ocr.selectText.12=Typ vykreslení (Pokročilé)
ocr.help=Prosím, přečtěte si tuto dokumentaci o použití pro jiné jazyky a/nebo použití mimo Docker ocr.help=Prosím, přečtěte si tuto dokumentaci o použití pro jiné jazyky a/nebo použití mimo Docker
ocr.credit=Tato služba používá OCRmyPDF a Tesseract pro OCR. ocr.credit=Tato služba používá qpdf a Tesseract pro OCR.
ocr.submit=Zpracovat PDF s OCR ocr.submit=Zpracovat PDF s OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Převést na PDF
#compress #compress
compress.title=Komprese compress.title=Komprese
compress.header=Komprimovat PDF compress.header=Komprimovat PDF
compress.credit=Tato služba používá Ghostscript pro kompresi/optimalizaci PDF. compress.credit=Tato služba používá qpdf pro kompresi/optimalizaci PDF.
compress.selectText.1=Ruční režim - Od 1 do 4 compress.selectText.1=Ruční režim - Od 1 do 4
compress.selectText.2=Úroveň optimalizace: compress.selectText.2=Úroveň optimalizace:
compress.selectText.3=4 (Hrozné pro textové obrázky) compress.selectText.3=4 (Hrozné pro textové obrázky)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Změnit
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF na PDF/A pdfToPDFA.title=PDF na PDF/A
pdfToPDFA.header=PDF na PDF/A pdfToPDFA.header=PDF na PDF/A
pdfToPDFA.credit=Tato služba používá ghostscript pro konverzi do formátu PDF/A pdfToPDFA.credit=Tato služba používá qpdf pro konverzi do formátu PDF/A
pdfToPDFA.submit=Převést pdfToPDFA.submit=Převést
pdfToPDFA.tip=V současné době nepracuje pro více vstupů najednou pdfToPDFA.tip=V současné době nepracuje pro více vstupů najednou
pdfToPDFA.outputFormat=Výstupní formát pdfToPDFA.outputFormat=Výstupní formát
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Úroveň záhlaví: Zvolte úroveň záhlaví pro použit
splitByChapters.desc.3=Zahrnout metadatů: Pokud je zaškrtnuto, původní metadata PDF souboru budou zahrnuty do každého odděleného PDF souboru. splitByChapters.desc.3=Zahrnout metadatů: Pokud je zaškrtnuto, původní metadata PDF souboru budou zahrnuty do každého odděleného PDF souboru.
splitByChapters.desc.4=Povolit duplicitní záznamy: Pokud je zaškrtnuto, návštěvníci mohou vytvořit samostatné PDF soubory z více záhlaví na stejné straně. splitByChapters.desc.4=Povolit duplicitní záznamy: Pokud je zaškrtnuto, návštěvníci mohou vytvořit samostatné PDF soubory z více záhlaví na stejné straně.
splitByChapters.submit=Podělit se PDF splitByChapters.submit=Podělit se PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR-tilstand
ocr.selectText.11=Fjern billeder efter OCR (Fjerner ALLE billeder, kun nyttigt hvis det er en del af konverteringstrinnet) ocr.selectText.11=Fjern billeder efter OCR (Fjerner ALLE billeder, kun nyttigt hvis det er en del af konverteringstrinnet)
ocr.selectText.12=Renderingstype (Avanceret) ocr.selectText.12=Renderingstype (Avanceret)
ocr.help=Læs venligst denne dokumentation om, hvordan man bruger dette til andre sprog og/eller brug uden for docker ocr.help=Læs venligst denne dokumentation om, hvordan man bruger dette til andre sprog og/eller brug uden for docker
ocr.credit=Denne tjeneste bruger OCRmyPDF og Tesseract til OCR. ocr.credit=Denne tjeneste bruger qpdf og Tesseract til OCR.
ocr.submit=Behandl PDF med OCR ocr.submit=Behandl PDF med OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Konvertér til PDF
#compress #compress
compress.title=Komprimer compress.title=Komprimer
compress.header=Komprimer PDF compress.header=Komprimer PDF
compress.credit=Denne tjeneste bruger Ghostscript til PDF Komprimering/Optimering. compress.credit=Denne tjeneste bruger qpdf til PDF Komprimering/Optimering.
compress.selectText.1=Manuel Tilstand - Fra 1 til 4 compress.selectText.1=Manuel Tilstand - Fra 1 til 4
compress.selectText.2=Optimeringsniveau: compress.selectText.2=Optimeringsniveau:
compress.selectText.3=4 (Forfærdelig for tekstbilleder) compress.selectText.3=4 (Forfærdelig for tekstbilleder)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Ændre
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF Til PDF/A pdfToPDFA.title=PDF Til PDF/A
pdfToPDFA.header=PDF Til PDF/A pdfToPDFA.header=PDF Til PDF/A
pdfToPDFA.credit=Denne tjeneste bruger ghostscript til PDF/A-konvertering pdfToPDFA.credit=Denne tjeneste bruger qpdf til PDF/A-konvertering
pdfToPDFA.submit=Konvertér pdfToPDFA.submit=Konvertér
pdfToPDFA.tip=Fungerer i øjeblikket ikke for flere input på én gang pdfToPDFA.tip=Fungerer i øjeblikket ikke for flere input på én gang
pdfToPDFA.outputFormat=Outputformat pdfToPDFA.outputFormat=Outputformat
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bogmærke niveau: Vælg nivået af bogmærker, der skal b
splitByChapters.desc.3=Inkluder metadata: Hvis markeret, vil den originale PDF's metadata inkluderes i hver splitterdels PDF. splitByChapters.desc.3=Inkluder metadata: Hvis markeret, vil den originale PDF's metadata inkluderes i hver splitterdels PDF.
splitByChapters.desc.4=Tillad duplikater: Hvis markeret, tillader det flere bogmærker på samme side til at oprette separate PDF'er. splitByChapters.desc.4=Tillad duplikater: Hvis markeret, tillader det flere bogmærker på samme side til at oprette separate PDF'er.
splitByChapters.submit=Splitter PDF splitByChapters.submit=Splitter PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR-Modus
ocr.selectText.11=Bilder nach OCR entfernen (Entfernt ALLE Bilder, nur sinnvoll, wenn Teil des Konvertierungsschritts) ocr.selectText.11=Bilder nach OCR entfernen (Entfernt ALLE Bilder, nur sinnvoll, wenn Teil des Konvertierungsschritts)
ocr.selectText.12=Rendertyp (Erweitert) ocr.selectText.12=Rendertyp (Erweitert)
ocr.help=Bitte lesen Sie diese Dokumentation, um zu erfahren, wie Sie dies für andere Sprachen verwenden und/oder nicht in Docker verwenden können ocr.help=Bitte lesen Sie diese Dokumentation, um zu erfahren, wie Sie dies für andere Sprachen verwenden und/oder nicht in Docker verwenden können
ocr.credit=Dieser Dienst verwendet OCRmyPDF und Tesseract für OCR. ocr.credit=Dieser Dienst verwendet qpdf und Tesseract für OCR.
ocr.submit=PDF mit OCR verarbeiten ocr.submit=PDF mit OCR verarbeiten
@@ -892,7 +892,7 @@ fileToPDF.submit=In PDF konvertieren
#compress #compress
compress.title=Komprimieren compress.title=Komprimieren
compress.header=PDF komprimieren compress.header=PDF komprimieren
compress.credit=Dieser Dienst verwendet Ghostscript für die PDF-Komprimierung/-Optimierung. compress.credit=Dieser Dienst verwendet qpdf für die PDF-Komprimierung/-Optimierung.
compress.selectText.1=Manueller Modus Von 1 bis 4 compress.selectText.1=Manueller Modus Von 1 bis 4
compress.selectText.2=Optimierungsstufe: compress.selectText.2=Optimierungsstufe:
compress.selectText.3=4 (Schrecklich für Textbilder) compress.selectText.3=4 (Schrecklich für Textbilder)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen! multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Ändern
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF zu PDF/A pdfToPDFA.title=PDF zu PDF/A
pdfToPDFA.header=PDF zu PDF/A pdfToPDFA.header=PDF zu PDF/A
pdfToPDFA.credit=Dieser Dienst verwendet ghostscript für die PDF/A-Konvertierung pdfToPDFA.credit=Dieser Dienst verwendet qpdf für die PDF/A-Konvertierung
pdfToPDFA.submit=Konvertieren pdfToPDFA.submit=Konvertieren
pdfToPDFA.tip=Dieser Dienst kann nur einzelne Eingangsdateien verarbeiten. pdfToPDFA.tip=Dieser Dienst kann nur einzelne Eingangsdateien verarbeiten.
pdfToPDFA.outputFormat=Ausgabeformat pdfToPDFA.outputFormat=Ausgabeformat
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Lesezeichenebene: Wählen Sie die Ebene der Lesezeichen,
splitByChapters.desc.3=Metadaten einschließen: Wenn diese Option aktiviert ist, werden die Metadaten der ursprünglichen PDF-Datei in jede aufgeteilte PDF-Datei übernommen. splitByChapters.desc.3=Metadaten einschließen: Wenn diese Option aktiviert ist, werden die Metadaten der ursprünglichen PDF-Datei in jede aufgeteilte PDF-Datei übernommen.
splitByChapters.desc.4=Duplikate erlauben: Wenn diese Option aktiviert ist, können mehrere Lesezeichen auf derselben Seite separate PDF Dateien erstellen. splitByChapters.desc.4=Duplikate erlauben: Wenn diese Option aktiviert ist, können mehrere Lesezeichen auf derselben Seite separate PDF Dateien erstellen.
splitByChapters.submit=PDF teilen splitByChapters.submit=PDF teilen
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Λειτουργία OCR
ocr.selectText.11=Κατάργηση εικόνων μετά το OCR (Καταργεί ΟΛΕΣ τις εικόνες, είναι χρήσιμο μόνο αν αποτελεί μέρος του βήματος μετατροπής) ocr.selectText.11=Κατάργηση εικόνων μετά το OCR (Καταργεί ΟΛΕΣ τις εικόνες, είναι χρήσιμο μόνο αν αποτελεί μέρος του βήματος μετατροπής)
ocr.selectText.12=Τύπος απόδοσης (Για προχωρημένους) ocr.selectText.12=Τύπος απόδοσης (Για προχωρημένους)
ocr.help=Διαβάστε αυτήν την τεκμηρίωση σχετικά με τον τρόπο χρήσης αυτής για άλλες γλώσσες ή/και μη χρήσης σε docker ocr.help=Διαβάστε αυτήν την τεκμηρίωση σχετικά με τον τρόπο χρήσης αυτής για άλλες γλώσσες ή/και μη χρήσης σε docker
ocr.credit=Αυτή η υπηρεσία χρησιμοποιεί OCRmyPDF και Tesseract για OCR. ocr.credit=Αυτή η υπηρεσία χρησιμοποιεί qpdf και Tesseract για OCR.
ocr.submit=Επεξεργασία PDF με OCR ocr.submit=Επεξεργασία PDF με OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Μετατροπή σε PDF
#compress #compress
compress.title=Συμπίεση compress.title=Συμπίεση
compress.header=Συμπίεση PDF compress.header=Συμπίεση PDF
compress.credit=Αυτή η υπηρεσία χρησιμοποιεί Ghostscript για PDF Συμπίεση/Βελτιστοποίηση. compress.credit=Αυτή η υπηρεσία χρησιμοποιεί qpdf για PDF Συμπίεση/Βελτιστοποίηση.
compress.selectText.1=Χειροκίνητη Λειτουργία - Από 1 έως 4 compress.selectText.1=Χειροκίνητη Λειτουργία - Από 1 έως 4
compress.selectText.2=Επίπεδο Βελτιστοποίησης: compress.selectText.2=Επίπεδο Βελτιστοποίησης:
compress.selectText.3=4 (Πολύ κακό για εικόνες κειμένου) compress.selectText.3=4 (Πολύ κακό για εικόνες κειμένου)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Αλλαγή
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF σε PDF/A pdfToPDFA.title=PDF σε PDF/A
pdfToPDFA.header=PDF σε PDF/A pdfToPDFA.header=PDF σε PDF/A
pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί ghostscript για PDF/A μετατροπή pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί qpdf για PDF/A μετατροπή
pdfToPDFA.submit=Μετατροπή pdfToPDFA.submit=Μετατροπή
pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα
pdfToPDFA.outputFormat=Εξόδος αναμορφώσεων pdfToPDFA.outputFormat=Εξόδος αναμορφώσεων
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Επίπεδο Σήμανσης Σκέψης: Επιλέ
splitByChapters.desc.3=Πρόσθεση Metadata: Αν επεξεργαστείται, οι αρχικές metadata του PDF θα προστεθούν σε κάθε διαλυμένο PDF. splitByChapters.desc.3=Πρόσθεση Metadata: Αν επεξεργαστείται, οι αρχικές metadata του PDF θα προστεθούν σε κάθε διαλυμένο PDF.
splitByChapters.desc.4=Διάλυση Παρόντων Τίτλων Επιπέδου: Αν επεξεργαστείται, επιτρέπει τη δημιουργία αποκοπών PDF με βάση πλήρως καθορισμένους σήμαντες έδρας. splitByChapters.desc.4=Διάλυση Παρόντων Τίτλων Επιπέδου: Αν επεξεργαστείται, επιτρέπει τη δημιουργία αποκοπών PDF με βάση πλήρως καθορισμένους σήμαντες έδρας.
splitByChapters.submit=Διαλύστε το PDF splitByChapters.submit=Διαλύστε το PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR Mode
ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step) ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step)
ocr.selectText.12=Render Type (Advanced) ocr.selectText.12=Render Type (Advanced)
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
ocr.credit=This service uses OCRmyPDF and Tesseract for OCR. ocr.credit=This service uses qpdf and Tesseract for OCR.
ocr.submit=Process PDF with OCR ocr.submit=Process PDF with OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Convert to PDF
#compress #compress
compress.title=Compress compress.title=Compress
compress.header=Compress PDF compress.header=Compress PDF
compress.credit=This service uses Ghostscript for PDF Compress/Optimisation. compress.credit=This service uses qpdf for PDF Compress/Optimisation.
compress.selectText.1=Manual Mode - From 1 to 4 compress.selectText.1=Manual Mode - From 1 to 4
compress.selectText.2=Optimization level: compress.selectText.2=Optimization level:
compress.selectText.3=4 (Terrible for text images) compress.selectText.3=4 (Terrible for text images)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Change
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF To PDF/A pdfToPDFA.title=PDF To PDF/A
pdfToPDFA.header=PDF To PDF/A pdfToPDFA.header=PDF To PDF/A
pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion pdfToPDFA.credit=This service uses qpdf for PDF/A conversion
pdfToPDFA.submit=Convert pdfToPDFA.submit=Convert
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format pdfToPDFA.outputFormat=Output format
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR Mode
ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step) ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step)
ocr.selectText.12=Render Type (Advanced) ocr.selectText.12=Render Type (Advanced)
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
ocr.credit=This service uses OCRmyPDF and Tesseract for OCR. ocr.credit=This service uses qpdf and Tesseract for OCR.
ocr.submit=Process PDF with OCR ocr.submit=Process PDF with OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Convert to PDF
#compress #compress
compress.title=Compress compress.title=Compress
compress.header=Compress PDF compress.header=Compress PDF
compress.credit=This service uses Ghostscript for PDF Compress/Optimisation. compress.credit=This service uses qpdf for PDF Compress/Optimisation.
compress.selectText.1=Manual Mode - From 1 to 4 compress.selectText.1=Manual Mode - From 1 to 4
compress.selectText.2=Optimization level: compress.selectText.2=Optimization level:
compress.selectText.3=4 (Terrible for text images) compress.selectText.3=4 (Terrible for text images)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Change
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF To PDF/A pdfToPDFA.title=PDF To PDF/A
pdfToPDFA.header=PDF To PDF/A pdfToPDFA.header=PDF To PDF/A
pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion pdfToPDFA.credit=This service uses qpdf for PDF/A conversion
pdfToPDFA.submit=Convert pdfToPDFA.submit=Convert
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format pdfToPDFA.outputFormat=Output format
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Modo OCR
ocr.selectText.11=Eliminar imágenes después de OCR (Elimina TODAS las imágenes, solo es útil si es parte del paso de conversión) ocr.selectText.11=Eliminar imágenes después de OCR (Elimina TODAS las imágenes, solo es útil si es parte del paso de conversión)
ocr.selectText.12=Tipo de procesamiento (avanzado) ocr.selectText.12=Tipo de procesamiento (avanzado)
ocr.help=Lea esta documentación sobre cómo usar esto para otros idiomas y/o no usarlo en Docker ocr.help=Lea esta documentación sobre cómo usar esto para otros idiomas y/o no usarlo en Docker
ocr.credit=Este servicio utiliza OCRmyPDF y Tesseract para OCR ocr.credit=Este servicio utiliza qpdf y Tesseract para OCR
ocr.submit=Procesar PDF con OCR ocr.submit=Procesar PDF con OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Convertir a PDF
#compress #compress
compress.title=Comprimir compress.title=Comprimir
compress.header=Comprimir PDF compress.header=Comprimir PDF
compress.credit=Este servicio utiliza Ghostscript para compresión/optimización de PDF compress.credit=Este servicio utiliza qpdf para compresión/optimización de PDF
compress.selectText.1=Modo manual - De 1 a 4 compress.selectText.1=Modo manual - De 1 a 4
compress.selectText.2=Nivel de optimización: compress.selectText.2=Nivel de optimización:
compress.selectText.3=4 (Terrible para imágenes de texto) compress.selectText.3=4 (Terrible para imágenes de texto)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Cambiar
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF a PDF/A pdfToPDFA.title=PDF a PDF/A
pdfToPDFA.header=PDF a PDF/A pdfToPDFA.header=PDF a PDF/A
pdfToPDFA.credit=Este servicio usa ghostscript para la conversión a PDF/A pdfToPDFA.credit=Este servicio usa qpdf para la conversión a PDF/A
pdfToPDFA.submit=Convertir pdfToPDFA.submit=Convertir
pdfToPDFA.tip=Actualmente no funciona para múltiples entrada a la vez pdfToPDFA.tip=Actualmente no funciona para múltiples entrada a la vez
pdfToPDFA.outputFormat=Formato de salida pdfToPDFA.outputFormat=Formato de salida
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Nivel de Marcador: Elige el nivel de marcadores para divi
splitByChapters.desc.3=Incluir Metadatos: Si está seleccionado, los metadatos del PDF original se incluirán en cada PDF dividido. splitByChapters.desc.3=Incluir Metadatos: Si está seleccionado, los metadatos del PDF original se incluirán en cada PDF dividido.
splitByChapters.desc.4=Permitir Duplicados: Si está seleccionado, permite que múltiples marcadores en la misma página creen archivos PDF separados. splitByChapters.desc.4=Permitir Duplicados: Si está seleccionado, permite que múltiples marcadores en la misma página creen archivos PDF separados.
splitByChapters.submit=Dividir PDF splitByChapters.submit=Dividir PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR modua
ocr.selectText.11=Irudiak ezabatu OCR-ren ondoren (Irudi GUZTIAK ezabatzen ditu, bakarrik da erabilgarri bihurketa urratsaren parte baldin bada) ocr.selectText.11=Irudiak ezabatu OCR-ren ondoren (Irudi GUZTIAK ezabatzen ditu, bakarrik da erabilgarri bihurketa urratsaren parte baldin bada)
ocr.selectText.12=Prozesaketa-mota (aurreratua) ocr.selectText.12=Prozesaketa-mota (aurreratua)
ocr.help=Irakurri honen erabilerari buruzko dokumentazioa beste hizkuntza batzuetarako eta/edo ez erabili Docker-en ocr.help=Irakurri honen erabilerari buruzko dokumentazioa beste hizkuntza batzuetarako eta/edo ez erabili Docker-en
ocr.credit=Zerbitzu honek OCRmyPDF eta OCR-rako Tesseract erabiltzen ditu ocr.credit=Zerbitzu honek qpdf eta OCR-rako Tesseract erabiltzen ditu
ocr.submit=PDF prozesatu OCR-rekin ocr.submit=PDF prozesatu OCR-rekin
@@ -892,7 +892,7 @@ fileToPDF.submit=PDF bihurtu
#compress #compress
compress.title=Konprimatu compress.title=Konprimatu
compress.header=PDFa konprimatu compress.header=PDFa konprimatu
compress.credit=Zerbitzu honek Ghostscript erabiltzen du PDFak komprimatzeko/optimizatzeko compress.credit=Zerbitzu honek qpdf erabiltzen du PDFak komprimatzeko/optimizatzeko
compress.selectText.1=Eskuz 1etik 4ra compress.selectText.1=Eskuz 1etik 4ra
compress.selectText.2=Optimizazio maila: compress.selectText.2=Optimizazio maila:
compress.selectText.3=4 (Izugarria testu-irudietarako) compress.selectText.3=4 (Izugarria testu-irudietarako)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Aldatu
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDFa PDF/A bihurtu pdfToPDFA.title=PDFa PDF/A bihurtu
pdfToPDFA.header=PDFa PDF/A bihurtu pdfToPDFA.header=PDFa PDF/A bihurtu
pdfToPDFA.credit=Zerbitzu honek ghostscript erabiltzen du PDFak PDF/A bihurtzeko pdfToPDFA.credit=Zerbitzu honek qpdf erabiltzen du PDFak PDF/A bihurtzeko
pdfToPDFA.submit=Bihurtu pdfToPDFA.submit=Bihurtu
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format pdfToPDFA.outputFormat=Output format
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Mode OCR
ocr.selectText.11=Supprimer les images après l'OCR (Supprime TOUTES les images, utile uniquement si elles font partie de l'étape de conversion) ocr.selectText.11=Supprimer les images après l'OCR (Supprime TOUTES les images, utile uniquement si elles font partie de l'étape de conversion)
ocr.selectText.12=Type de rendu (avancé) ocr.selectText.12=Type de rendu (avancé)
ocr.help=Veuillez lire cette documentation pour savoir comment utiliser l'OCR pour d'autres langues ou une utilisation hors Docker : ocr.help=Veuillez lire cette documentation pour savoir comment utiliser l'OCR pour d'autres langues ou une utilisation hors Docker :
ocr.credit=Ce service utilise OCRmyPDF et Tesseract pour l'OCR. ocr.credit=Ce service utilise qpdf et Tesseract pour l'OCR.
ocr.submit=Traiter ocr.submit=Traiter
@@ -892,7 +892,7 @@ fileToPDF.submit=Convertir
#compress #compress
compress.title=Compresser un PDF compress.title=Compresser un PDF
compress.header=Compresser un PDF (lorsque c'est possible!) compress.header=Compresser un PDF (lorsque c'est possible!)
compress.credit=Ce service utilise Ghostscript pour la compression et l'optimisation des PDF. compress.credit=Ce service utilise qpdf pour la compression et l'optimisation des PDF.
compress.selectText.1=Mode manuel de 1 à 4 compress.selectText.1=Mode manuel de 1 à 4
compress.selectText.2=Niveau d'optimisation compress.selectText.2=Niveau d'optimisation
compress.selectText.3=4 (terrible pour les images textuelles) compress.selectText.3=4 (terrible pour les images textuelles)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Déplacer vers la gauche
multiTool.moveRight=Déplacer vers la droite multiTool.moveRight=Déplacer vers la droite
multiTool.delete=Supprimer multiTool.delete=Supprimer
multiTool.dragDropMessage=Page(s) sélectionnées multiTool.dragDropMessage=Page(s) sélectionnées
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles ! multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles !
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Modifier
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF en PDF/A pdfToPDFA.title=PDF en PDF/A
pdfToPDFA.header=PDF en PDF/A pdfToPDFA.header=PDF en PDF/A
pdfToPDFA.credit=Ce service utilise ghostscript pour la conversion en PDF/A. pdfToPDFA.credit=Ce service utilise qpdf pour la conversion en PDF/A.
pdfToPDFA.submit=Convertir pdfToPDFA.submit=Convertir
pdfToPDFA.tip=Ne fonctionne actuellement pas pour plusieurs entrées à la fois pdfToPDFA.tip=Ne fonctionne actuellement pas pour plusieurs entrées à la fois
pdfToPDFA.outputFormat=Format de sortie pdfToPDFA.outputFormat=Format de sortie
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Niveau de Signet : Choisissez le niveau de signets à uti
splitByChapters.desc.3=Inclure les Métadonnées : Si coché, les métadonnées du PDF original seront incluses dans chaque PDF divisé. splitByChapters.desc.3=Inclure les Métadonnées : Si coché, les métadonnées du PDF original seront incluses dans chaque PDF divisé.
splitByChapters.desc.4=Autoriser les Doublons : Si coché, permet à plusieurs signets sur la même page de créer des PDF séparés. splitByChapters.desc.4=Autoriser les Doublons : Si coché, permet à plusieurs signets sur la même page de créer des PDF séparés.
splitByChapters.submit=Diviser le PDF splitByChapters.submit=Diviser le PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Mód OCR
ocr.selectText.11=Bain íomhánna tar éis OCR (Bain GACH íomhá, ní úsáideach ach amháin má tá siad mar chuid den chéim tiontaithe) ocr.selectText.11=Bain íomhánna tar éis OCR (Bain GACH íomhá, ní úsáideach ach amháin má tá siad mar chuid den chéim tiontaithe)
ocr.selectText.12=Cineál Rindreála (Ardleibhéal) ocr.selectText.12=Cineál Rindreála (Ardleibhéal)
ocr.help=Léigh le do thoil an doiciméadú seo ar conas é seo a úsáid do theangacha eile agus/nó úsáid nach bhfuil i ndugairí ocr.help=Léigh le do thoil an doiciméadú seo ar conas é seo a úsáid do theangacha eile agus/nó úsáid nach bhfuil i ndugairí
ocr.credit=Úsáideann an tseirbhís seo OCRmyPDF agus Tesseract le haghaidh OCR. ocr.credit=Úsáideann an tseirbhís seo qpdf agus Tesseract le haghaidh OCR.
ocr.submit=Próiseáil PDF le OCR ocr.submit=Próiseáil PDF le OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Tiontaigh go PDF
#compress #compress
compress.title=Comhbhrúigh compress.title=Comhbhrúigh
compress.header=Comhbhrúigh PDF compress.header=Comhbhrúigh PDF
compress.credit=Úsáideann an tseirbhís seo Ghostscript le haghaidh Comhbhrú/Optimization PDF. compress.credit=Úsáideann an tseirbhís seo qpdf le haghaidh Comhbhrú/Optimization PDF.
compress.selectText.1=Mód Láimhe - Ó 1 go 4 compress.selectText.1=Mód Láimhe - Ó 1 go 4
compress.selectText.2=Leibhéal optamaithe: compress.selectText.2=Leibhéal optamaithe:
compress.selectText.3=4 (Uafásach le haghaidh íomhánna téacs) compress.selectText.3=4 (Uafásach le haghaidh íomhánna téacs)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Athrú
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF Go PDF/A pdfToPDFA.title=PDF Go PDF/A
pdfToPDFA.header=PDF Go PDF/A pdfToPDFA.header=PDF Go PDF/A
pdfToPDFA.credit=Úsáideann an tseirbhís seo ghostscript chun PDF/A a thiontú pdfToPDFA.credit=Úsáideann an tseirbhís seo qpdf chun PDF/A a thiontú
pdfToPDFA.submit=Tiontaigh pdfToPDFA.submit=Tiontaigh
pdfToPDFA.tip=Faoi láthair ní oibríonn sé le haghaidh ionchuir iolracha ag an am céanna pdfToPDFA.tip=Faoi láthair ní oibríonn sé le haghaidh ionchuir iolracha ag an am céanna
pdfToPDFA.outputFormat=Formáid aschuir pdfToPDFA.outputFormat=Formáid aschuir
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR मोड
ocr.selectText.11=OCR के बाद छवियां हटाएँ (सभी छवियां हटाएँ, केवल परिवर्तन चरण का हिस्सा होता है) ocr.selectText.11=OCR के बाद छवियां हटाएँ (सभी छवियां हटाएँ, केवल परिवर्तन चरण का हिस्सा होता है)
ocr.selectText.12=रेंडर टाइप (उन्नत) ocr.selectText.12=रेंडर टाइप (उन्नत)
ocr.help=कृपया इस डॉक्यूमेंटेशन को पढ़ें कि इसे अन्य भाषाओं के लिए कैसे उपयोग किया जाता है और/या डॉकर में नहीं हैं ocr.help=कृपया इस डॉक्यूमेंटेशन को पढ़ें कि इसे अन्य भाषाओं के लिए कैसे उपयोग किया जाता है और/या डॉकर में नहीं हैं
ocr.credit=इस सेवा में OCRmyPDF और टेसरेक्ट का उपयोग होता है। ocr.credit=इस सेवा में qpdf और टेसरेक्ट का उपयोग होता है।
ocr.submit=OCR के साथ PDF प्रोसेस करें ocr.submit=OCR के साथ PDF प्रोसेस करें
@@ -892,7 +892,7 @@ fileToPDF.submit=पीडीएफ़ में बदलें
#compress #compress
compress.title=संकुचित करें compress.title=संकुचित करें
compress.header=PDF को संकुचित करें compress.header=PDF को संकुचित करें
compress.credit=यह सेवा PDF संकुचन/अनुकूलन के लिए Ghostscript का उपयोग करती है। compress.credit=यह सेवा PDF संकुचन/अनुकूलन के लिए qpdf का उपयोग करती है।
compress.selectText.1=मैनुअल मोड - 1 से 4 तक compress.selectText.1=मैनुअल मोड - 1 से 4 तक
compress.selectText.2=अनुकूलन स्तर: compress.selectText.2=अनुकूलन स्तर:
compress.selectText.3=4 (पाठ छवियों के लिए अत्यधिक) compress.selectText.3=4 (पाठ छवियों के लिए अत्यधिक)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=बदलें
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF से PDF/A में pdfToPDFA.title=PDF से PDF/A में
pdfToPDFA.header=PDF से PDF/A में pdfToPDFA.header=PDF से PDF/A में
pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए ghostscript का उपयोग किया जाता है। pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए qpdf का उपयोग किया जाता है।
pdfToPDFA.submit=परिवर्तित करें pdfToPDFA.submit=परिवर्तित करें
pdfToPDFA.tip=यह सैकड़ों प्रविष्टियाँ एक ही समय में काम करते हैं pdfToPDFA.tip=यह सैकड़ों प्रविष्टियाँ एक ही समय में काम करते हैं
pdfToPDFA.outputFormat=आउटपुट फॉर्मेट pdfToPDFA.outputFormat=आउटपुट फॉर्मेट
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=लेमैक्स स्तर: विभाजन
splitByChapters.desc.3=मॉडेटरेट का शामिल करें: यदि सत्यापित किया जाता है, प्रारंभिक PDF की मॉडेटरेट को प्रत्येक विभाग PDF में शामिल किया जाएगा। splitByChapters.desc.3=मॉडेटरेट का शामिल करें: यदि सत्यापित किया जाता है, प्रारंभिक PDF की मॉडेटरेट को प्रत्येक विभाग PDF में शामिल किया जाएगा।
splitByChapters.desc.4=यादृच्छिक पुनरावृत्ति अनुमोदित: यदि सत्यापित किया जाता है, एक ही पेज पर दोहरे मूल्यांकन पब्लिक पीड़एफ बनाने की संभावना देता है। splitByChapters.desc.4=यादृच्छिक पुनरावृत्ति अनुमोदित: यदि सत्यापित किया जाता है, एक ही पेज पर दोहरे मूल्यांकन पब्लिक पीड़एफ बनाने की संभावना देता है।
splitByChapters.submit=PDF विभाजित splitByChapters.submit=PDF विभाजित
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR način
ocr.selectText.11=Ukloni slike nakon OCR-a (Uklanja SVE slike, korisno samo ako je dio koraka konverzije) ocr.selectText.11=Ukloni slike nakon OCR-a (Uklanja SVE slike, korisno samo ako je dio koraka konverzije)
ocr.selectText.12=Vrsta iscrtavanja (napredno) ocr.selectText.12=Vrsta iscrtavanja (napredno)
ocr.help=Pročitajte ovu dokumentaciju o tome kako ovo koristiti za druge jezike i/ili koristiti ne u dockeru ocr.help=Pročitajte ovu dokumentaciju o tome kako ovo koristiti za druge jezike i/ili koristiti ne u dockeru
ocr.credit=Ova usluga koristi OCRmyPDF i Tesseract za OCR. ocr.credit=Ova usluga koristi qpdf i Tesseract za OCR.
ocr.submit=Obradi PDF sa OCR-om ocr.submit=Obradi PDF sa OCR-om
@@ -892,7 +892,7 @@ fileToPDF.submit=Pretvori u PDF
#compress #compress
compress.title=Komprimirajte compress.title=Komprimirajte
compress.header=Komprimirajte PDF compress.header=Komprimirajte PDF
compress.credit=Ova usluga koristi Ghostscript za komprimiranje / optimizaciju PDF-a. compress.credit=Ova usluga koristi qpdf za komprimiranje / optimizaciju PDF-a.
compress.selectText.1=Ručni režim - Od 1 do 4 compress.selectText.1=Ručni režim - Od 1 do 4
compress.selectText.2=Nivo optimizacije: compress.selectText.2=Nivo optimizacije:
compress.selectText.3=4 (Užasno za tekstualne slike) compress.selectText.3=4 (Užasno za tekstualne slike)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Promijeniti
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF u PDF/A pdfToPDFA.title=PDF u PDF/A
pdfToPDFA.header=PDF u PDF/A pdfToPDFA.header=PDF u PDF/A
pdfToPDFA.credit=Ova usluga koristi ghostscript za PDF/A pretvorbu pdfToPDFA.credit=Ova usluga koristi qpdf za PDF/A pretvorbu
pdfToPDFA.submit=Pretvoriti pdfToPDFA.submit=Pretvoriti
pdfToPDFA.tip=Trenutno ne radi za više unosa odjednom pdfToPDFA.tip=Trenutno ne radi za više unosa odjednom
pdfToPDFA.outputFormat=Izlazni format pdfToPDFA.outputFormat=Izlazni format
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Nivo oznaka: Odaberite nivo oznaka koji će se koristiti
splitByChapters.desc.3=Uključi metapodatke: Ako je pokušano, metapodaci iz originalne PDF datoteke će biti uključeni u svaku podijeljenu PDF datoteku. splitByChapters.desc.3=Uključi metapodatke: Ako je pokušano, metapodaci iz originalne PDF datoteke će biti uključeni u svaku podijeljenu PDF datoteku.
splitByChapters.desc.4=Dopuštaj duplikate: Ako je ova opcija zaštićena, dozvoljava se da se na istoj strani mogu stvoriti posebne PDF datoteke s više oznaka. splitByChapters.desc.4=Dopuštaj duplikate: Ako je ova opcija zaštićena, dozvoljava se da se na istoj strani mogu stvoriti posebne PDF datoteke s više oznaka.
splitByChapters.submit=Podijeli PDF splitByChapters.submit=Podijeli PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR mód
ocr.selectText.11=Képek eltávolítása OCR után (Az ÖSSZES kép eltávolítása, csak akkor hasznos, ha a konverzió része) ocr.selectText.11=Képek eltávolítása OCR után (Az ÖSSZES kép eltávolítása, csak akkor hasznos, ha a konverzió része)
ocr.selectText.12=Render típusa (Speciális) ocr.selectText.12=Render típusa (Speciális)
ocr.help=Kérjük, olvassa el ezt a dokumentációt az egyéb nyelvek használatához és/vagy a nem Docker-es használathoz. ocr.help=Kérjük, olvassa el ezt a dokumentációt az egyéb nyelvek használatához és/vagy a nem Docker-es használathoz.
ocr.credit=Ez a szolgáltatás az OCRmyPDF és a Tesseract OCR használatával működik. ocr.credit=Ez a szolgáltatás az qpdf és a Tesseract OCR használatával működik.
ocr.submit=PDF feldolgozása OCR-rel ocr.submit=PDF feldolgozása OCR-rel
@@ -892,7 +892,7 @@ fileToPDF.submit=Konvertálás PDF dokumentummá
#compress #compress
compress.title=Tömörítés compress.title=Tömörítés
compress.header=PDF tömörítése compress.header=PDF tömörítése
compress.credit=Ez a szolgáltatás a Ghostscript-et használja a PDF tömörítéséhez/optimalizálásához. compress.credit=Ez a szolgáltatás a qpdf-et használja a PDF tömörítéséhez/optimalizálásához.
compress.selectText.1=Kézi mód - 1-től 4-ig compress.selectText.1=Kézi mód - 1-től 4-ig
compress.selectText.2=Optimalizálási szint: compress.selectText.2=Optimalizálási szint:
compress.selectText.3=4 (nem ajánlott a szöveges képekhez) compress.selectText.3=4 (nem ajánlott a szöveges képekhez)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Módosítás
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF >> PDF/A pdfToPDFA.title=PDF >> PDF/A
pdfToPDFA.header=PDF >> PDF/A pdfToPDFA.header=PDF >> PDF/A
pdfToPDFA.credit=Ez a szolgáltatás az ghostscript-t használja a PDF/A konverzióhoz pdfToPDFA.credit=Ez a szolgáltatás az qpdf-t használja a PDF/A konverzióhoz
pdfToPDFA.submit=Konvertálás pdfToPDFA.submit=Konvertálás
pdfToPDFA.tip=Jelenleg egyszerre több fájl nem működik ezzel a funkcióval pdfToPDFA.tip=Jelenleg egyszerre több fájl nem működik ezzel a funkcióval
pdfToPDFA.outputFormat=Kimeneti formátum pdfToPDFA.outputFormat=Kimeneti formátum
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Metaadatok belefoglalása: Ha bevanítva van, az eredeti PDF fájl metaadatai megtartódnak minden osztott fájlban. splitByChapters.desc.3=Metaadatok belefoglalása: Ha bevanítva van, az eredeti PDF fájl metaadatai megtartódnak minden osztott fájlban.
splitByChapters.desc.4=Duplikációk engedélyezése: Ha bevanítva van, lehetővé teszi a megadott oldalon lévő több kijelzőszint alapján új PDF-ek létrehozása. splitByChapters.desc.4=Duplikációk engedélyezése: Ha bevanítva van, lehetővé teszi a megadott oldalon lévő több kijelzőszint alapján új PDF-ek létrehozása.
splitByChapters.submit=PDF osztás splitByChapters.submit=PDF osztás
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Mode OCR
ocr.selectText.11=Hapus gambar setelah OCR (Menghapus Semua gambar, hanya berguna jika merupakan bagian dari langkah konversi) ocr.selectText.11=Hapus gambar setelah OCR (Menghapus Semua gambar, hanya berguna jika merupakan bagian dari langkah konversi)
ocr.selectText.12=Jenis Render (Lanjutan) ocr.selectText.12=Jenis Render (Lanjutan)
ocr.help=Silakan baca dokumentasi ini tentang cara menggunakan ini untuk bahasa lain dan/atau penggunaan yang tidak ada di docker ocr.help=Silakan baca dokumentasi ini tentang cara menggunakan ini untuk bahasa lain dan/atau penggunaan yang tidak ada di docker
ocr.credit=Layanan ini menggunakan OCRmyPDF dan Tesseract untuk OCR. ocr.credit=Layanan ini menggunakan qpdf dan Tesseract untuk OCR.
ocr.submit=Memproses PDF dengan OCR ocr.submit=Memproses PDF dengan OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Konversi ke PDF
#compress #compress
compress.title=Kompres compress.title=Kompres
compress.header=Kompres PDF compress.header=Kompres PDF
compress.credit=Layanan ini menggunakan Ghostscript untuk Kompresi/Optimalisasi PDF. compress.credit=Layanan ini menggunakan qpdf untuk Kompresi/Optimalisasi PDF.
compress.selectText.1=Mode Manual - Dari 1 hingga 4 compress.selectText.1=Mode Manual - Dari 1 hingga 4
compress.selectText.2=Tingkat Optimalisasi: compress.selectText.2=Tingkat Optimalisasi:
compress.selectText.3=4 (Buruk untuk gambar teks) compress.selectText.3=4 (Buruk untuk gambar teks)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Ganti
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF Ke PDF/A pdfToPDFA.title=PDF Ke PDF/A
pdfToPDFA.header=PDF ke PDF/A pdfToPDFA.header=PDF ke PDF/A
pdfToPDFA.credit=Layanan ini menggunakan ghostscript untuk konversi PDF/A. pdfToPDFA.credit=Layanan ini menggunakan qpdf untuk konversi PDF/A.
pdfToPDFA.submit=Konversi pdfToPDFA.submit=Konversi
pdfToPDFA.tip=Saat ini tidak dapat digunakan untuk beberapa input sekaligus pdfToPDFA.tip=Saat ini tidak dapat digunakan untuk beberapa input sekaligus
pdfToPDFA.outputFormat=Format keluaran pdfToPDFA.outputFormat=Format keluaran
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Tingkatan Markah: Pilih tingkatan markah yang digunakan u
splitByChapters.desc.3=Termasuk Metadata: Jika dicentang, metadata asli PDF akan disertakan dalam setiap PDF yang dibagi. splitByChapters.desc.3=Termasuk Metadata: Jika dicentang, metadata asli PDF akan disertakan dalam setiap PDF yang dibagi.
splitByChapters.desc.4=Izinkan Duplikat: Jika dicentang, mengizinkan beberapa markah pada halaman yang sama untuk membuat PDF terpisah. splitByChapters.desc.4=Izinkan Duplikat: Jika dicentang, mengizinkan beberapa markah pada halaman yang sama untuk membuat PDF terpisah.
splitByChapters.submit=Pecah PDF splitByChapters.submit=Pecah PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Modalità OCR
ocr.selectText.11=Rimuovi immagini dopo la scansione (Rimuove TUTTE le immagini, utile solo come parte del processo di conversione) ocr.selectText.11=Rimuovi immagini dopo la scansione (Rimuove TUTTE le immagini, utile solo come parte del processo di conversione)
ocr.selectText.12=Modalità di rendering (avanzato) ocr.selectText.12=Modalità di rendering (avanzato)
ocr.help=Per favore leggi la documentazione su come usare il programma per altri linguaggi e/o uso non in Docker ocr.help=Per favore leggi la documentazione su come usare il programma per altri linguaggi e/o uso non in Docker
ocr.credit=Questo servizio utilizza OCRmyPDF e Tesseract per l'OCR. ocr.credit=Questo servizio utilizza qpdf e Tesseract per l'OCR.
ocr.submit=Scansiona testo nel PDF con OCR ocr.submit=Scansiona testo nel PDF con OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Converti in PDF
#compress #compress
compress.title=Comprimi compress.title=Comprimi
compress.header=Comprimi PDF compress.header=Comprimi PDF
compress.credit=Questo servizio utilizza Ghostscript per la compressione/ottimizzazione dei PDF. compress.credit=Questo servizio utilizza qpdf per la compressione/ottimizzazione dei PDF.
compress.selectText.1=Modalità manuale - Da 1 a 4 compress.selectText.1=Modalità manuale - Da 1 a 4
compress.selectText.2=Livello di ottimizzazione: compress.selectText.2=Livello di ottimizzazione:
compress.selectText.3=4 (Terribile per le immagini di testo) compress.selectText.3=4 (Terribile per le immagini di testo)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Sposta a sinistra
multiTool.moveRight=Sposta a destra multiTool.moveRight=Sposta a destra
multiTool.delete=Elimina multiTool.delete=Elimina
multiTool.dragDropMessage=Pagina(e) selezionata(e) multiTool.dragDropMessage=Pagina(e) selezionata(e)
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive! multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Cambia proprietà
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=Da PDF a PDF/A pdfToPDFA.title=Da PDF a PDF/A
pdfToPDFA.header=Da PDF a PDF/A pdfToPDFA.header=Da PDF a PDF/A
pdfToPDFA.credit=Questo servizio utilizza Ghostscript per la conversione in PDF/A. pdfToPDFA.credit=Questo servizio utilizza qpdf per la conversione in PDF/A.
pdfToPDFA.submit=Converti pdfToPDFA.submit=Converti
pdfToPDFA.tip=Attualmente non funziona per più input contemporaneamente pdfToPDFA.tip=Attualmente non funziona per più input contemporaneamente
pdfToPDFA.outputFormat=Formato di output pdfToPDFA.outputFormat=Formato di output
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Livello segnalibro: seleziona il livello dei segnalibri d
splitByChapters.desc.3=Includi metadati: se selezionato, i metadati del PDF originale verranno inclusi in ogni PDF diviso. splitByChapters.desc.3=Includi metadati: se selezionato, i metadati del PDF originale verranno inclusi in ogni PDF diviso.
splitByChapters.desc.4=Consenti duplicati: se selezionata, consente più segnalibri sulla stessa pagina per creare PDF separati. splitByChapters.desc.4=Consenti duplicati: se selezionata, consente più segnalibri sulla stessa pagina per creare PDF separati.
splitByChapters.submit=Dividi PDF splitByChapters.submit=Dividi PDF
#release notes
releases.footer=Rilasci
releases.title=Note di rilascio
releases.header=Note di rilascio
releases.current.version=Rilascio corrente
releases.note=Le note di rilascio sono disponibili solo in inglese

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCRモード
ocr.selectText.11=OCR後に画像を削除する (すべての画像を削除します。変換ステップの一部である場合にのみ有効です)。 ocr.selectText.11=OCR後に画像を削除する (すべての画像を削除します。変換ステップの一部である場合にのみ有効です)。
ocr.selectText.12=レンダリングタイプ (高度) ocr.selectText.12=レンダリングタイプ (高度)
ocr.help=他の言語でこれを使用する方法やDocker以外で使用する方法についてはこのドキュメントをお読みください。 ocr.help=他の言語でこれを使用する方法やDocker以外で使用する方法についてはこのドキュメントをお読みください。
ocr.credit=本サービスにはOCRにOCRmyPDFとTesseractを使用しています。 ocr.credit=本サービスにはOCRにqpdfとTesseractを使用しています。
ocr.submit=OCRでPDFを処理する ocr.submit=OCRでPDFを処理する
@@ -892,7 +892,7 @@ fileToPDF.submit=PDFを変換
#compress #compress
compress.title=圧縮 compress.title=圧縮
compress.header=PDFを圧縮 compress.header=PDFを圧縮
compress.credit=本サービスはPDFの圧縮/最適化にGhostscriptを使用しています。 compress.credit=本サービスはPDFの圧縮/最適化にqpdfを使用しています。
compress.selectText.1=手動モード - 1 から 4 compress.selectText.1=手動モード - 1 から 4
compress.selectText.2=品質レベル: compress.selectText.2=品質レベル:
compress.selectText.3=4 (テキスト画像は最悪) compress.selectText.3=4 (テキスト画像は最悪)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=変更
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDFをPDF/Aに変換 pdfToPDFA.title=PDFをPDF/Aに変換
pdfToPDFA.header=PDFをPDF/Aに変換 pdfToPDFA.header=PDFをPDF/Aに変換
pdfToPDFA.credit=本サービスはPDF/Aの変換にghostscriptを使用しています。 pdfToPDFA.credit=本サービスはPDF/Aの変換にqpdfを使用しています。
pdfToPDFA.submit=変換 pdfToPDFA.submit=変換
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
pdfToPDFA.outputFormat=Output format pdfToPDFA.outputFormat=Output format
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR 모드
ocr.selectText.11=OCR 후 이미지 제거(모든 이미지 제거, 변환 단계의 일부인 경우에만 유용) ocr.selectText.11=OCR 후 이미지 제거(모든 이미지 제거, 변환 단계의 일부인 경우에만 유용)
ocr.selectText.12=렌더 유형(고급) ocr.selectText.12=렌더 유형(고급)
ocr.help=다른 언어 또는 Docker에 포함되지 않은 언어에 대해 사용하는 방법에 대해서는 이 문서를 참조합니다. ocr.help=다른 언어 또는 Docker에 포함되지 않은 언어에 대해 사용하는 방법에 대해서는 이 문서를 참조합니다.
ocr.credit=이 서비스는 OCR에 OCRmyPDF와 Tesseract를 사용합니다. ocr.credit=이 서비스는 OCR에 qpdf와 Tesseract를 사용합니다.
ocr.submit=인식 ocr.submit=인식
@@ -892,7 +892,7 @@ fileToPDF.submit=PDF로 변환
#compress #compress
compress.title=압축 compress.title=압축
compress.header=PDF 압축 compress.header=PDF 압축
compress.credit=이 서비스는 PDF 압축 및 최적화를 위해 Ghostscript를 사용합니다. compress.credit=이 서비스는 PDF 압축 및 최적화를 위해 qpdf를 사용합니다.
compress.selectText.1=수동 모드 - 1에서 4 compress.selectText.1=수동 모드 - 1에서 4
compress.selectText.2=최적화 수준: compress.selectText.2=최적화 수준:
compress.selectText.3=4 (텍스트 이미지에 적합하지 않음) compress.selectText.3=4 (텍스트 이미지에 적합하지 않음)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=변경
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF를 PDF/A로 pdfToPDFA.title=PDF를 PDF/A로
pdfToPDFA.header=PDF 문서를 PDF/A로 변환 pdfToPDFA.header=PDF 문서를 PDF/A로 변환
pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 ghostscript 문서를 사용합니다. pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 qpdf 문서를 사용합니다.
pdfToPDFA.submit=변환 pdfToPDFA.submit=변환
pdfToPDFA.tip=현재 한 번에 여러 입력에 대해 작동하지 않습니다. pdfToPDFA.tip=현재 한 번에 여러 입력에 대해 작동하지 않습니다.
pdfToPDFA.outputFormat=출력 형식 pdfToPDFA.outputFormat=출력 형식
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=북마크 레벨: 분할에 사용할 북마크 레벨을
splitByChapters.desc.3=메타데이터 포함: 체크하면 각 분할된 PDF에는 원본 PDF의 메타데이터가 포함됩니다. splitByChapters.desc.3=메타데이터 포함: 체크하면 각 분할된 PDF에는 원본 PDF의 메타데이터가 포함됩니다.
splitByChapters.desc.4=중복 허용: 중복 북마크가 있는 같은 페이지에 여러 번 분할 PDF를 생성합니다. splitByChapters.desc.4=중복 허용: 중복 북마크가 있는 같은 페이지에 여러 번 분할 PDF를 생성합니다.
splitByChapters.submit=PDF 분할 splitByChapters.submit=PDF 분할
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR-modus
ocr.selectText.11=Verwijder afbeeldingen na OCR (Verwijdert ALLE afbeeldingen, alleen nuttig als onderdeel van conversiestap) ocr.selectText.11=Verwijder afbeeldingen na OCR (Verwijdert ALLE afbeeldingen, alleen nuttig als onderdeel van conversiestap)
ocr.selectText.12=Weergave Type (Geavanceerd) ocr.selectText.12=Weergave Type (Geavanceerd)
ocr.help=Lees deze documentatie over hoe dit te gebruiken voor andere talen en/of gebruik buiten docker ocr.help=Lees deze documentatie over hoe dit te gebruiken voor andere talen en/of gebruik buiten docker
ocr.credit=Deze dienst maakt gebruik van OCRmyPDF en Tesseract voor OCR. ocr.credit=Deze dienst maakt gebruik van qpdf en Tesseract voor OCR.
ocr.submit=Verwerk PDF met OCR ocr.submit=Verwerk PDF met OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Omzetten naar PDF
#compress #compress
compress.title=Comprimeren compress.title=Comprimeren
compress.header=PDF comprimeren compress.header=PDF comprimeren
compress.credit=Deze functie gebruikt Ghostscript voor PDF Compressie/Optimalisatie. compress.credit=Deze functie gebruikt qpdf voor PDF Compressie/Optimalisatie.
compress.selectText.1=Handmatige modus - Van 1 tot 4 compress.selectText.1=Handmatige modus - Van 1 tot 4
compress.selectText.2=Optimalisatieniveau: compress.selectText.2=Optimalisatieniveau:
compress.selectText.3=4 (Verschrikkelijk voor tekstafbeeldingen) compress.selectText.3=4 (Verschrikkelijk voor tekstafbeeldingen)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Wijzigen
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF naar PDF/A pdfToPDFA.title=PDF naar PDF/A
pdfToPDFA.header=PDF naar PDF/A pdfToPDFA.header=PDF naar PDF/A
pdfToPDFA.credit=Deze service gebruikt ghostscript voor PDF/A-conversie pdfToPDFA.credit=Deze service gebruikt qpdf voor PDF/A-conversie
pdfToPDFA.submit=Converteren pdfToPDFA.submit=Converteren
pdfToPDFA.tip=Werkt momenteel niet voor meerdere inputs tegelijkertijd. pdfToPDFA.tip=Werkt momenteel niet voor meerdere inputs tegelijkertijd.
pdfToPDFA.outputFormat=Uitvoerindeling pdfToPDFA.outputFormat=Uitvoerindeling
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Boekmarkeer niveau: Kies het boekmarkeer niveau om te geb
splitByChapters.desc.3=Metadata inclusief: Als gecijfeld, de originele PDF's metadata wordt ingevoegd in elk gesplitst PDF-bestand. splitByChapters.desc.3=Metadata inclusief: Als gecijfeld, de originele PDF's metadata wordt ingevoegd in elk gesplitst PDF-bestand.
splitByChapters.desc.4=Dubbele items toestaan: Als gecijfeld, zorgen multiple boekmarkeersymboolen op dezelfde pagina voor het maken van aparte PDF-bestanden. splitByChapters.desc.4=Dubbele items toestaan: Als gecijfeld, zorgen multiple boekmarkeersymboolen op dezelfde pagina voor het maken van aparte PDF-bestanden.
splitByChapters.submit=PDF splitsen splitByChapters.submit=PDF splitsen
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR-modus
ocr.selectText.11=Fjern bilder etter OCR (Fjerner ALLE bilder, kun nyttig hvis det er en del av konverteringsprosessen) ocr.selectText.11=Fjern bilder etter OCR (Fjerner ALLE bilder, kun nyttig hvis det er en del av konverteringsprosessen)
ocr.selectText.12=Renderingstype (Avansert) ocr.selectText.12=Renderingstype (Avansert)
ocr.help=Vennligst les denne dokumentasjonen for hvordan du bruker dette for andre språk og/eller bruk utenfor Docker. ocr.help=Vennligst les denne dokumentasjonen for hvordan du bruker dette for andre språk og/eller bruk utenfor Docker.
ocr.credit=Denne tjenesten bruker OCRmyPDF og Tesseract for OCR. ocr.credit=Denne tjenesten bruker qpdf og Tesseract for OCR.
ocr.submit=Behandle PDF med OCR ocr.submit=Behandle PDF med OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Konverter til PDF
#compress #compress
compress.title=Komprimer compress.title=Komprimer
compress.header=Komprimer PDF compress.header=Komprimer PDF
compress.credit=Denne tjenesten bruker Ghostscript for PDF-komprimering/optimisering. compress.credit=Denne tjenesten bruker qpdf for PDF-komprimering/optimisering.
compress.selectText.1=Manuell modus - Fra 1 til 4 compress.selectText.1=Manuell modus - Fra 1 til 4
compress.selectText.2=Optimeringsnivå: compress.selectText.2=Optimeringsnivå:
compress.selectText.3=4 (Dårlig for tekstbilder) compress.selectText.3=4 (Dårlig for tekstbilder)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Endre
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF til PDF/A pdfToPDFA.title=PDF til PDF/A
pdfToPDFA.header=PDF til PDF/A pdfToPDFA.header=PDF til PDF/A
pdfToPDFA.credit=Denne tjenesten bruker ghostscript for PDF/A-konvertering pdfToPDFA.credit=Denne tjenesten bruker qpdf for PDF/A-konvertering
pdfToPDFA.submit=Konverter pdfToPDFA.submit=Konverter
pdfToPDFA.tip=Fungere for øyeblikket ikke for flere innganger samtidig pdfToPDFA.tip=Fungere for øyeblikket ikke for flere innganger samtidig
pdfToPDFA.outputFormat=Utdataformat pdfToPDFA.outputFormat=Utdataformat
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Tryb OCR
ocr.selectText.11=Usuń obrazy po OCR (usuwa wszystkie obrazy, przydatne tylko, jeśli jest częścią etapu konwersji) ocr.selectText.11=Usuń obrazy po OCR (usuwa wszystkie obrazy, przydatne tylko, jeśli jest częścią etapu konwersji)
ocr.selectText.12=Typ renderowania (zaawansowany) ocr.selectText.12=Typ renderowania (zaawansowany)
ocr.help=Przeczytaj tę dokumentację, aby dowiedzieć się, jak używać tego w innych językach i/lub nie używać docker ocr.help=Przeczytaj tę dokumentację, aby dowiedzieć się, jak używać tego w innych językach i/lub nie używać docker
ocr.credit=Ta usługa używa OCRmyPDF i Tesseract do OCR. ocr.credit=Ta usługa używa qpdf i Tesseract do OCR.
ocr.submit=Przetwarzaj PDF za pomocą OCR ocr.submit=Przetwarzaj PDF za pomocą OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Konwertuj na PDF
#compress #compress
compress.title=Kompresuj compress.title=Kompresuj
compress.header=Kompresuj PDF compress.header=Kompresuj PDF
compress.credit=Ta usługa używa Ghostscript do kompresji/optymalizacji PDF. compress.credit=Ta usługa używa qpdf do kompresji/optymalizacji PDF.
compress.selectText.1=Tryb ręczny - Od 1 do 4 compress.selectText.1=Tryb ręczny - Od 1 do 4
compress.selectText.2=Poziom optymalizacji: compress.selectText.2=Poziom optymalizacji:
compress.selectText.3=4 (Duże dla obrazów tekstowych) compress.selectText.3=4 (Duże dla obrazów tekstowych)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Zmień
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF na PDF/A pdfToPDFA.title=PDF na PDF/A
pdfToPDFA.header=PDF na PDF/A pdfToPDFA.header=PDF na PDF/A
pdfToPDFA.credit=Ta usługa używa ghostscript do konwersji PDF/A pdfToPDFA.credit=Ta usługa używa qpdf do konwersji PDF/A
pdfToPDFA.submit=Konwertuj pdfToPDFA.submit=Konwertuj
pdfToPDFA.tip=Tylko jeden plik na raz pdfToPDFA.tip=Tylko jeden plik na raz
pdfToPDFA.outputFormat=Format wyjściowy: pdfToPDFA.outputFormat=Format wyjściowy:
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Poziom Zakładek: Wybierz poziom zakładek, który ma zos
splitByChapters.desc.3=Dołącz Metadane: Jeśli opcja ta jest zaznaczona, metadane oryginalnego pliku PDF zostaną uwzględnione w każdym rozdzielonych plików PDF. splitByChapters.desc.3=Dołącz Metadane: Jeśli opcja ta jest zaznaczona, metadane oryginalnego pliku PDF zostaną uwzględnione w każdym rozdzielonych plików PDF.
splitByChapters.desc.4=Zezwól na Duplikaty: Jeśli ta opcja jest zaznaczona, pozwala na tworzenie oddzielnych plików PDF przez wiele zakładek na tej samej stronie. splitByChapters.desc.4=Zezwól na Duplikaty: Jeśli ta opcja jest zaznaczona, pozwala na tworzenie oddzielnych plików PDF przez wiele zakładek na tej samej stronie.
splitByChapters.submit=Podziel PDF splitByChapters.submit=Podziel PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Modo OCR
ocr.selectText.11=Remover imagens após o OCR (remove TODAS as imagens, útil apenas como parte do processo de conversão) ocr.selectText.11=Remover imagens após o OCR (remove TODAS as imagens, útil apenas como parte do processo de conversão)
ocr.selectText.12=Tipo de Renderização (avançado) ocr.selectText.12=Tipo de Renderização (avançado)
ocr.help=Por favor, leia a documentação sobre como usar isso para outros idiomas e/ou fora do ambiente Docker ocr.help=Por favor, leia a documentação sobre como usar isso para outros idiomas e/ou fora do ambiente Docker
ocr.credit=Este serviço usa OCRmyPDF e Tesseract para OCR. ocr.credit=Este serviço usa qpdf e Tesseract para OCR.
ocr.submit=Processar PDF com OCR ocr.submit=Processar PDF com OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Converter para PDF
#compress #compress
compress.title=Comprimir compress.title=Comprimir
compress.header=Comprimir PDF compress.header=Comprimir PDF
compress.credit=Este serviço usa o Ghostscript para compressão/otimização de PDF. compress.credit=Este serviço usa o qpdf para compressão/otimização de PDF.
compress.selectText.1=Modo Manual - De 1 a 4 compress.selectText.1=Modo Manual - De 1 a 4
compress.selectText.2=Nível de Otimização: compress.selectText.2=Nível de Otimização:
compress.selectText.3=4 (Pior para imagens de texto) compress.selectText.3=4 (Pior para imagens de texto)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Alterar
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF para PDF/A pdfToPDFA.title=PDF para PDF/A
pdfToPDFA.header=PDF para PDF/A pdfToPDFA.header=PDF para PDF/A
pdfToPDFA.credit=Este serviço usa ghostscript para conversão de PDF/A pdfToPDFA.credit=Este serviço usa qpdf para conversão de PDF/A
pdfToPDFA.submit=Converter pdfToPDFA.submit=Converter
pdfToPDFA.tip=Atualmente não funciona para múltiplas entradas ao mesmo tempo pdfToPDFA.tip=Atualmente não funciona para múltiplas entradas ao mesmo tempo
pdfToPDFA.outputFormat=Formato de saída pdfToPDFA.outputFormat=Formato de saída
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Nível de Marcador: Escolha o nível de marcador a ser us
splitByChapters.desc.3=Incluir Metadados: Se marcado, os metadados do PDF original serão incluidos em cada divisão do PDF. splitByChapters.desc.3=Incluir Metadados: Se marcado, os metadados do PDF original serão incluidos em cada divisão do PDF.
splitByChapters.desc.4=Permitir Cópias: Se marcado, habilita vários marcadores na mesma página para criar PDFs separados. splitByChapters.desc.4=Permitir Cópias: Se marcado, habilita vários marcadores na mesma página para criar PDFs separados.
splitByChapters.submit=Dividir PDF splitByChapters.submit=Dividir PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Modo OCR
ocr.selectText.11=Remover imagens após o OCR (remove TODAS as imagens, útil apenas como parte do processo de conversão) ocr.selectText.11=Remover imagens após o OCR (remove TODAS as imagens, útil apenas como parte do processo de conversão)
ocr.selectText.12=Tipo de renderização (avançado) ocr.selectText.12=Tipo de renderização (avançado)
ocr.help=Por favor, leia a documentação sobre como usar isso para outros idiomas e/ou fora do ambiente Docker ocr.help=Por favor, leia a documentação sobre como usar isso para outros idiomas e/ou fora do ambiente Docker
ocr.credit=Este serviço usa OCRmyPDF e Tesseract para OCR. ocr.credit=Este serviço usa qpdf e Tesseract para OCR.
ocr.submit=Processar PDF com OCR ocr.submit=Processar PDF com OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Converter para PDF
#compress #compress
compress.title=Comprimir compress.title=Comprimir
compress.header=Comprimir PDF compress.header=Comprimir PDF
compress.credit=Este serviço usa o Ghostscript para compressão/otimização de PDF. compress.credit=Este serviço usa o qpdf para compressão/otimização de PDF.
compress.selectText.1=Modo Manual - De 1 a 4 compress.selectText.1=Modo Manual - De 1 a 4
compress.selectText.2=Nível de Otimização: compress.selectText.2=Nível de Otimização:
compress.selectText.3=4 (Pior para imagens de texto) compress.selectText.3=4 (Pior para imagens de texto)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Mudar
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF para PDF/A pdfToPDFA.title=PDF para PDF/A
pdfToPDFA.header=PDF para PDF/A pdfToPDFA.header=PDF para PDF/A
pdfToPDFA.credit=Este serviço usa ghostscript para Conversão de PDF/A pdfToPDFA.credit=Este serviço usa qpdf para Conversão de PDF/A
pdfToPDFA.submit=Converter pdfToPDFA.submit=Converter
pdfToPDFA.tip=Actualmente não funciona para múltiplos inputs de uma só vez pdfToPDFA.tip=Actualmente não funciona para múltiplos inputs de uma só vez
pdfToPDFA.outputFormat=Formato de saída pdfToPDFA.outputFormat=Formato de saída
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Nível de Anotações: Escolha o nível das anotações a
splitByChapters.desc.3=Inclua Metadados: Se marcado, os metadados originais do PDF serão incluídos em cada PDF dividido. splitByChapters.desc.3=Inclua Metadados: Se marcado, os metadados originais do PDF serão incluídos em cada PDF dividido.
splitByChapters.desc.4=Permitir Duplicatas: Se marcado, permite a criação de vários bookmarks na mesma página para criar separadamente vários PDFs. splitByChapters.desc.4=Permitir Duplicatas: Se marcado, permite a criação de vários bookmarks na mesma página para criar separadamente vários PDFs.
splitByChapters.submit=Dividir o PDF splitByChapters.submit=Dividir o PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Mod OCR
ocr.selectText.11=Elimină imaginile după OCR (Elimină TOATE imaginile, util doar în etapa de conversie) ocr.selectText.11=Elimină imaginile după OCR (Elimină TOATE imaginile, util doar în etapa de conversie)
ocr.selectText.12=Tip de redare (Avansat) ocr.selectText.12=Tip de redare (Avansat)
ocr.help=Citiți documentația pentru a afla cum să utilizați acest serviciu pentru alte limbi și/sau în afara mediului Docker ocr.help=Citiți documentația pentru a afla cum să utilizați acest serviciu pentru alte limbi și/sau în afara mediului Docker
ocr.credit=Acest serviciu utilizează OCRmyPDF și Tesseract pentru OCR. ocr.credit=Acest serviciu utilizează qpdf și Tesseract pentru OCR.
ocr.submit=Procesează PDF-ul cu OCR ocr.submit=Procesează PDF-ul cu OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Convertiți în PDF
#compress #compress
compress.title=Comprimare compress.title=Comprimare
compress.header=Comprimare PDF compress.header=Comprimare PDF
compress.credit=Acest serviciu utilizează OCRmyPDF pentru comprimarea/optimizarea PDF-urilor. compress.credit=Acest serviciu utilizează qpdf pentru comprimarea/optimizarea PDF-urilor.
compress.selectText.1=Nivel de optimizare: compress.selectText.1=Nivel de optimizare:
compress.selectText.2=0 (Fără optimizare) compress.selectText.2=0 (Fără optimizare)
compress.selectText.3=1 (Implicit, optimizare fără pierdere) compress.selectText.3=1 (Implicit, optimizare fără pierdere)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Schimbă
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF către PDF/A pdfToPDFA.title=PDF către PDF/A
pdfToPDFA.header=PDF către PDF/A pdfToPDFA.header=PDF către PDF/A
pdfToPDFA.credit=Acest serviciu utilizează ghostscript pentru conversia în PDF/A pdfToPDFA.credit=Acest serviciu utilizează qpdf pentru conversia în PDF/A
pdfToPDFA.submit=Convertește pdfToPDFA.submit=Convertește
pdfToPDFA.tip=În prezent nu funcționează pentru mai multe intrări simultan pdfToPDFA.tip=În prezent nu funcționează pentru mai multe intrări simultan
pdfToPDFA.outputFormat=Format de ieșire pdfToPDFA.outputFormat=Format de ieșire
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR режим
ocr.selectText.11=Удалить изображения после OCR (удаляет ВСЕ изображения, полезно только в том случае, если они являются частью шага преобразования) ocr.selectText.11=Удалить изображения после OCR (удаляет ВСЕ изображения, полезно только в том случае, если они являются частью шага преобразования)
ocr.selectText.12=Тип рендера (расширенный) ocr.selectText.12=Тип рендера (расширенный)
ocr.help=Прочтите эту документацию о том, как использовать это для других языков и/или использовать не в докере. ocr.help=Прочтите эту документацию о том, как использовать это для других языков и/или использовать не в докере.
ocr.credit=Этот сервис использует OCRmyPDF и Tesseract для OCR. ocr.credit=Этот сервис использует qpdf и Tesseract для OCR.
ocr.submit=Обработка PDF с OCR ocr.submit=Обработка PDF с OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Преобразовать в PDF
#compress #compress
compress.title=Сжать compress.title=Сжать
compress.header=Сжать PDF compress.header=Сжать PDF
compress.credit=Эта служба использует Ghostscript для сжатия/оптимизации PDF. compress.credit=Эта служба использует qpdf для сжатия/оптимизации PDF.
compress.selectText.1=Ручной режим - от 1 до 4 compress.selectText.1=Ручной режим - от 1 до 4
compress.selectText.2=Уровень оптимизации: compress.selectText.2=Уровень оптимизации:
compress.selectText.3=4 (Ужасно для текстовых изображений) compress.selectText.3=4 (Ужасно для текстовых изображений)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Изменить
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF в PDF/A pdfToPDFA.title=PDF в PDF/A
pdfToPDFA.header=PDF в PDF/A pdfToPDFA.header=PDF в PDF/A
pdfToPDFA.credit=Этот сервис использует ghostscript для преобразования PDF/A pdfToPDFA.credit=Этот сервис использует qpdf для преобразования PDF/A
pdfToPDFA.submit=Конвертировать pdfToPDFA.submit=Конвертировать
pdfToPDFA.tip=В настоящее время не поддерживается при нескольких входных данных одновременно pdfToPDFA.tip=В настоящее время не поддерживается при нескольких входных данных одновременно
pdfToPDFA.outputFormat=Формат вывода pdfToPDFA.outputFormat=Формат вывода
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Уровень закладки: выберите уро
splitByChapters.desc.3=Включить метаданные: если эта опция отмечена, метаданные исходного PDF будут включены в каждый разбитый PDF. splitByChapters.desc.3=Включить метаданные: если эта опция отмечена, метаданные исходного PDF будут включены в каждый разбитый PDF.
splitByChapters.desc.4=Позволять дубликаты: если эта опция отмечена, на одной странице могут быть созданы несколько PDF из-за нескольких одинаковых закладок. splitByChapters.desc.4=Позволять дубликаты: если эта опция отмечена, на одной странице могут быть созданы несколько PDF из-за нескольких одинаковых закладок.
splitByChapters.submit=Разделить PDF splitByChapters.submit=Разделить PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR režim
ocr.selectText.11=Odstrániť obrázky po OCR (Odstráni VŠETKY obrázky, užitočné iba ak je súčasťou konverzného kroku) ocr.selectText.11=Odstrániť obrázky po OCR (Odstráni VŠETKY obrázky, užitočné iba ak je súčasťou konverzného kroku)
ocr.selectText.12=Typ vykreslenia (Pokročilé) ocr.selectText.12=Typ vykreslenia (Pokročilé)
ocr.help=Prosím, prečítajte si túto dokumentáciu o tom, ako používať OCR pre iné jazyky a/alebo použitie mimo docker ocr.help=Prosím, prečítajte si túto dokumentáciu o tom, ako používať OCR pre iné jazyky a/alebo použitie mimo docker
ocr.credit=Táto služba používa OCRmyPDF a Tesseract pre OCR. ocr.credit=Táto služba používa qpdf a Tesseract pre OCR.
ocr.submit=Spracovať PDF s OCR ocr.submit=Spracovať PDF s OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Konvertovať do PDF
#compress #compress
compress.title=Komprimovať compress.title=Komprimovať
compress.header=Komprimovať PDF compress.header=Komprimovať PDF
compress.credit=Táto služba používa Ghostscript pre kompresiu/optimalizáciu PDF. compress.credit=Táto služba používa qpdf pre kompresiu/optimalizáciu PDF.
compress.selectText.1=Manuálny režim - Od 1 do 4 compress.selectText.1=Manuálny režim - Od 1 do 4
compress.selectText.2=Úroveň optimalizácie: compress.selectText.2=Úroveň optimalizácie:
compress.selectText.3=4 (Hrozné pre textové obrázky) compress.selectText.3=4 (Hrozné pre textové obrázky)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Zmeniť
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF na PDF/A pdfToPDFA.title=PDF na PDF/A
pdfToPDFA.header=PDF na PDF/A pdfToPDFA.header=PDF na PDF/A
pdfToPDFA.credit=Táto služba používa ghostscript na konverziu PDF/A pdfToPDFA.credit=Táto služba používa qpdf na konverziu PDF/A
pdfToPDFA.submit=Konvertovať pdfToPDFA.submit=Konvertovať
pdfToPDFA.tip=Momentálne nefunguje pre viacero vstupov naraz pdfToPDFA.tip=Momentálne nefunguje pre viacero vstupov naraz
pdfToPDFA.outputFormat=Výstupný formát pdfToPDFA.outputFormat=Výstupný formát
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Režim OCR-a
ocr.selectText.11=Ukloni slike nakon OCR-a (Uklanja SVE slike, korisno samo ako je deo koraka konverzije) ocr.selectText.11=Ukloni slike nakon OCR-a (Uklanja SVE slike, korisno samo ako je deo koraka konverzije)
ocr.selectText.12=Tip rendiranja (Napredno) ocr.selectText.12=Tip rendiranja (Napredno)
ocr.help=Molimo vas da pročitate ovu dokumentaciju o tome kako koristiti ovo za druge jezike i/ili korišćenje van docker-a ocr.help=Molimo vas da pročitate ovu dokumentaciju o tome kako koristiti ovo za druge jezike i/ili korišćenje van docker-a
ocr.credit=Ova usluga koristi OCRmyPDF i Tesseract za OCR. ocr.credit=Ova usluga koristi qpdf i Tesseract za OCR.
ocr.submit=Obradi PDF sa OCR-om ocr.submit=Obradi PDF sa OCR-om
@@ -892,7 +892,7 @@ fileToPDF.submit=Konvertuj u PDF
#compress #compress
compress.title=Kompresija compress.title=Kompresija
compress.header=Kompresuj PDF compress.header=Kompresuj PDF
compress.credit=Ova usluga koristi Ghostscript za kompresiju / optimizaciju PDF-a. compress.credit=Ova usluga koristi qpdf za kompresiju / optimizaciju PDF-a.
compress.selectText.1=Ručni režim - Od 1 do 4 compress.selectText.1=Ručni režim - Od 1 do 4
compress.selectText.2=Nivo optimizacije: compress.selectText.2=Nivo optimizacije:
compress.selectText.3=4 (Užasno za tekstualne slike) compress.selectText.3=4 (Užasno za tekstualne slike)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Promeni
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF u PDF/A pdfToPDFA.title=PDF u PDF/A
pdfToPDFA.header=PDF u PDF/A pdfToPDFA.header=PDF u PDF/A
pdfToPDFA.credit=Ova usluga koristi ghostscript za konverziju u PDF/A format pdfToPDFA.credit=Ova usluga koristi qpdf za konverziju u PDF/A format
pdfToPDFA.submit=Konvertuj pdfToPDFA.submit=Konvertuj
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format pdfToPDFA.outputFormat=Output format
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR-läge
ocr.selectText.11=Ta bort bilder efter OCR (tar bort ALLA bilder, endast användbart som en del av konverteringssteget) ocr.selectText.11=Ta bort bilder efter OCR (tar bort ALLA bilder, endast användbart som en del av konverteringssteget)
ocr.selectText.12=Renderingstyp (avancerat) ocr.selectText.12=Renderingstyp (avancerat)
ocr.help=Vänligen läs denna dokumentation om hur du använder detta för andra språk och/eller använder inte i docker ocr.help=Vänligen läs denna dokumentation om hur du använder detta för andra språk och/eller använder inte i docker
ocr.credit=Denna tjänst använder OCRmyPDF och Tesseract för OCR. ocr.credit=Denna tjänst använder qpdf och Tesseract för OCR.
ocr.submit=Bearbeta PDF med OCR ocr.submit=Bearbeta PDF med OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Konvertera till PDF
#compress #compress
compress.title=Komprimera compress.title=Komprimera
compress.header=Komprimera PDF compress.header=Komprimera PDF
compress.credit=Denna tjänst använder Ghostscript för PDF-komprimering/optimering. compress.credit=Denna tjänst använder qpdf för PDF-komprimering/optimering.
compress.selectText.1=Manuellt läge - Från 1 till 4 compress.selectText.1=Manuellt läge - Från 1 till 4
compress.selectText.2=Optimeringsnivå: compress.selectText.2=Optimeringsnivå:
compress.selectText.3=4 (Fruktansvärt för textbilder) compress.selectText.3=4 (Fruktansvärt för textbilder)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Ändra
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF till PDF/A pdfToPDFA.title=PDF till PDF/A
pdfToPDFA.header=PDF till PDF/A pdfToPDFA.header=PDF till PDF/A
pdfToPDFA.credit=Denna tjänst använder ghostscript för PDF/A-konvertering pdfToPDFA.credit=Denna tjänst använder qpdf för PDF/A-konvertering
pdfToPDFA.submit=Konvertera pdfToPDFA.submit=Konvertera
pdfToPDFA.tip=Fungerar för närvarande inte för flera inmatningar samtidigt pdfToPDFA.tip=Fungerar för närvarande inte för flera inmatningar samtidigt
pdfToPDFA.outputFormat=Utdataformat pdfToPDFA.outputFormat=Utdataformat
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bokmärkesnivå: Välj nivån av bokmärken att använda
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Tillåt duplicieringar: Om kryssrutan är markerad tillåts flera bokmärken på samma sida skapa individuella PDF:er. splitByChapters.desc.4=Tillåt duplicieringar: Om kryssrutan är markerad tillåts flera bokmärken på samma sida skapa individuella PDF:er.
splitByChapters.submit=Dela upp PDF splitByChapters.submit=Dela upp PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=โหมด OCR
ocr.selectText.11=ลบภาพหลังจาก OCR (ลบภาพทั้งหมด, มีประโยชน์เฉพาะหากเป็นส่วนหนึ่งของขั้นตอนการแปลง) ocr.selectText.11=ลบภาพหลังจาก OCR (ลบภาพทั้งหมด, มีประโยชน์เฉพาะหากเป็นส่วนหนึ่งของขั้นตอนการแปลง)
ocr.selectText.12=ประเภทการเรนเดอร์ (ขั้นสูง) ocr.selectText.12=ประเภทการเรนเดอร์ (ขั้นสูง)
ocr.help=โปรดอ่านเอกสารนี้เพื่อใช้งานภาษาอื่นๆ และ/หรือใช้งานนอก docker ocr.help=โปรดอ่านเอกสารนี้เพื่อใช้งานภาษาอื่นๆ และ/หรือใช้งานนอก docker
ocr.credit=บริการนี้ใช้ OCRmyPDF และ Tesseract สำหรับ OCR ocr.credit=บริการนี้ใช้ qpdf และ Tesseract สำหรับ OCR
ocr.submit=ประมวลผล PDF ด้วย OCR ocr.submit=ประมวลผล PDF ด้วย OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=แปลงเป็น PDF
#compress #compress
compress.title=บีบอัด compress.title=บีบอัด
compress.header=บีบอัด PDF compress.header=บีบอัด PDF
compress.credit=บริการนี้ใช้ Ghostscript สำหรับการบีบอัด/การเพิ่มประสิทธิภาพ PDF compress.credit=บริการนี้ใช้ qpdf สำหรับการบีบอัด/การเพิ่มประสิทธิภาพ PDF
compress.selectText.1=โหมดแมนนวล - จาก 1 ถึง 4 compress.selectText.1=โหมดแมนนวล - จาก 1 ถึง 4
compress.selectText.2=ระดับการเพิ่มประสิทธิภาพ: compress.selectText.2=ระดับการเพิ่มประสิทธิภาพ:
compress.selectText.3=4 (ไม่ดีสำหรับภาพข้อความ) compress.selectText.3=4 (ไม่ดีสำหรับภาพข้อความ)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=เปลี่ยน
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF เป็น PDF/A pdfToPDFA.title=PDF เป็น PDF/A
pdfToPDFA.header=PDF เป็น PDF/A pdfToPDFA.header=PDF เป็น PDF/A
pdfToPDFA.credit=บริการนี้ใช้ ghostscript สำหรับการแปลง PDF/A pdfToPDFA.credit=บริการนี้ใช้ qpdf สำหรับการแปลง PDF/A
pdfToPDFA.submit=แปลง pdfToPDFA.submit=แปลง
pdfToPDFA.tip=ปัจจุบันไม่ทำงานสำหรับการป้อนข้อมูลหลายรายการพร้อมกัน pdfToPDFA.tip=ปัจจุบันไม่ทำงานสำหรับการป้อนข้อมูลหลายรายการพร้อมกัน
pdfToPDFA.outputFormat=รูปแบบผลลัพธ์ pdfToPDFA.outputFormat=รูปแบบผลลัพธ์
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=ระดับบุคคลที่ได้รับ
splitByChapters.desc.3=รวมข้อมูลเสริม: หากถูกเลือก ข้อมูลเสริมของไฟล์ PDF ที่เดิมจะถูกรวมอยู่ในแต่ละไฟล์ที่แบ่งออก splitByChapters.desc.3=รวมข้อมูลเสริม: หากถูกเลือก ข้อมูลเสริมของไฟล์ PDF ที่เดิมจะถูกรวมอยู่ในแต่ละไฟล์ที่แบ่งออก
splitByChapters.desc.4=อนุญาตให้มีการซ้ำ: หากถูกเลือก จะทำให้สามารถสร้างไฟล์ PDF แยกออกมาจากหน้าเดียวกันได้หลายรายการ splitByChapters.desc.4=อนุญาตให้มีการซ้ำ: หากถูกเลือก จะทำให้สามารถสร้างไฟล์ PDF แยกออกมาจากหน้าเดียวกันได้หลายรายการ
splitByChapters.submit=แบ่งไฟล์ PDF splitByChapters.submit=แบ่งไฟล์ PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR Modu
ocr.selectText.11=OCR'den sonra resimleri kaldır (TÜM resimleri kaldırır, sadece dönüşüm adımının bir parçasıysa yararlıdır) ocr.selectText.11=OCR'den sonra resimleri kaldır (TÜM resimleri kaldırır, sadece dönüşüm adımının bir parçasıysa yararlıdır)
ocr.selectText.12=Render Türü (İleri Seviye) ocr.selectText.12=Render Türü (İleri Seviye)
ocr.help=Lütfen bu belgede başka dillerde nasıl kullanılacağı ve/veya docker'da kullanılmaması hakkında bilgi edinin ocr.help=Lütfen bu belgede başka dillerde nasıl kullanılacağı ve/veya docker'da kullanılmaması hakkında bilgi edinin
ocr.credit=Bu hizmet OCR için OCRmyPDF ve Tesseract'ı kullanır. ocr.credit=Bu hizmet OCR için qpdf ve Tesseract'ı kullanır.
ocr.submit=PDF'i OCR(Metin Tanıma) ile İşle ocr.submit=PDF'i OCR(Metin Tanıma) ile İşle
@@ -892,7 +892,7 @@ fileToPDF.submit=PDF'e Dönüştür
#compress #compress
compress.title=Sıkıştır compress.title=Sıkıştır
compress.header=PDF'i Sıkıştır compress.header=PDF'i Sıkıştır
compress.credit=Bu hizmet PDF Sıkıştırma/Optimizasyonu için Ghostscript kullanır. compress.credit=Bu hizmet PDF Sıkıştırma/Optimizasyonu için qpdf kullanır.
compress.selectText.1=Manuel Mod - 1'den 4'e compress.selectText.1=Manuel Mod - 1'den 4'e
compress.selectText.2=Optimizasyon seviyesi: compress.selectText.2=Optimizasyon seviyesi:
compress.selectText.3=4 (Metin resimleri için hiç uygun değil) compress.selectText.3=4 (Metin resimleri için hiç uygun değil)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Değiştir
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF'den PDF/A'ya pdfToPDFA.title=PDF'den PDF/A'ya
pdfToPDFA.header=PDF'den PDF/A'ya pdfToPDFA.header=PDF'den PDF/A'ya
pdfToPDFA.credit=Bu hizmet PDF/A dönüşümü için ghostscript kullanır pdfToPDFA.credit=Bu hizmet PDF/A dönüşümü için qpdf kullanır
pdfToPDFA.submit=Dönüştür pdfToPDFA.submit=Dönüştür
pdfToPDFA.tip=Şu anda aynı anda birden fazla giriş için çalışmıyor pdfToPDFA.tip=Şu anda aynı anda birden fazla giriş için çalışmıyor
pdfToPDFA.outputFormat=Çıkış formatı pdfToPDFA.outputFormat=Çıkış formatı
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Режим OCR
ocr.selectText.11=Видалити зображення після OCR (видаляє ВСІ зображення, корисно лише в тому випадку, якщо вони є частиною етапу перетворення) ocr.selectText.11=Видалити зображення після OCR (видаляє ВСІ зображення, корисно лише в тому випадку, якщо вони є частиною етапу перетворення)
ocr.selectText.12=Тип рендеру (розширений) ocr.selectText.12=Тип рендеру (розширений)
ocr.help=Прочитайте цю документацію про те, як використовувати це для інших мов і/або використовувати не в докері. ocr.help=Прочитайте цю документацію про те, як використовувати це для інших мов і/або використовувати не в докері.
ocr.credit=Цей сервіс використовує OCRmyPDF та Tesseract для OCR. ocr.credit=Цей сервіс використовує qpdf та Tesseract для OCR.
ocr.submit=Обробка PDF з OCR ocr.submit=Обробка PDF з OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Перетворити у PDF
#compress #compress
compress.title=Стиснути compress.title=Стиснути
compress.header=Стиснути PDF compress.header=Стиснути PDF
compress.credit=Ця служба використовує Ghostscript для стиснення/оптимізації PDF. compress.credit=Ця служба використовує qpdf для стиснення/оптимізації PDF.
compress.selectText.1=Ручний режим - від 1 до 4 compress.selectText.1=Ручний режим - від 1 до 4
compress.selectText.2=Рівень оптимізації: compress.selectText.2=Рівень оптимізації:
compress.selectText.3=4 (Жахливо для текстових зображень) compress.selectText.3=4 (Жахливо для текстових зображень)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Змінити
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF в PDF/A pdfToPDFA.title=PDF в PDF/A
pdfToPDFA.header=PDF в PDF/A pdfToPDFA.header=PDF в PDF/A
pdfToPDFA.credit=Цей сервіс використовує ghostscript для перетворення у формат PDF/A pdfToPDFA.credit=Цей сервіс використовує qpdf для перетворення у формат PDF/A
pdfToPDFA.submit=Конвертувати pdfToPDFA.submit=Конвертувати
pdfToPDFA.tip=Наразі не працює для кількох вхідних файлів одночасно pdfToPDFA.tip=Наразі не працює для кількох вхідних файлів одночасно
pdfToPDFA.outputFormat=Вихідний формат pdfToPDFA.outputFormat=Вихідний формат
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=Chế độ OCR
ocr.selectText.11=Xóa hình ảnh sau khi OCR (Xóa TẤT CẢ hình ảnh, chỉ hữu ích nếu là một phần của bước chuyển đổi) ocr.selectText.11=Xóa hình ảnh sau khi OCR (Xóa TẤT CẢ hình ảnh, chỉ hữu ích nếu là một phần của bước chuyển đổi)
ocr.selectText.12=Loại hiển thị (Nâng cao) ocr.selectText.12=Loại hiển thị (Nâng cao)
ocr.help=Vui lòng đọc tài liệu này về cách sử dụng cho các ngôn ngữ khác và/hoặc sử dụng không trong docker ocr.help=Vui lòng đọc tài liệu này về cách sử dụng cho các ngôn ngữ khác và/hoặc sử dụng không trong docker
ocr.credit=Dịch vụ này sử dụng OCRmyPDF và Tesseract cho OCR. ocr.credit=Dịch vụ này sử dụng qpdf và Tesseract cho OCR.
ocr.submit=Xử lý PDF với OCR ocr.submit=Xử lý PDF với OCR
@@ -892,7 +892,7 @@ fileToPDF.submit=Chuyển đổi sang PDF
#compress #compress
compress.title=Nén compress.title=Nén
compress.header=Nén PDF compress.header=Nén PDF
compress.credit=Dịch vụ này sử dụng Ghostscript để Nén/Tối ưu hóa PDF. compress.credit=Dịch vụ này sử dụng qpdf để Nén/Tối ưu hóa PDF.
compress.selectText.1=Chế độ thủ công - Từ 1 đến 4 compress.selectText.1=Chế độ thủ công - Từ 1 đến 4
compress.selectText.2=Mức độ tối ưu hóa: compress.selectText.2=Mức độ tối ưu hóa:
compress.selectText.3=4 (Tệ cho hình ảnh văn bản) compress.selectText.3=4 (Tệ cho hình ảnh văn bản)
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=Thay đổi
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF sang PDF/A pdfToPDFA.title=PDF sang PDF/A
pdfToPDFA.header=PDF sang PDF/A pdfToPDFA.header=PDF sang PDF/A
pdfToPDFA.credit=Dịch vụ này sử dụng ghostscript để chuyển đổi PDF/A pdfToPDFA.credit=Dịch vụ này sử dụng qpdf để chuyển đổi PDF/A
pdfToPDFA.submit=Chuyển đổi pdfToPDFA.submit=Chuyển đổi
pdfToPDFA.tip=Hiện tại không hoạt động với nhiều đầu vào cùng lúc pdfToPDFA.tip=Hiện tại không hoạt động với nhiều đầu vào cùng lúc
pdfToPDFA.outputFormat=Định dạng đầu ra pdfToPDFA.outputFormat=Định dạng đầu ra
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR模式
ocr.selectText.11=OCR后移除图像移除所有图像只有在转换步骤中才有用 ocr.selectText.11=OCR后移除图像移除所有图像只有在转换步骤中才有用
ocr.selectText.12=渲染类型(高级) ocr.selectText.12=渲染类型(高级)
ocr.help=请阅读此文档,了解如何将其用于其他语言和/或不在docker中使用。 ocr.help=请阅读此文档,了解如何将其用于其他语言和/或不在docker中使用。
ocr.credit=此服务使用OCRmyPDF和Tesseract进行OCR。 ocr.credit=此服务使用qpdf和Tesseract进行OCR。
ocr.submit=用OCR处理PDF ocr.submit=用OCR处理PDF
@@ -892,7 +892,7 @@ fileToPDF.submit=转换为 PDF
#compress #compress
compress.title=压缩 compress.title=压缩
compress.header=压缩PDF compress.header=压缩PDF
compress.credit=此服务使用Ghostscript进行PDF压缩/优化。 compress.credit=此服务使用qpdf进行PDF压缩/优化。
compress.selectText.1=手动模式 - 从 1 到 4 compress.selectText.1=手动模式 - 从 1 到 4
compress.selectText.2=优化级别: compress.selectText.2=优化级别:
compress.selectText.3=4文本图像很糟糕 compress.selectText.3=4文本图像很糟糕
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=更改
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF转PDF/A pdfToPDFA.title=PDF转PDF/A
pdfToPDFA.header=将PDF转换为PDF/A pdfToPDFA.header=将PDF转换为PDF/A
pdfToPDFA.credit=此服务使用ghostscript进行PDF/A转换 pdfToPDFA.credit=此服务使用qpdf进行PDF/A转换
pdfToPDFA.submit=转换 pdfToPDFA.submit=转换
pdfToPDFA.tip=目前不支持上传多个 pdfToPDFA.tip=目前不支持上传多个
pdfToPDFA.outputFormat=输出格式 pdfToPDFA.outputFormat=输出格式
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
splitByChapters.submit=Split PDF splitByChapters.submit=Split PDF
#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

View File

@@ -868,7 +868,7 @@ ocr.selectText.10=OCR 模式
ocr.selectText.11=移除 OCR 後的影像(移除所有影像,只有在轉換步驟中才有用) ocr.selectText.11=移除 OCR 後的影像(移除所有影像,只有在轉換步驟中才有用)
ocr.selectText.12=渲染類型(進階) ocr.selectText.12=渲染類型(進階)
ocr.help=請閱讀此文件,了解如何使用其他語言和/或在 Docker 中使用 ocr.help=請閱讀此文件,了解如何使用其他語言和/或在 Docker 中使用
ocr.credit=此服務使用 OCRmyPDF 和 Tesseract 進行 OCR。 ocr.credit=此服務使用 qpdf 和 Tesseract 進行 OCR。
ocr.submit=使用 OCR 處理 PDF ocr.submit=使用 OCR 處理 PDF
@@ -892,7 +892,7 @@ fileToPDF.submit=轉換為 PDF
#compress #compress
compress.title=壓縮 compress.title=壓縮
compress.header=壓縮 PDF compress.header=壓縮 PDF
compress.credit=此服務使用 Ghostscript 進行 PDF 壓縮/最佳化。 compress.credit=此服務使用 qpdf 進行 PDF 壓縮/最佳化。
compress.selectText.1=手動模式 - 從 1 到 4 compress.selectText.1=手動模式 - 從 1 到 4
compress.selectText.2=最佳化等級: compress.selectText.2=最佳化等級:
compress.selectText.3=4對於含有文字的影像來說結果很糟 compress.selectText.3=4對於含有文字的影像來說結果很糟
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo
multiTool.redo=Redo
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
@@ -1112,7 +1114,7 @@ changeMetadata.submit=變更
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF 轉 PDF/A pdfToPDFA.title=PDF 轉 PDF/A
pdfToPDFA.header=PDF 轉 PDF/A pdfToPDFA.header=PDF 轉 PDF/A
pdfToPDFA.credit=此服務使用 ghostscript 進行 PDF/A 轉換 pdfToPDFA.credit=此服務使用 qpdf 進行 PDF/A 轉換
pdfToPDFA.submit=轉換 pdfToPDFA.submit=轉換
pdfToPDFA.tip=目前不支援上傳多個 pdfToPDFA.tip=目前不支援上傳多個
pdfToPDFA.outputFormat=輸出格式 pdfToPDFA.outputFormat=輸出格式
@@ -1260,3 +1262,11 @@ splitByChapters.desc.2=書籤層級選擇用於分割的書籤層級0 表
splitByChapters.desc.3=包含中繼資料:如果勾選,原始 PDF 的中繼資料將包含在每個分割後的 PDF 中。 splitByChapters.desc.3=包含中繼資料:如果勾選,原始 PDF 的中繼資料將包含在每個分割後的 PDF 中。
splitByChapters.desc.4=允許重複:如果勾選,允許同一頁面上的多個書籤建立獨立的 PDF。 splitByChapters.desc.4=允許重複:如果勾選,允許同一頁面上的多個書籤建立獨立的 PDF。
splitByChapters.submit=分割 PDF splitByChapters.submit=分割 PDF
#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

View File

@@ -16,7 +16,7 @@ security:
csrfDisabled: true # set to 'true' to disable CSRF protection (not recommended for production) csrfDisabled: true # set to 'true' to disable CSRF protection (not recommended for production)
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1 loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
loginMethod: all # 'all' (Login Username/Password and OAuth2[must be enabled and configured]), 'normal'(only Login with Username/Password) or 'oauth2'(only Login with OAuth2) loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2)
initialLogin: initialLogin:
username: '' # initial username for the first login username: '' # initial username for the first login
password: '' # initial password for the first login password: '' # initial password for the first login
@@ -42,14 +42,14 @@ security:
issuer: '' # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint issuer: '' # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint
clientId: '' # client ID from your provider clientId: '' # client ID from your provider
clientSecret: '' # client secret from your provider clientSecret: '' # client secret from your provider
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
useAsUsername: email # default is 'email'; custom fields can be used as the username useAsUsername: email # default is 'email'; custom fields can be used as the username
scopes: openid, profile, email # specify the scopes for which the application will request permissions scopes: openid, profile, email # specify the scopes for which the application will request permissions
provider: google # set this to your OAuth provider's name, e.g., 'google' or 'keycloak' provider: google # set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
saml2: saml2:
enabled: false # currently in alpha, not recommended for use yet, enableAlphaFunctionality must be set to true enabled: false # Only enabled for paid enterprise clients (enterpriseEdition.enabled must be true)
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
registrationId: stirling registrationId: stirling
idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata
@@ -107,9 +107,9 @@ processExecutor:
sessionLimit: # Process executor instances limits sessionLimit: # Process executor instances limits
libreOfficeSessionLimit: 1 libreOfficeSessionLimit: 1
pdfToHtmlSessionLimit: 1 pdfToHtmlSessionLimit: 1
ocrMyPdfSessionLimit: 2 qpdfSessionLimit: 4
tesseractSessionLimit: 1
pythonOpenCvSessionLimit: 8 pythonOpenCvSessionLimit: 8
ghostScriptSessionLimit: 16
weasyPrintSessionLimit: 16 weasyPrintSessionLimit: 16
installAppSessionLimit: 1 installAppSessionLimit: 1
calibreSessionLimit: 1 calibreSessionLimit: 1
@@ -117,7 +117,7 @@ processExecutor:
libreOfficetimeoutMinutes: 30 libreOfficetimeoutMinutes: 30
pdfToHtmltimeoutMinutes: 20 pdfToHtmltimeoutMinutes: 20
pythonOpenCvtimeoutMinutes: 30 pythonOpenCvtimeoutMinutes: 30
ghostScripttimeoutMinutes: 30
weasyPrinttimeoutMinutes: 30 weasyPrinttimeoutMinutes: 30
installApptimeoutMinutes: 60 installApptimeoutMinutes: 60
calibretimeoutMinutes: 30 calibretimeoutMinutes: 30
tesseractTimeoutMinutes: 30

View File

@@ -407,7 +407,7 @@
{ {
"moduleName": "commons-io:commons-io", "moduleName": "commons-io:commons-io",
"moduleUrl": "https://commons.apache.org/proper/commons-io/", "moduleUrl": "https://commons.apache.org/proper/commons-io/",
"moduleVersion": "2.17.0", "moduleVersion": "2.18.0",
"moduleLicense": "Apache-2.0", "moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },

View File

@@ -64,7 +64,7 @@ function setupFileInput(chooser) {
dragCounter = 0; dragCounter = 0;
fileInput.dispatchEvent(new Event("change", { bubbles: true })); fileInput.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: {source: 'drag-drop'} }));
}; };
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
@@ -81,9 +81,24 @@ function setupFileInput(chooser) {
document.body.addEventListener("drop", dropListener); document.body.addEventListener("drop", dropListener);
$("#" + elementId).on("change", function (e) { $("#" + elementId).on("change", function (e) {
allFiles = Array.from(e.target.files); let element = e.target;
const isDragAndDrop = e.detail?.source == 'drag-drop';
if (element instanceof HTMLInputElement && element.hasAttribute("multiple")) {
allFiles = isDragAndDrop ? allFiles : [... allFiles, ... element.files];
} else {
allFiles = Array.from(isDragAndDrop ? allFiles : [element.files[0]]);
}
if (!isDragAndDrop) {
let dataTransfer = new DataTransfer();
allFiles.forEach(file => dataTransfer.items.add(file));
element.files = dataTransfer.files;
}
handleFileInputChange(this); handleFileInputChange(this);
}); this.dispatchEvent(new CustomEvent("file-input-change", { bubbles: true }));
});
function handleFileInputChange(inputElement) { function handleFileInputChange(inputElement) {
const files = allFiles; const files = allFiles;

View File

@@ -3,7 +3,7 @@ let currentSort = {
descending: false, descending: false,
}; };
document.getElementById("fileInput-input").addEventListener("change", function () { document.getElementById("fileInput-input").addEventListener("file-input-change", function () {
var files = this.files; var files = this.files;
displayFiles(files); displayFiles(files);
}); });

View File

@@ -1,12 +1,20 @@
import { DeletePageCommand } from "./commands/delete-page.js";
import { SelectPageCommand } from "./commands/select.js";
import { SplitFileCommand } from "./commands/split.js";
import { UndoManager } from "./UndoManager.js";
class PdfActionsManager { class PdfActionsManager {
pageDirection; pageDirection;
pagesContainer; pagesContainer;
static selectedPages = []; // Static property shared across all instances static selectedPages = []; // Static property shared across all instances
undoManager;
constructor(id) { constructor(id, undoManager) {
this.pagesContainer = document.getElementById(id); this.pagesContainer = document.getElementById(id);
this.pageDirection = document.documentElement.getAttribute("dir"); this.pageDirection = document.documentElement.getAttribute("dir");
this.undoManager = undoManager || new UndoManager();
var styleElement = document.createElement("link"); var styleElement = document.createElement("link");
styleElement.rel = "stylesheet"; styleElement.rel = "stylesheet";
styleElement.href = "css/pdfActions.css"; styleElement.href = "css/pdfActions.css";
@@ -27,7 +35,8 @@ class PdfActionsManager {
const sibling = imgContainer.previousSibling; const sibling = imgContainer.previousSibling;
if (sibling) { if (sibling) {
this.movePageTo(imgContainer, sibling, true); let movePageCommand = this.movePageTo(imgContainer, sibling, true, true);
this._pushUndoClearRedo(movePageCommand);
} }
} }
@@ -35,7 +44,12 @@ class PdfActionsManager {
var imgContainer = this.getPageContainer(e.target); var imgContainer = this.getPageContainer(e.target);
const sibling = imgContainer.nextSibling; const sibling = imgContainer.nextSibling;
if (sibling) { if (sibling) {
this.movePageTo(imgContainer, sibling.nextSibling, true); let movePageCommand = this.movePageTo(
imgContainer,
sibling.nextSibling,
true
);
this._pushUndoClearRedo(movePageCommand);
} }
} }
@@ -43,30 +57,27 @@ class PdfActionsManager {
var imgContainer = this.getPageContainer(e.target); var imgContainer = this.getPageContainer(e.target);
const img = imgContainer.querySelector("img"); const img = imgContainer.querySelector("img");
this.rotateElement(img, -90); let rotateCommand = this.rotateElement(img, -90);
this._pushUndoClearRedo(rotateCommand);
} }
rotateCWButtonCallback(e) { rotateCWButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target); var imgContainer = this.getPageContainer(e.target);
const img = imgContainer.querySelector("img"); const img = imgContainer.querySelector("img");
this.rotateElement(img, 90); let rotateCommand = this.rotateElement(img, 90);
this._pushUndoClearRedo(rotateCommand);
} }
deletePageButtonCallback(e) { deletePageButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target); let imgContainer = this.getPageContainer(e.target);
this.pagesContainer.removeChild(imgContainer); let deletePageCommand = new DeletePageCommand(
if (this.pagesContainer.childElementCount === 0) { imgContainer,
const filenameInput = document.getElementById("filename-input"); this.pagesContainer
const filenameParagraph = document.getElementById("filename"); );
const downloadBtn = document.getElementById("export-button"); deletePageCommand.execute();
filenameInput.disabled = true; this._pushUndoClearRedo(deletePageCommand);
filenameInput.value = "";
filenameParagraph.innerText = "";
downloadBtn.disabled = true;
}
} }
insertFileButtonCallback(e) { insertFileButtonCallback(e) {
@@ -81,7 +92,15 @@ class PdfActionsManager {
splitFileButtonCallback(e) { splitFileButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target); var imgContainer = this.getPageContainer(e.target);
imgContainer.classList.toggle("split-before");
let splitFileCommand = new SplitFileCommand(imgContainer, "split-before");
splitFileCommand.execute();
this._pushUndoClearRedo(splitFileCommand);
}
_pushUndoClearRedo(command) {
this.undoManager.pushUndoClearRedo(command);
} }
setActions({ movePageTo, addFiles, rotateElement }) { setActions({ movePageTo, addFiles, rotateElement }) {
@@ -159,25 +178,10 @@ class PdfActionsManager {
selectCheckbox.onchange = () => { selectCheckbox.onchange = () => {
const pageNumber = Array.from(div.parentNode.children).indexOf(div) + 1; const pageNumber = Array.from(div.parentNode.children).indexOf(div) + 1;
if (selectCheckbox.checked) { let selectPageCommand = new SelectPageCommand(pageNumber, selectCheckbox);
//adds to array of selected pages selectPageCommand.execute();
window.selectedPages.push(pageNumber);
} else {
//remove page from selected pages array
const index = window.selectedPages.indexOf(pageNumber);
if (index !== -1) {
window.selectedPages.splice(index, 1);
}
}
if (window.selectedPages.length > 0 && !window.selectPage) { this._pushUndoClearRedo(selectPageCommand);
window.toggleSelectPageVisibility();
}
if (window.selectedPages.length == 0 && window.selectPage) {
window.toggleSelectPageVisibility();
}
window.updateSelectedPagesDisplay();
}; };
const insertFileButtonContainer = document.createElement("div"); const insertFileButtonContainer = document.createElement("div");

View File

@@ -1,11 +1,18 @@
import { MovePageUpCommand, MovePageDownCommand } from "./commands/move-page.js";
import { RemoveSelectedCommand } from "./commands/remove.js";
import { RotateAllCommand, RotateElementCommand } from "./commands/rotate.js";
import { SplitAllCommand } from "./commands/split.js";
import { UndoManager } from "./UndoManager.js";
class PdfContainer { class PdfContainer {
fileName; fileName;
pagesContainer; pagesContainer;
pagesContainerWrapper; pagesContainerWrapper;
pdfAdapters; pdfAdapters;
downloadLink; downloadLink;
undoManager;
constructor(id, wrapperId, pdfAdapters) { constructor(id, wrapperId, pdfAdapters, undoManager) {
this.pagesContainer = document.getElementById(id); this.pagesContainer = document.getElementById(id);
this.pagesContainerWrapper = document.getElementById(wrapperId); this.pagesContainerWrapper = document.getElementById(wrapperId);
this.downloadLink = null; this.downloadLink = null;
@@ -31,6 +38,8 @@ class PdfContainer {
this.removeAllElements = this.removeAllElements.bind(this); this.removeAllElements = this.removeAllElements.bind(this);
this.resetPages = this.resetPages.bind(this); this.resetPages = this.resetPages.bind(this);
this.undoManager = undoManager || new UndoManager();
this.pdfAdapters = pdfAdapters; this.pdfAdapters = pdfAdapters;
this.pdfAdapters.forEach((adapter) => { this.pdfAdapters.forEach((adapter) => {
@@ -58,6 +67,33 @@ class PdfContainer {
window.removeAllElements = this.removeAllElements; window.removeAllElements = this.removeAllElements;
window.resetPages = this.resetPages; window.resetPages = this.resetPages;
let undoBtn = document.getElementById('undo-btn');
let redoBtn = document.getElementById('redo-btn');
document.addEventListener('undo-manager-update', (e) => {
let canUndo = e.detail.canUndo;
let canRedo = e.detail.canRedo;
undoBtn.disabled = !canUndo;
redoBtn.disabled = !canRedo;
})
window.undo = () => {
if (undoManager.canUndo()) undoManager.undo();
else {
undoBtn.disabled = !undoManager.canUndo();
redoBtn.disabled = !undoManager.canRedo();
}
}
window.redo = () => {
if (undoManager.canRedo()) undoManager.redo();
else {
undoBtn.disabled = !undoManager.canUndo();
redoBtn.disabled = !undoManager.canRedo();
}
}
const filenameInput = document.getElementById("filename-input"); const filenameInput = document.getElementById("filename-input");
const downloadBtn = document.getElementById("export-button"); const downloadBtn = document.getElementById("export-button");
@@ -68,32 +104,28 @@ class PdfContainer {
downloadBtn.disabled = true; downloadBtn.disabled = true;
} }
movePageTo(startElement, endElement, scrollTo = false) { movePageTo(startElement, endElement, scrollTo = false, moveUp = false) {
const childArray = Array.from(this.pagesContainer.childNodes); let movePageCommand;
const startIndex = childArray.indexOf(startElement); if (moveUp) {
const endIndex = childArray.indexOf(endElement); movePageCommand = new MovePageUpCommand(
startElement,
// Check & remove page number elements here too if they exist because Firefox doesn't fire the relevant event on page move. endElement,
const pageNumberElement = startElement.querySelector(".page-number"); this.pagesContainer,
if (pageNumberElement) { this.pagesContainerWrapper,
startElement.removeChild(pageNumberElement); scrollTo
} );
this.pagesContainer.removeChild(startElement);
if (!endElement) {
this.pagesContainer.append(startElement);
} else { } else {
this.pagesContainer.insertBefore(startElement, endElement); movePageCommand = new MovePageDownCommand(
startElement,
endElement,
this.pagesContainer,
this.pagesContainerWrapper,
scrollTo
);
} }
if (scrollTo) { movePageCommand.execute();
const { width } = startElement.getBoundingClientRect(); return movePageCommand;
const vector = endIndex !== -1 && startIndex > endIndex ? 0 - width : width;
this.pagesContainerWrapper.scroll({
left: this.pagesContainerWrapper.scrollLeft + vector,
});
}
} }
addFiles(nextSiblingElement, blank = false) { addFiles(nextSiblingElement, blank = false) {
@@ -165,56 +197,22 @@ class PdfContainer {
async addFilesBlank(nextSiblingElement) { async addFilesBlank(nextSiblingElement) {
const pdfContent = ` let doc = await PDFLib.PDFDocument.create();
%PDF-1.4 let docBytes = await doc.save();
1 0 obj
<< /Type /Catalog /Pages 2 0 R >> const url = URL.createObjectURL(new Blob([docBytes], { type: 'application/pdf' }));
endobj
2 0 obj const renderer = await this.toRenderer(url);
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj await this.addPdfFile(renderer, doc, nextSiblingElement);
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 5 0 R >>
endobj
5 0 obj
<< /Length 44 >>
stream
0 0 0 595 0 842 re
W
n
endstream
endobj
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000071 00000 n
0000000121 00000 n
0000000205 00000 n
0000000400 00000 n
trailer
<< /Size 6 /Root 1 0 R >>
startxref
278
%%EOF
`;
const blob = new Blob([pdfContent], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const file = new File([blob], "blank_page.pdf", { type: "application/pdf" });
await this.addPdfFile(file, nextSiblingElement);
} }
rotateElement(element, deg) { rotateElement(element, deg) {
var lastTransform = element.style.rotate; let rotateCommand = new RotateElementCommand(element, deg);
if (!lastTransform) { rotateCommand.execute();
lastTransform = "0";
}
const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
const newAngle = lastAngle + deg;
element.style.rotate = newAngle + "deg";
return rotateCommand;
} }
async addPdfFile(renderer, pdfDocument, nextSiblingElement) { async addPdfFile(renderer, pdfDocument, nextSiblingElement) {
@@ -309,6 +307,7 @@ class PdfContainer {
} }
rotateAll(deg) { rotateAll(deg) {
let elementsToRotate = [];
for (let i = 0; i < this.pagesContainer.childNodes.length; i++) { for (let i = 0; i < this.pagesContainer.childNodes.length; i++) {
const child = this.pagesContainer.children[i]; const child = this.pagesContainer.children[i];
if (!child) continue; if (!child) continue;
@@ -320,8 +319,13 @@ class PdfContainer {
const img = child.querySelector("img"); const img = child.querySelector("img");
if (!img) continue; if (!img) continue;
this.rotateElement(img, deg); elementsToRotate.push(img);
} }
let rotateAllCommand = new RotateAllCommand(elementsToRotate, deg);
rotateAllCommand.execute();
this.undoManager.pushUndoClearRedo(rotateAllCommand);
} }
removeAllElements(){ removeAllElements(){
@@ -336,34 +340,13 @@ class PdfContainer {
deleteSelected() { deleteSelected() {
window.selectedPages.sort((a, b) => a - b); window.selectedPages.sort((a, b) => a - b);
let deletions = 0; let removeSelectedCommand = new RemoveSelectedCommand(
this.pagesContainer,
window.selectedPages,
this.updatePageNumbersAndCheckboxes
);
window.selectedPages.forEach((pageIndex) => { this.undoManager.pushUndoClearRedo(removeSelectedCommand);
const adjustedIndex = pageIndex - 1 - deletions;
const child = this.pagesContainer.children[adjustedIndex];
if (child) {
this.pagesContainer.removeChild(child);
deletions++;
}
});
if (this.pagesContainer.childElementCount === 0) {
const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
const downloadBtn = document.getElementById("export-button");
if (filenameInput)
filenameInput.disabled = true;
filenameInput.value = "";
if (filenameParagraph)
filenameParagraph.innerText = "";
downloadBtn.disabled = true;
}
window.selectedPages = [];
this.updatePageNumbersAndCheckboxes();
document.dispatchEvent(new Event("selectedPagesUpdated"));
} }
toggleSelectAll() { toggleSelectAll() {
@@ -531,33 +514,16 @@ class PdfContainer {
splitAll() { splitAll() {
const allPages = this.pagesContainer.querySelectorAll(".page-container"); const allPages = this.pagesContainer.querySelectorAll(".page-container");
let splitAllCommand = new SplitAllCommand(
allPages,
window.selectPage,
window.selectedPages,
"split-before"
);
splitAllCommand.execute();
if (!window.selectPage) { this.undoManager.pushUndoClearRedo(splitAllCommand);
const hasSplit = this.pagesContainer.querySelectorAll(".split-before").length > 0;
if (hasSplit) {
allPages.forEach(page => {
page.classList.remove("split-before");
});
} else {
allPages.forEach(page => {
page.classList.add("split-before");
});
} }
return;
}
allPages.forEach((page, index) => {
const pageIndex = index;
if (window.selectPage && !window.selectedPages.includes(pageIndex)) return;
if (page.classList.contains("split-before")) {
page.classList.remove("split-before");
} else {
page.classList.add("split-before");
}
});
}
async splitPDF(baseDocBytes, splitters) { async splitPDF(baseDocBytes, splitters) {
const baseDocument = await PDFLib.PDFDocument.load(baseDocBytes); const baseDocument = await PDFLib.PDFDocument.load(baseDocBytes);

View File

@@ -0,0 +1,65 @@
export class UndoManager {
_undoStack;
_redoStack;
constructor() {
this._undoStack = [];
this._redoStack = [];
}
pushUndo(command) {
this._undoStack.push(command);
this._dispatchStateChange();
}
pushRedo(command) {
this._redoStack.push(command);
this._dispatchStateChange();
}
pushUndoClearRedo(command) {
this._undoStack.push(command);
this._redoStack = [];
this._dispatchStateChange();
}
undo() {
if (!this.canUndo()) return;
let cmd = this._undoStack.pop();
cmd.undo();
this._redoStack.push(cmd);
this._dispatchStateChange();
}
canUndo() {
return this._undoStack && this._undoStack.length > 0;
}
redo() {
if (!this.canRedo()) return;
let cmd = this._redoStack.pop();
cmd.redo();
this._undoStack.push(cmd);
this._dispatchStateChange();
}
canRedo() {
return this._redoStack && this._redoStack.length > 0;
}
_dispatchStateChange() {
document.dispatchEvent(
new CustomEvent("undo-manager-update", {
bubbles: true,
detail: {
canUndo: this.canUndo(),
canRedo: this.canRedo(),
},
})
);
}
}

View File

@@ -0,0 +1,5 @@
export class Command {
execute() {}
undo() {}
redo() {}
}

View File

@@ -0,0 +1,74 @@
import { Command } from "./command.js";
export class DeletePageCommand extends Command {
constructor(element, pagesContainer) {
super();
this.element = element;
this.pagesContainer = pagesContainer;
this.filenameInputValue = document.getElementById("filename-input").value;
const filenameParagraph = document.getElementById("filename");
this.filenameParagraphText = filenameParagraph ? filenameParagraph.innerText : "";
}
execute() {
this.nextSibling = this.element.nextSibling;
this.pagesContainer.removeChild(this.element);
if (this.pagesContainer.childElementCount === 0) {
const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
const downloadBtn = document.getElementById("export-button");
filenameInput.disabled = true;
filenameInput.value = "";
filenameParagraph.innerText = "";
downloadBtn.disabled = true;
}
}
undo() {
let node = this.nextSibling;
if (node) this.pagesContainer.insertBefore(this.element, node);
else this.pagesContainer.appendChild(this.element);
const pageNumberElement = this.element.querySelector(".page-number");
if (pageNumberElement) {
this.element.removeChild(pageNumberElement);
}
const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
const downloadBtn = document.getElementById("export-button");
filenameInput.disabled = false;
filenameInput.value = this.filenameInputValue;
if (this.filenameParagraph)
filenameParagraph.innerText = this.filenameParagraphText;
downloadBtn.disabled = false;
}
redo() {
const pageNumberElement = this.element.querySelector(".page-number");
if (pageNumberElement) {
this.element.removeChild(pageNumberElement);
}
this.pagesContainer.removeChild(this.element);
if (this.pagesContainer.childElementCount === 0) {
const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
const downloadBtn = document.getElementById("export-button");
filenameInput.disabled = true;
filenameInput.value = "";
filenameParagraph.innerText = "";
downloadBtn.disabled = true;
}
}
}

View File

@@ -0,0 +1,133 @@
import { Command } from "./command.js";
export class AbstractMovePageCommand extends Command {
constructor(
startElement,
endElement,
pagesContainer,
pagesContainerWrapper,
scrollTo = false
) {
super();
this.pagesContainer = pagesContainer;
const childArray = Array.from(this.pagesContainer.childNodes);
this.startIndex = childArray.indexOf(startElement);
this.endIndex = childArray.indexOf(endElement);
this.startElement = startElement;
this.endElement = endElement;
this.scrollTo = scrollTo;
this.pagesContainerWrapper = pagesContainerWrapper;
}
execute() {
// Check & remove page number elements here too if they exist because Firefox doesn't fire the relevant event on page move.
const pageNumberElement = this.startElement.querySelector(".page-number");
if (pageNumberElement) {
this.startElement.removeChild(pageNumberElement);
}
this.pagesContainer.removeChild(this.startElement);
if (!this.endElement) {
this.pagesContainer.append(this.startElement);
} else {
this.pagesContainer.insertBefore(this.startElement, this.endElement);
}
if (this.scrollTo) {
const { width } = this.startElement.getBoundingClientRect();
const vector =
this.endIndex !== -1 && this.startIndex > this.endIndex
? 0 - width
: width;
this.pagesContainerWrapper.scroll({
left: this.pagesContainerWrapper.scrollLeft + vector,
});
}
}
undo() {
// Requires overriding in child classes
}
redo() {
this.execute();
}
}
export class MovePageUpCommand extends AbstractMovePageCommand {
constructor(
startElement,
endElement,
pagesContainer,
pagesContainerWrapper,
scrollTo = false
) {
super(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo);
}
undo() {
if (this.endElement) {
this.pagesContainer.removeChild(this.endElement);
this.startElement.insertAdjacentElement("beforebegin", this.endElement);
}
if (this.scrollTo) {
const { width } = this.startElement.getBoundingClientRect();
const vector =
this.endIndex === -1 || this.startIndex <= this.endIndex
? 0 - width
: width;
this.pagesContainerWrapper.scroll({
left: this.pagesContainerWrapper.scrollLeft - vector,
});
}
}
redo() {
this.execute();
}
}
export class MovePageDownCommand extends AbstractMovePageCommand {
constructor(
startElement,
endElement,
pagesContainer,
pagesContainerWrapper,
scrollTo = false
) {
super(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo);
}
undo() {
let previousElement = this.startElement.previousSibling;
if (this.startElement) {
this.pagesContainer.removeChild(this.startElement);
previousElement.insertAdjacentElement("beforebegin", this.startElement);
}
if (this.scrollTo) {
const { width } = this.startElement.getBoundingClientRect();
const vector =
this.endIndex === -1 || this.startIndex <= this.endIndex
? 0 - width
: width;
this.pagesContainerWrapper.scroll({
left: this.pagesContainerWrapper.scrollLeft - vector,
});
}
}
redo() {
this.execute();
}
}

View File

@@ -0,0 +1,101 @@
import { Command } from "./command.js";
export class RemoveSelectedCommand extends Command {
constructor(pagesContainer, selectedPages, updatePageNumbersAndCheckboxes) {
super();
this.pagesContainer = pagesContainer;
this.selectedPages = selectedPages;
this.deletedChildren = [];
if (updatePageNumbersAndCheckboxes) {
this.updatePageNumbersAndCheckboxes = updatePageNumbersAndCheckboxes;
} else {
const pageDivs = document.querySelectorAll(".pdf-actions_container");
pageDivs.forEach((div, index) => {
const pageNumber = index + 1;
const checkbox = div.querySelector(".pdf-actions_checkbox");
checkbox.id = `selectPageCheckbox-${pageNumber}`;
checkbox.setAttribute("data-page-number", pageNumber);
checkbox.checked = window.selectedPages.includes(pageNumber);
});
}
const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
this.originalFilenameInputValue = filenameInput ? filenameInput.value : "";
if (filenameParagraph)
this.originalFilenameParagraphText = filenameParagraph.innerText;
}
execute() {
let deletions = 0;
this.selectedPages.forEach((pageIndex) => {
const adjustedIndex = pageIndex - 1 - deletions;
const child = this.pagesContainer.children[adjustedIndex];
if (child) {
this.pagesContainer.removeChild(child);
deletions++;
this.deletedChildren.push({
idx: adjustedIndex,
childNode: child,
});
}
});
if (this.pagesContainer.childElementCount === 0) {
const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
const downloadBtn = document.getElementById("export-button");
if (filenameInput) filenameInput.disabled = true;
filenameInput.value = "";
if (filenameParagraph) filenameParagraph.innerText = "";
downloadBtn.disabled = true;
}
window.selectedPages = [];
this.updatePageNumbersAndCheckboxes();
document.dispatchEvent(new Event("selectedPagesUpdated"));
}
undo() {
while (this.deletedChildren.length > 0) {
let deletedChild = this.deletedChildren.pop();
if (this.pagesContainer.children.length <= deletedChild.idx)
this.pagesContainer.appendChild(deletedChild.childNode);
else {
this.pagesContainer.insertBefore(
deletedChild.childNode,
this.pagesContainer.children[deletedChild.idx]
);
}
}
if (this.pagesContainer.childElementCount > 0) {
const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
const downloadBtn = document.getElementById("export-button");
if (filenameInput) filenameInput.disabled = false;
filenameInput.value = this.originalFilenameInputValue;
if (filenameParagraph)
filenameParagraph.innerText = this.originalFilenameParagraphText;
downloadBtn.disabled = false;
}
window.selectedPages = this.selectedPages;
this.updatePageNumbersAndCheckboxes();
document.dispatchEvent(new Event("selectedPagesUpdated"));
}
redo() {
this.execute();
}
}

View File

@@ -0,0 +1,74 @@
import { Command } from "./command.js";
export class RotateElementCommand extends Command {
constructor(element, degree) {
super();
this.element = element;
this.degree = degree;
}
execute() {
let lastTransform = this.element.style.rotate;
if (!lastTransform) {
lastTransform = "0";
}
const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
const newAngle = lastAngle + parseInt(this.degree);
this.element.style.rotate = newAngle + "deg";
}
undo() {
let lastTransform = this.element.style.rotate;
if (!lastTransform) {
lastTransform = "0";
}
const currentAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
const undoAngle = currentAngle + -parseInt(this.degree);
this.element.style.rotate = undoAngle + "deg";
}
redo() {
this.execute();
}
}
export class RotateAllCommand extends Command {
constructor(elements, degree) {
super();
this.elements = elements;
this.degree = degree;
}
execute() {
for (let element of this.elements) {
let lastTransform = element.style.rotate;
if (!lastTransform) {
lastTransform = "0";
}
const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
const newAngle = lastAngle + this.degree;
element.style.rotate = newAngle + "deg";
}
}
undo() {
for (let element of this.elements) {
let lastTransform = element.style.rotate;
if (!lastTransform) {
lastTransform = "0";
}
const currentAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
const undoAngle = currentAngle + -this.degree;
element.style.rotate = undoAngle + "deg";
}
}
redo() {
this.execute();
}
}

View File

@@ -0,0 +1,59 @@
import { Command } from "./command.js";
export class SelectPageCommand extends Command {
constructor(pageNumber, checkbox) {
super();
this.pageNumber = pageNumber;
this.selectCheckbox = checkbox;
}
execute() {
if (this.selectCheckbox.checked) {
//adds to array of selected pages
window.selectedPages.push(this.pageNumber);
} else {
//remove page from selected pages array
const index = window.selectedPages.indexOf(this.pageNumber);
if (index !== -1) {
window.selectedPages.splice(index, 1);
}
}
if (window.selectedPages.length > 0 && !window.selectPage) {
window.toggleSelectPageVisibility();
}
if (window.selectedPages.length == 0 && window.selectPage) {
window.toggleSelectPageVisibility();
}
window.updateSelectedPagesDisplay();
}
undo() {
this.selectCheckbox.checked = !this.selectCheckbox.checked;
if (this.selectCheckbox.checked) {
//adds to array of selected pages
window.selectedPages.push(this.pageNumber);
} else {
//remove page from selected pages array
const index = window.selectedPages.indexOf(this.pageNumber);
if (index !== -1) {
window.selectedPages.splice(index, 1);
}
}
if (window.selectedPages.length > 0 && !window.selectPage) {
window.toggleSelectPageVisibility();
}
if (window.selectedPages.length == 0 && window.selectPage) {
window.toggleSelectPageVisibility();
}
window.updateSelectedPagesDisplay();
}
redo() {
this.selectCheckbox.checked = !this.selectCheckbox.checked;
this.execute();
}
}

View File

@@ -0,0 +1,101 @@
import { Command } from "./command.js";
export class SplitFileCommand extends Command {
constructor(element, splitClass) {
super();
this.element = element;
this.splitClass = splitClass;
}
execute() {
this.element.classList.toggle(this.splitClass);
}
undo() {
this.element.classList.toggle(this.splitClass);
}
redo() {
this.execute();
}
}
export class SplitAllCommand extends Command {
constructor(elements, isSelectedInWindow, selectedPages, splitClass) {
super();
this.elements = elements;
this.isSelectedInWindow = isSelectedInWindow;
this.selectedPages = selectedPages;
this.splitClass = splitClass;
}
execute() {
if (!this.isSelectedInWindow) {
const hasSplit = this._hasSplit(this.elements, this.splitClass);
if (hasSplit) {
this.elements.forEach((page) => {
page.classList.remove(this.splitClass);
});
} else {
this.elements.forEach((page) => {
page.classList.add(this.splitClass);
});
}
return;
}
this.elements.forEach((page, index) => {
const pageIndex = index;
if (this.isSelectedInWindow && !this.selectedPages.includes(pageIndex))
return;
if (page.classList.contains(this.splitClass)) {
page.classList.remove(this.splitClass);
} else {
page.classList.add(this.splitClass);
}
});
}
_hasSplit() {
if (!this.elements || this.elements.length == 0) return false;
for (const node of this.elements) {
if (node.classList.contains(this.splitClass)) return true;
}
return false;
}
undo() {
if (!this.isSelectedInWindow) {
const hasSplit = this._hasSplit(this.elements, this.splitClass);
if (hasSplit) {
this.elements.forEach((page) => {
page.classList.remove(this.splitClass);
});
} else {
this.elements.forEach((page) => {
page.classList.add(this.splitClass);
});
}
return;
}
this.elements.forEach((page, index) => {
const pageIndex = index;
if (this.isSelectedInWindow && !this.selectedPages.includes(pageIndex))
return;
if (page.classList.contains(this.splitClass)) {
page.classList.remove(this.splitClass);
} else {
page.classList.add(this.splitClass);
}
});
}
redo() {
this.execute();
}
}

View File

@@ -189,7 +189,7 @@
<label for="authType">Authentication Type</label> <label for="authType">Authentication Type</label>
<select id="authType" name="authType" class="form-control" required> <select id="authType" name="authType" class="form-control" required>
<option value="web" selected>WEB</option> <option value="web" selected>WEB</option>
<option value="oauth2">OAUTH2</option> <option value="sso">SSO</option>
</select> </select>
</div> </div>
<div class="form-check mb-3" id="checkboxContainer"> <div class="form-check mb-3" id="checkboxContainer">
@@ -267,7 +267,7 @@
var passwordFieldContainer = $('#passwordContainer'); var passwordFieldContainer = $('#passwordContainer');
var checkboxContainer = $('#checkboxContainer'); var checkboxContainer = $('#checkboxContainer');
if (authType === 'oauth2') { if (authType === 'sso') {
passwordField.removeAttr('required'); passwordField.removeAttr('required');
passwordField.prop('disabled', true).val(''); passwordField.prop('disabled', true).val('');
passwordFieldContainer.slideUp('fast'); passwordFieldContainer.slideUp('fast');

View File

@@ -59,7 +59,7 @@
<link rel="stylesheet" th:href="@{'/css/fileSelect.css'}" th:if="${currentPage != 'home'}"> <link rel="stylesheet" th:href="@{'/css/fileSelect.css'}" th:if="${currentPage != 'home'}">
<link rel="stylesheet" th:href="@{'/css/footer.css'}"> <link rel="stylesheet" th:href="@{'/css/footer.css'}">
<link rel="preload" href="/fonts/google-symbol.woff2" as="font" type="font/woff2" crossorigin="anonymous"> <link rel="preload" th:href="@{'/fonts/google-symbol.woff2'}" as="font" type="font/woff2" crossorigin="anonymous">
<script th:src="@{'/js/thirdParty/fontfaceobserver.standalone.js'}"></script> <script th:src="@{'/js/thirdParty/fontfaceobserver.standalone.js'}"></script>

View File

@@ -4,6 +4,7 @@
<div class="d-flex justify-content-center"> <div class="d-flex justify-content-center">
<ul class="list-unstyled d-flex"> <ul class="list-unstyled d-flex">
<li><a class="footer-link px-2" id="licenses" target="_blank" th:href="@{'/licenses'}" th:text="#{licenses.nav}">Licenses</a></li> <li><a class="footer-link px-2" id="licenses" target="_blank" th:href="@{'/licenses'}" th:text="#{licenses.nav}">Licenses</a></li>
<li><a class="footer-link px-2" id="licenses" target="_blank" th:href="@{'/releases'}" th:text="#{releases.footer}">Releases</a></li>
<li><a class="footer-link px-2" id="survey" target="_blank" href="https://stirlingpdf.info/s/cm28y3niq000o56dv7liv8wsu" th:text="#{survey.nav}">Survey</a></li> <li><a class="footer-link px-2" id="survey" target="_blank" href="https://stirlingpdf.info/s/cm28y3niq000o56dv7liv8wsu" th:text="#{survey.nav}">Survey</a></li>
<li th:if="${@privacyPolicy != ''}"><a class="footer-link px-2" target="_blank" th:href="${@privacyPolicy}" th:text="#{legal.privacy}">privacyPolicy</a></li> <li th:if="${@privacyPolicy != ''}"><a class="footer-link px-2" target="_blank" th:href="${@privacyPolicy}" th:text="#{legal.privacy}">privacyPolicy</a></li>
<li th:if="${@termsAndConditions != ''}"><a class="footer-link px-2" target="_blank" th:href="${@termsAndConditions}" th:text="#{legal.terms}">termsAndConditions</a></li> <li th:if="${@termsAndConditions != ''}"><a class="footer-link px-2" target="_blank" th:href="${@termsAndConditions}" th:text="#{legal.terms}">termsAndConditions</a></li>

View File

@@ -156,7 +156,7 @@
resultDiv2.innerHTML = loading; resultDiv2.innerHTML = loading;
// Create a new Worker // Create a new Worker
const worker = new Worker('/js/compare/pdfWorker.js'); const worker = new Worker('./js/compare/pdfWorker.js');
// Post messages to the worker // Post messages to the worker

View File

@@ -29,9 +29,14 @@
<label for="optimizeLevel" th:text="#{compress.selectText.2}"></label> <label for="optimizeLevel" th:text="#{compress.selectText.2}"></label>
<select name="optimizeLevel" id="optimizeLevel" class="form-control"> <select name="optimizeLevel" id="optimizeLevel" class="form-control">
<option value="1">1</option> <option value="1">1</option>
<option value="2" selected>2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
<option value="4" th:text="#{compress.selectText.3}"></option> <option value="4">4</option>
<option value="5" selected>5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
</select> </select>
</div> </div>
</div> </div>

View File

@@ -62,26 +62,6 @@
</div> </div>
<br> <br>
<label for="languages" class="form-label" th:text="#{ocr.selectText.9}"></label> <label for="languages" class="form-label" th:text="#{ocr.selectText.9}"></label>
<div class="form-check ms-3">
<input type="checkbox" name="sidecar" id="sidecar" />
<label for="sidecar" th:text="#{ocr.selectText.2}"></label>
</div>
<div class="form-check ms-3">
<input type="checkbox" name="deskew" id="deskew" />
<label for="deskew" th:text="#{ocr.selectText.3}"></label>
</div>
<div class="form-check ms-3">
<input type="checkbox" name="clean" id="clean" />
<label for="clean" th:text="#{ocr.selectText.4}"></label>
</div>
<div class="form-check ms-3">
<input type="checkbox" name="clean-final" id="clean-final" />
<label for="clean-final" th:text="#{ocr.selectText.5}"></label>
</div>
<div class="form-check ms-3">
<input type="checkbox" name="removeImagesAfter" id="removeImagesAfter" />
<label for="removeImagesAfter" th:text="#{ocr.selectText.11}"></label>
</div>
<div class="mb-3"> <div class="mb-3">
<label th:text="#{ocr.selectText.12}"></label> <label th:text="#{ocr.selectText.12}"></label>
<select class="form-control" name="ocrRenderType"> <select class="form-control" name="ocrRenderType">
@@ -239,7 +219,6 @@
}); });
}); });
</script> </script>
<p th:text="#{ocr.credit}"></p>
<p th:text="#{ocr.help}"></p> <p th:text="#{ocr.help}"></p>
<a href="https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md">https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md</a> <a href="https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md">https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md</a>
</div> </div>

View File

@@ -57,6 +57,27 @@
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.insertPageBreak}"></span> <span class="btn-tooltip" th:text="#{multiTool.insertPageBreak}"></span>
</button> </button>
<button id="undo-btn" class="btn btn-secondary" onclick="undo()" disabled>
<span class="material-symbols-rounded">
undo
</span>
<span class="btn-tooltip">
<div th:text="#{multiTool.undo}"></div>
<div class="text-uppercase" th:text="'(CTRL + Z)'"></div>
</span>
</button>
<button id="redo-btn" class="btn btn-secondary" onclick="redo()" disabled>
<span class="material-symbols-rounded">
redo
</span>
<span class="btn-tooltip">
<div th:text="#{multiTool.redo}"></div>
<div class="text-uppercase" th:text="'(CTRL + Y)'"></div>
</span>
</button>
</button>
<button id="select-pages-container" class="btn btn-secondary enable-on-file" <button id="select-pages-container" class="btn btn-secondary enable-on-file"
onclick="toggleSelectPageVisibility()" disabled> onclick="toggleSelectPageVisibility()" disabled>
<span id="select-pages-button" class="material-symbols-rounded"> <span id="select-pages-button" class="material-symbols-rounded">
@@ -139,7 +160,9 @@
split: '[[#{multiTool.split}]]', split: '[[#{multiTool.split}]]',
addFile: '[[#{multiTool.addFile}]]', addFile: '[[#{multiTool.addFile}]]',
insertPageBreak:'[[#{multiTool.insertPageBreak}]]', insertPageBreak:'[[#{multiTool.insertPageBreak}]]',
dragDropMessage:'[[#{multiTool.dragDropMessage}]]' dragDropMessage:'[[#{multiTool.dragDropMessage}]]',
undo: '[[#{multiTool.undo}]]',
redo: '[[#{multiTool.redo}]]'
}; };
@@ -155,17 +178,20 @@
}); });
</script> </script>
<script type="module"> <script type="module">
import { UndoManager } from './js/multitool/UndoManager.js';
import PdfContainer from './js/multitool/PdfContainer.js'; import PdfContainer from './js/multitool/PdfContainer.js';
import DragDropManager from "./js/multitool/DragDropManager.js"; import DragDropManager from "./js/multitool/DragDropManager.js";
import ImageHighlighter from "./js/multitool/ImageHighlighter.js"; import ImageHighlighter from "./js/multitool/ImageHighlighter.js";
import PdfActionsManager from './js/multitool/PdfActionsManager.js'; import PdfActionsManager from './js/multitool/PdfActionsManager.js';
import FileDragManager from './js/multitool/fileInput.js'; import FileDragManager from './js/multitool/fileInput.js';
// enables drag and drop // enables drag and drop
var undoManager = new UndoManager();
const dragDropManager = new DragDropManager('drag-container', 'pages-container'); const dragDropManager = new DragDropManager('drag-container', 'pages-container');
// enables image highlight on click // enables image highlight on click
const imageHighlighter = new ImageHighlighter('image-highlighter'); const imageHighlighter = new ImageHighlighter('image-highlighter');
// enables the default action buttons on each file // enables the default action buttons on each file
const pdfActionsManager = new PdfActionsManager('pages-container'); const pdfActionsManager = new PdfActionsManager('pages-container', undoManager);
const fileDragManager = new FileDragManager(); const fileDragManager = new FileDragManager();
// Scroll the wrapper horizontally // Scroll the wrapper horizontally
@@ -178,10 +204,23 @@
imageHighlighter, imageHighlighter,
pdfActionsManager, pdfActionsManager,
fileDragManager fileDragManager
] ],
undoManager
) )
fileDragManager.setCallback(async (files) => pdfContainer.addFilesFromFiles(files)); fileDragManager.setCallback(async (files) => pdfContainer.addFilesFromFiles(files));
document.addEventListener('keydown', function(event) {
let targetElementId = event.target.id;
// To avoid undoing/redoing the page when the user is simply undoing/redoing text
const isFilenameInputField = (targetElementId === 'filename-input') && (event.target === document.activeElement);
const isUndo = (event.ctrlKey && event.key === 'z');
const isRedo = (event.ctrlKey && event.key == 'y');
if (isUndo && !isFilenameInputField)
undoManager.undo();
else if (isRedo && !isFilenameInputField) undoManager.redo();
});
</script> </script>
</body> </body>

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