Compare commits
29 Commits
pixeebot/g
...
pixeebot/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72636dda9f | ||
|
|
329f755823 | ||
|
|
acc50e48c5 | ||
|
|
92a571d31c | ||
|
|
99d481d69f | ||
|
|
5976e69f54 | ||
|
|
e588d8f99e | ||
|
|
1c0f423510 | ||
|
|
2d6fe55985 | ||
|
|
5171088fca | ||
|
|
b4837df76c | ||
|
|
d20e8f7d54 | ||
|
|
2885fac30d | ||
|
|
a5ba6c403a | ||
|
|
2a4a19a80f | ||
|
|
b2e6d89d16 | ||
|
|
b59d2d15b4 | ||
|
|
61e750646c | ||
|
|
de9c21b3de | ||
|
|
b32d6cb858 | ||
|
|
d832a90de0 | ||
|
|
212e521238 | ||
|
|
0915e72a3d | ||
|
|
ee5013651f | ||
|
|
4aa44e6fc0 | ||
|
|
41c743a9f8 | ||
|
|
833b3c45c6 | ||
|
|
654bc94d44 | ||
|
|
86fa404c90 |
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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 \
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 | | | | ✔️ | | | | | | | ✔️ | | |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
56
README.md
56
README.md
@@ -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) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
|
|
||||||
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
|
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
|
||||||
|
|
||||||
|
|||||||
@@ -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 |
|
||||||
| ---------------------- | ---------- | ---- |
|
| ---------------------- | ---------- | ---- |
|
||||||
|
|||||||
20
build.gradle
20
build.gradle
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public class InitialSecuritySetup {
|
|||||||
initializeAdminUser();
|
initializeAdminUser();
|
||||||
} else {
|
} else {
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseBackupHelper.exportDatabase();
|
||||||
|
userService.migrateOauth2ToSSO();
|
||||||
}
|
}
|
||||||
initializeInternalApiUser();
|
initializeInternalApiUser();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ package stirling.software.SPDF.model;
|
|||||||
|
|
||||||
public enum AuthenticationType {
|
public enum AuthenticationType {
|
||||||
WEB,
|
WEB,
|
||||||
OAUTH2
|
SSO
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"))) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 və 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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
65
src/main/resources/static/js/multitool/UndoManager.js
Normal file
65
src/main/resources/static/js/multitool/UndoManager.js
Normal 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(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export class Command {
|
||||||
|
execute() {}
|
||||||
|
undo() {}
|
||||||
|
redo() {}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
src/main/resources/static/js/multitool/commands/move-page.js
Normal file
133
src/main/resources/static/js/multitool/commands/move-page.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/main/resources/static/js/multitool/commands/remove.js
Normal file
101
src/main/resources/static/js/multitool/commands/remove.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/main/resources/static/js/multitool/commands/rotate.js
Normal file
74
src/main/resources/static/js/multitool/commands/rotate.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/main/resources/static/js/multitool/commands/select.js
Normal file
59
src/main/resources/static/js/multitool/commands/select.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/main/resources/static/js/multitool/commands/split.js
Normal file
101
src/main/resources/static/js/multitool/commands/split.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user