diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml index 7b62867c..50d03bcc 100644 --- a/.github/workflows/push-docker.yml +++ b/.github/workflows/push-docker.yml @@ -21,6 +21,8 @@ jobs: - uses: gradle/gradle-build-action@v2.4.2 + env: + DOCKER_ENABLE_SECURITY: false with: gradle-version: 7.6 arguments: clean build @@ -77,6 +79,8 @@ jobs: cache-to: type=gha,mode=max tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + build-args: + VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} platforms: linux/amd64,linux/arm64/v8 @@ -105,6 +109,8 @@ jobs: cache-to: type=gha,mode=max tags: ${{ steps.meta2.outputs.tags }} labels: ${{ steps.meta2.outputs.labels }} + build-args: + VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} platforms: linux/amd64,linux/arm64/v8 diff --git a/.github/workflows/releaseArtifacts.yml b/.github/workflows/releaseArtifacts.yml index 5369317b..f57aacd4 100644 --- a/.github/workflows/releaseArtifacts.yml +++ b/.github/workflows/releaseArtifacts.yml @@ -1,10 +1,20 @@ name: Release Artifacts + on: release: types: [created] + jobs: push: runs-on: ubuntu-latest + strategy: + matrix: + enable_security: [true, false] + include: + - enable_security: true + file_suffix: '-with-login' + - enable_security: false + file_suffix: '' steps: - uses: actions/checkout@v3.5.2 @@ -17,15 +27,17 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Generate jar + - name: Generate jar (With Security=${{ matrix.enable_security }}) run: ./gradlew clean createExe + env: + DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }} - name: Upload binaries to release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ./build/launch4j/Stirling-PDF.exe - asset_name: Stirling-PDF.exe + asset_name: Stirling-PDF${{ matrix.file_suffix }}.exe tag: ${{ github.ref }} overwrite: true @@ -33,13 +45,11 @@ jobs: id: versionNumber run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)" - - name: Upload binaries to release + - name: Upload jar binaries to release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar - asset_name: Stirling-PDF.jar + asset_name: Stirling-PDF${{ matrix.file_suffix }}.jar tag: ${{ github.ref }} overwrite: true - - diff --git a/.gitignore b/.gitignore index 661ca292..ff9a3bc5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ pipeline/ #### Stirling-PDF Files ### customFiles/ -config/ +configs/ watchedFolders/ @@ -116,7 +116,7 @@ watchedFolders/ *.zip *.tar.gz *.rar - +*.db /build /.vscode \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c98a21c8..e4bd868c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,45 @@ -# Build jbig2enc in a separate stage +# Use the base image FROM frooodle/stirling-pdf-base:beta4 -# Create scripts folder and copy local scripts -RUN mkdir /scripts -COPY ./scripts/* /scripts/ +ARG VERSION_TAG -#Install fonts -RUN mkdir /usr/share/fonts/opentype/noto/ +# Set Environment Variables +ENV DOCKER_ENABLE_SECURITY=false \ + HOME=/home/stirlingpdfuser \ + VERSION_TAG=$VERSION_TAG +# PUID=1000 \ +# PGID=1000 \ +# UMASK=022 \ + + +# Create user and group +##RUN groupadd -g $PGID stirlingpdfgroup && \ +## useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \ +## mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME + +# Set up necessary directories and permissions +RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /customFiles +##&& \ +## chown -R stirlingpdfuser:stirlingpdfgroup /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /customFiles && \ +## chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tesseract-ocr-original + +# Copy necessary files +COPY ./scripts/* /scripts/ COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/ -RUN fc-cache -f -v - -# Copy the application JAR file COPY build/libs/*.jar app.jar -# Expose the application port +# Set font cache and permissions +RUN fc-cache -f -v && chmod +x /scripts/init.sh + +##&& \ +## chown stirlingpdfuser:stirlingpdfgroup /app.jar && \ +## chmod +x /scripts/init.sh + +# Expose necessary ports EXPOSE 8080 -# Set environment variables -ENV APP_HOME_NAME="Stirling PDF" -#ENV APP_HOME_DESCRIPTION="Personal PDF Website!" -#ENV APP_NAVBAR_NAME="Stirling PDF" - -# Run the application -RUN chmod +x /scripts/init.sh +# Set user and run command +##USER stirlingpdfuser ENTRYPOINT ["/scripts/init.sh"] CMD ["java", "-jar", "/app.jar"] diff --git a/Dockerfile-lite b/Dockerfile-lite index d3968a2a..f41a8a8f 100644 --- a/Dockerfile-lite +++ b/Dockerfile-lite @@ -10,14 +10,45 @@ RUN apt-get update && \ unoconv && \ rm -rf /var/lib/apt/lists/* -# Copy the application JAR file + +# Set Environment Variables +ENV DOCKER_ENABLE_SECURITY=false \ + HOME=/home/stirlingpdfuser \ + VERSION_TAG=$VERSION_TAG +# PUID=1000 \ +# PGID=1000 \ +# UMASK=022 \ + +# Create user and group +#RUN groupadd -g $PGID stirlingpdfgroup && \ +# useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \ +# mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME + +# Set up necessary directories and permissions +RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /configs /customFiles + +# chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/fonts/opentype/noto /configs /customFiles + +# Copy necessary files +COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ +COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/ COPY build/libs/*.jar app.jar +# Set font cache and permissions +RUN fc-cache -f -v +# chown stirlingpdfuser:stirlingpdfgroup /app.jar + + + + # Expose the application port EXPOSE 8080 # Set environment variables -ENV GROUPS_TO_REMOVE=Python,OpenCV,OCRmyPDF +ENV ENDPOINTS_GROUPS_TO_REMOVE=Python,OpenCV,OCRmyPDF +ENV DOCKER_ENABLE_SECURITY=false # Run the application +#USER stirlingpdfuser + CMD ["java", "-jar", "/app.jar"] diff --git a/Dockerfile-ultra-lite b/Dockerfile-ultra-lite index ac8579af..84798820 100644 --- a/Dockerfile-ultra-lite +++ b/Dockerfile-ultra-lite @@ -1,14 +1,34 @@ # Build jbig2enc in a separate stage FROM bellsoft/liberica-openjdk-alpine:17 -# Copy the application JAR file +# Set Environment Variables +ENV PUID=1000 \ + PGID=1000 \ + UMASK=022 \ + DOCKER_ENABLE_SECURITY=false \ + HOME=/home/stirlingpdfuser \ + VERSION_TAG=$VERSION_TAG + +# Create user and group using Alpine's addgroup and adduser +RUN addgroup -g $PGID stirlingpdfgroup && \ + adduser -u $PUID -G stirlingpdfgroup -s /bin/sh -D stirlingpdfuser && \ + mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME + +# Set up necessary directories and permissions +RUN mkdir -p /scripts /configs /customFiles && \ + chown -R stirlingpdfuser:stirlingpdfgroup /scripts /configs /customFiles + COPY build/libs/*.jar app.jar +# Set font cache and permissions +RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar + # Expose the application port EXPOSE 8080 # Set environment variables -ENV GROUPS_TO_REMOVE=CLI +ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI +ENV DOCKER_ENABLE_SECURITY=false # Run the application CMD ["java", "-jar", "/app.jar"] diff --git a/HowToAddNewLanguage.md b/HowToAddNewLanguage.md index 26a398bf..b6a7fa4a 100644 --- a/HowToAddNewLanguage.md +++ b/HowToAddNewLanguage.md @@ -8,7 +8,7 @@ Fork Stirling-PDF and make a new branch out of Main Then add reference to the language in the navbar by adding a new language entry to the dropdown -https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/templates/fragments/navbar.html#L306 +https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html and add a flag svg file to https://github.com/Frooodle/Stirling-PDF/tree/main/src/main/resources/static/images/flags Any SVG flags are fine, i got most of mine from [here](https://flagicons.lipis.dev/) @@ -25,7 +25,7 @@ The data-language-code is the code used to reference the file in the next step. Start by copying the existing english property file -[https://github.com/Frooodle/Stirling-PDF/tree/langSetup/src/main/resources/messages_en_GB.properties](https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/messages_en_US.properties) +[https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties](https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties) Copy and rename it to messages_{your data-language-code here}.properties, in the polish example you would set the name to messages_pl_PL.properties diff --git a/README.md b/README.md index 9b7365f3..77170245 100644 --- a/README.md +++ b/README.md @@ -19,46 +19,66 @@ Any file which has been downloaded by the user will have already been deleted fr Feel free to request any features or bug fixes either in github issues or our [Discord](https://discord.gg/Cn8pWhQRxZ) - ![stirling-home](images/stirling-home.png) ## Features -- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages. -- Split PDFs into multiple files at specified page numbers or extract all pages as individual files. -- Merge multiple PDFs together into a single resultant file -- Convert PDFs to and from images -- Reorganize PDF pages into different orders. -- Add/Generate signatures -- Format PDFs into a multi-paged page -- Scale page contents size by set % -- Adjust Contrast -- Crop PDF -- Auto Split PDF (With physically scanned page dividers) -- Flatten PDFs -- Repair PDFs -- Detect and remove blank pages -- Compare 2 PDFs and show differences in text -- Add images to PDFs -- Rotating PDFs in 90 degree increments. -- Compressing PDFs to decrease their filesize. (Using OCRMyPDF) -- Add and remove passwords -- Set PDF Permissions -- Add watermark(s) -- Convert Any common file to PDF (using LibreOffice) -- Convert PDF to Word/Powerpoint/Others (using LibreOffice) -- Convert HTML to PDF -- URL to PDF -- Extract images from PDF -- Extract images from Scans -- Add page numbers -- Auto rename file by detecting PDF header text -- OCR on PDF (Using OCRMyPDF) -- PDF/A conversion (Using OCRMyPDF) -- Edit metadata - Dark mode support. - Custom download options (see [here](https://github.com/Frooodle/Stirling-PDF/blob/main/images/settings.png) for example) - Parallel file processing and downloads - API for integration with external scripts +- Optional Login and Authentication support (see [here](https://github.com/Frooodle/Stirling-PDF/tree/main#login-authentication) for documentation) + + +## **PDF Features** + +### **Page Operations** +- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages. +- Merge multiple PDFs together into a single resultant file. +- Split PDFs into multiple files at specified page numbers or extract all pages as individual files. +- Reorganize PDF pages into different orders. +- Rotate PDFs in 90-degree increments. +- Remove pages. +- Multi-page layout (Format PDFs into a multi-paged page). +- Scale page contents size by set %. +- Adjust Contrast. +- Crop PDF. +- Auto Split PDF (With physically scanned page dividers). +- Extract page(s). +- Convert PDF to a single page. + +### **Conversion Operations** +- Convert PDFs to and from images. +- Convert any common file to PDF (using LibreOffice). +- Convert PDF to Word/Powerpoint/Others (using LibreOffice). +- Convert HTML to PDF. +- URL to PDF. +- Markdown to PDF. + +### **Security & Permissions** +- Add and remove passwords. +- Change/set PDF Permissions. +- Add watermark(s). +- Certify/sign PDFs. +- Sanitize PDFs. +- Auto-redact text. + +### **Other Operations** +- Add/Generate/Write signatures. +- Repair PDFs. +- Detect and remove blank pages. +- Compare 2 PDFs and show differences in text. +- Add images to PDFs. +- Compress PDFs to decrease their filesize (Using OCRMyPDF). +- Extract images from PDF. +- Extract images from Scans. +- Add page numbers. +- Auto rename file by detecting PDF header text. +- OCR on PDF (Using OCRMyPDF). +- PDF/A conversion (Using OCRMyPDF). +- Edit metadata. +- Flatten PDFs. +- Get all information on a PDF to view or export as JSON. + For a overview of the tasks and the technology each uses please view [groups.md](https://github.com/Frooodle/Stirling-PDF/blob/main/Groups.md) Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) hosted by the team at adminforge.de @@ -66,7 +86,6 @@ Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) h ## Technologies used - Spring Boot + Thymeleaf - PDFBox -- IText7 - [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions - [OcrMyPdf](https://github.com/ocrmypdf/OCRmyPDF) - HTML, CSS, JavaScript @@ -82,9 +101,9 @@ Please view https://github.com/Frooodle/Stirling-PDF/blob/main/LocalRunGuide.md ### Docker https://hub.docker.com/r/frooodle/s-pdf -Stirling PDF has 3 different versions, a Full version, Lite and ultra-Lite. Depending on the types of features you use you may want a smaller image to save on space. +Stirling PDF has 3 different versions, a Full version, Lite, and ultra-Lite. Depending on the types of features you use you may want a smaller image to save on space. To see what the different versions offer please look at our [version mapping](https://github.com/Frooodle/Stirling-PDF/blob/main/Version-groups.md) -For people that dont mind about space optimisation just use latest tag. +For people that don't mind about space optimization just use the latest tag. ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest?label=Stirling-PDF%20Full) ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-lite?label=Stirling-PDF%20Lite) ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite) @@ -94,19 +113,15 @@ Docker Run docker run -d \ -p 8080:8080 \ -v /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata \ + -v /location/of/extraConfigs:/configs \ + -e DOCKER_ENABLE_SECURITY=false \ --name stirling-pdf \ frooodle/s-pdf:latest Can also add these for customisation but are not required - -v /location/of/extraConfigs:/configs \ + -v /location/of/customFiles:/customFiles \ - -e APP_HOME_NAME="Stirling PDF" \ - -e APP_HOME_DESCRIPTION="Your locally hosted one-stop-shop for all your PDF needs." \ - -e APP_NAVBAR_NAME="Stirling PDF" \ - -e ALLOW_GOOGLE_VISIBILITY="true" \ - -e APP_ROOT_PATH="/" \ - -e APP_LOCALE="en_GB" \ ``` Docker Compose ``` @@ -118,16 +133,10 @@ services: - '8080:8080' volumes: - /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata #Required for extra OCR languages -# - /location/of/extraConfigs:/configs + - /location/of/extraConfigs:/configs # - /location/of/customFiles:/customFiles/ -# environment: -# APP_LOCALE: en_GB -# APP_HOME_NAME: Stirling PDF -# APP_HOME_DESCRIPTION: Your locally hosted one-stop-shop for all your PDF needs. -# APP_NAVBAR_NAME: Stirling PDF -# APP_ROOT_PATH: / -# ALLOW_GOOGLE_VISIBILITY: true - + environment: + - DOCKER_ENABLE_SECURITY=false ``` @@ -135,8 +144,9 @@ services: Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md ## Want to add your own language? -Stirling PDF currently supports 16! +Stirling PDF currently supports 18! - English (English) (en_GB) +- English (US) (en_US) - Arabic (العربية) (ar_AR) - German (Deutsch) (de_DE) - French (Français) (fr_FR) @@ -152,54 +162,107 @@ Stirling PDF currently supports 16! - Russian (Русский) (ru_RU) - Basque (Euskara) (eu_ES) - Japanese (日本語) (ja_JP) +- Dutch (Nederlands) (nl_NL) If you want to add your own language to Stirling-PDF please refer https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md And please create a PR to merge it back in so others can use it! -Also please note as i add new features i will google translate existing languages so that they dont lose support. This could mean that new features need grammer corrections as added. - ## How to View 1. Open a web browser and navigate to `http://localhost:8080/` 2. Use the application by following the instructions on the website. -## Customize App -Stirling PDF allows easy customization of the visible application name. -Simply use environment variables APP_HOME_NAME, APP_HOME_DESCRIPTION and APP_NAVBAR_NAME with Docker or Java. -If running Java directly, you can also pass these as properties using -D arguments. +## Customisation +Stirling PDF allows easy customization of the app. +Includes things like +- Custom application name +- Custom slogans, icons, images, and even custom HTML (via file overrides) -Using the same method you can also change -- The default language by providing APP_LOCALE with values like de-DE fr-FR or ar-AR (Note the - character not _ ) to select your default language (Will always default to English on invalid locale) Current accepted locales can be seen above in the Want to add your own language section -- Enable/Disable search engine visiblility with ALLOW_GOOGLE_VISIBILITY with true / false values. Default disable visiblility. -- Change root URI for Stirling-PDF ie change server.com/ to server.com/pdf-app by running APP_ROOT_PATH as pdf-app -- Disable and remove endpoints and functionality from Stirling-PDF. Currently the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma seperated lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image to pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Frooodle/Stirling-PDF/blob/main/groups.md) -- Change the max file size allowed through the server with the environment variable MAX_FILE_SIZE. default 2000MB -- Customise static files such as app logo by placing files in the /customFiles/static/ directory. Example to customise app logo is placing a /customFiles/static/favicon.svg to override current SVG. This can be used to change any images/icons/css/fonts/js etc in Stirling-PDF -- Enable/Disable metric api endpoints with ENABLE_API_METRICS. Default enabled +There are two options for this, either using the generated settings file ``settings.yml`` +This file is located in the ``/configs`` directory and follows standard YAML formatting + +Environment variables are also supported and would override the settings file +For example in the settings.yml you have +``` +system: + defaultLocale: 'en-US' +``` + +To have this via an environment variable you would have ``SYSTEM_DEFAULTLOCALE`` + +The Current list of settings is +``` +security: + enableLogin: false # set to 'true' to enable login + csrfDisabled: true + +system: + defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc) + googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow + customStaticFilePath: '/customFiles/static/' # Directory path for custom static files + +#ui: +# appName: exampleAppName # Application's visible name +# homeDescription: I am a description # Short description or tagline shown on homepage. +# appNameNavbar: navbarName # Name displayed on the navigation bar + +endpoints: + toRemove: [] # List endpoints to disable (e.g. ['img-to-pdf', 'remove-pages']) + groupsToRemove: [] # List groups to disable (e.g. ['LibreOffice']) + +metrics: + enabled: true # 'true' to enable Info APIs endpoints (view http://localhost:8080/swagger-ui/index.html#/API to learn more), 'false' to disable +``` +### Extra notes +- Endpoints. Currently, the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma separate lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image-to-pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Frooodle/Stirling-PDF/blob/main/groups.md) +- customStaticFilePath. Customise static files such as the app logo by placing files in the /customFiles/static/ directory. An example of customising app logo is placing a /customFiles/static/favicon.svg to override current SVG. This can be used to change any images/icons/css/fonts/js etc in Stirling-PDF + +### Environment only parameters +- ``SYSTEM_ROOTURIPATH`` ie set to ``/pdf-app`` to Set the application's root URI to ``localhost:8080/pdf-app`` +- ``SYSTEM_CONNECTIONTIMEOUTMINUTES`` to set custom connection timeout values +- ``DOCKER_ENABLE_SECURITY`` to tell docker to download security jar (required as true for auth login) ## API For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation [here](https://app.swaggerhub.com/apis-docs/Frooodle/Stirling-PDF/) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation (Or by following the API button in your settings of Stirling-PDF) +## Login authentication +![stirling-login](images/login-light.png) +### Prerequisites: +- User must have the folder ./configs volumed within docker so that it is retained during updates. +- Docker uses must download the security jar version by setting ``DOCKER_ENABLE_SECURITY`` to ``true`` in environment variables. +- Then either enable login via the settings.yml file or via setting ``SECURITY_ENABLE_LOGIN`` to ``true`` +- Now the initial user will be generated with username ``admin`` and password ``stirling``. On login you will be forced to change the password to a new one. You can also use the environment variables ``SECURITY_INITIALLOGIN_USERNAME`` and ``SECURITY_INITIALLOGIN_PASSWORD`` to set your own straight away (Recommended to remove them after user creation). + +Once the above has been done, on restart, a new stirling-pdf-DB.mv.db will show if everything worked. + +When you login to Stirling PDF you will be redirected to /login page to login with those default credentials. After login everything should function as normal + +To access your account settings go to Account settings in the settings cog menu (top right in navbar) This Account settings menu is also where you find your API key. + +To add new users go to the bottom of Account settings and hit 'Admin Settings', here you can add new users. The different roles mentioned within this are for rate limiting. This is a Work in progress which will be expanding on more in future + +For API usage you must provide a header with 'X-API-Key' and the associated API key for that user. + + ## FAQ -### Q1: Can you add authentication in Stirling PDF? -There is no Auth within Stirling PDF and there is none planned. This feature will not be added. Instead we recommended you use trusted and secure authentication software like Authentik or Authelia. - -### Q2: What are your planned features? -- Crop +### Q1: What are your planned features? - Progress bar/Tracking - Full custom logic pipelines to combine multiple operations together. - Folder support with auto scanning to perform operations on -- Redact sections of pages -- Add page numbers -- Auto rename (Renames file based on file title text) -- URL to PDF -- Change contrast +- Redact text (Via UI not just automated way) +- Add Forms +- Annotations +- Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing +- Fill forms mannual and automatic -### Q3: Why is my application downloading .htm files? -This is a issue caused commonly by your NGINX congifuration. The default file upload size for NGINX is 1MB, you need to add the following in your Nginx sites-available file. client_max_body_size SIZE; Where "SIZE" is 50M for example for 50MB files. +### Q2: Why is my application downloading .htm files? +This is a issue caused commonly by your NGINX congifuration. The default file upload size for NGINX is 1MB, you need to add the following in your Nginx sites-available file. ``client_max_body_size SIZE;`` Where "SIZE" is 50M for example for 50MB files. + +### Q3: Why is my download timing out +NGINX has timeout values by default so if you are running Stirling-PDF behind NGINX you may need to set a timeout value such as adding the config ``proxy_read_timeout 3600;`` diff --git a/build.gradle b/build.gradle index 5e95460f..58d95a88 100644 --- a/build.gradle +++ b/build.gradle @@ -8,15 +8,33 @@ plugins { } group = 'stirling.software' -version = '0.12.3' +version = '0.14.5' sourceCompatibility = '17' repositories { mavenCentral() } +sourceSets { + main { + java { + if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false') { + exclude 'stirling/software/SPDF/config/security/**' + exclude 'stirling/software/SPDF/controller/api/UserController.java' + exclude 'stirling/software/SPDF/controller/web/AccountWebController.java' + exclude 'stirling/software/SPDF/model/ApiKeyAuthenticationToken.java' + exclude 'stirling/software/SPDF/model/Authority.java' + exclude 'stirling/software/SPDF/model/PersistentLogin.java' + exclude 'stirling/software/SPDF/model/User.java' + exclude 'stirling/software/SPDF/repository/**' + } + } + } +} + + openApi { - apiDocsUrl = "http://localhost:8080/v3/api-docs" + apiDocsUrl = "http://localhost:8080/v1/api-docs" outputDir = file("$projectDir") outputFileName = "SwaggerDoc.json" } @@ -48,7 +66,18 @@ dependencies { implementation 'org.yaml:snakeyaml:2.1' implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.2' + + if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') { + implementation 'org.springframework.boot:spring-boot-starter-security:3.1.2' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE' + implementation "org.springframework.boot:spring-boot-starter-data-jpa" + implementation "com.h2database:h2" + } + testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.2' + + + // https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4' implementation 'commons-io:commons-io:2.13.0' @@ -57,16 +86,20 @@ dependencies { //general PDF implementation 'org.apache.pdfbox:pdfbox:2.0.29' + implementation 'org.apache.pdfbox:xmpbox:2.0.29' implementation 'org.bouncycastle:bcprov-jdk15on:1.70' implementation 'org.bouncycastle:bcpkix-jdk15on:1.70' - implementation 'com.itextpdf:itext7-core:7.2.5' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-core' implementation group: 'com.google.zxing', name: 'core', version: '3.5.1' // https://mvnrepository.com/artifact/org.commonmark/commonmark implementation 'org.commonmark:commonmark:0.21.0' + // https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core + implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0' developmentOnly("org.springframework.boot:spring-boot-devtools") + compileOnly 'org.projectlombok:lombok:1.18.28' + annotationProcessor 'org.projectlombok:lombok:1.18.28' } diff --git a/chart/stirling-pdf/Chart.yaml b/chart/stirling-pdf/Chart.yaml new file mode 100644 index 00000000..3482b36d --- /dev/null +++ b/chart/stirling-pdf/Chart.yaml @@ -0,0 +1,15 @@ +apiVersion: v2 +appVersion: 0.14.2 +description: locally hosted web application that allows you to perform various operations on PDF files +home: https://github.com/Frooodle/Stirling-PDF +keywords: +- stirling-pdf +- helm +- charts repo +maintainers: +- name: Frooodle + url: https://github.com/Frooodle/Stirling-PDF +name: stirling-pdf +sources: +- https://github.com/Frooodle/Stirling-PDF +version: 1.0.0 diff --git a/chart/stirling-pdf/templates/NOTES.txt b/chart/stirling-pdf/templates/NOTES.txt new file mode 100644 index 00000000..3b432f00 --- /dev/null +++ b/chart/stirling-pdf/templates/NOTES.txt @@ -0,0 +1,30 @@ +** Please be patient while the chart is being deployed ** + +Get the stirlingpdf URL by running: + +{{- if contains "NodePort" .Values.service.type }} + + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "stirlingpdf.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT/ + +{{- else if contains "LoadBalancer" .Values.service.type }} + +** Please ensure an external IP is associated to the {{ template "stirlingpdf.fullname" . }} service before proceeding ** +** Watch the status using: kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "stirlingpdf.fullname" . }} ** + + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "stirlingpdf.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo http://$SERVICE_IP:{{ .Values.service.externalPort }}/ + +OR + + export SERVICE_HOST=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "stirlingpdf.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') + echo http://$SERVICE_HOST:{{ .Values.service.externalPort }}/ + +{{- else if contains "ClusterIP" .Values.service.type }} + + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "stirlingpdf.name" . }}" -l "release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo http://127.0.0.1:8080/ + kubectl port-forward $POD_NAME 8080:8080 --namespace {{ .Release.Namespace }} + +{{- end }} diff --git a/chart/stirling-pdf/templates/_helpers.tpl b/chart/stirling-pdf/templates/_helpers.tpl new file mode 100644 index 00000000..4c862604 --- /dev/null +++ b/chart/stirling-pdf/templates/_helpers.tpl @@ -0,0 +1,129 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "stirlingpdf.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "stirlingpdf.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{- /* +Create chart name and version as used by the chart label. + +It does minimal escaping for use in Kubernetes labels. + +Example output: + +stirlingpdf-0.4.5 +*/ -}} +{{- define "stirlingpdf.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "stirlingpdf.labels" -}} +helm.sh/chart: {{ include "stirlingpdf.chart" . }} +{{ include "stirlingpdf.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +{{- if .Values.commonLabels}} +{{ toYaml .Values.commonLabels }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "stirlingpdf.selectorLabels" -}} +app.kubernetes.io/name: {{ include "stirlingpdf.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "stirlingpdf.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "stirlingpdf.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Return the proper image name to change the volume permissions +*/}} +{{- define "stirlingpdf.volumePermissions.image" -}} +{{- $registryName := .Values.volumePermissions.image.registry -}} +{{- $repositoryName := .Values.volumePermissions.image.repository -}} +{{- $tag := .Values.volumePermissions.image.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "stirlingpdf.imagePullSecrets" -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. +Also, we can not use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.global.imagePullSecrets }} + - name: {{ . }} +{{- end }} +{{- else if or .Values.image.pullSecrets .Values.volumePermissions.image.pullSecrets }} +imagePullSecrets: +{{- range .Values.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.volumePermissions.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- end -}} +{{- else if or .Values.image.pullSecrets .Values.volumePermissions.image.pullSecrets }} +imagePullSecrets: +{{- range .Values.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.volumePermissions.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/chart/stirling-pdf/templates/deployment.yaml b/chart/stirling-pdf/templates/deployment.yaml new file mode 100644 index 00000000..290a56c9 --- /dev/null +++ b/chart/stirling-pdf/templates/deployment.yaml @@ -0,0 +1,129 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "stirlingpdf.fullname" . }} + {{- with .Values.deployment.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "stirlingpdf.labels" . | nindent 4 }} + {{- if .Values.deployment.labels }} + {{- toYaml .Values.deployment.labels | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + {{- include "stirlingpdf.selectorLabels" . | nindent 6 }} + replicas: {{ .Values.replicaCount }} + strategy: +{{ toYaml .Values.strategy | indent 4 }} + revisionHistoryLimit: 10 + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "stirlingpdf.selectorLabels" . | nindent 8 }} + {{- if .Values.podLabels }} + {{- toYaml .Values.podLabels | nindent 8 }} + {{- end }} + spec: + {{- if .Values.priorityClassName }} + priorityClassName: "{{ .Values.priorityClassName }}" + {{- end }} + {{- if .Values.securityContext.enabled }} + securityContext: + fsGroup: {{ .Values.securityContext.fsGroup }} + {{- if .Values.securityContext.runAsNonRoot }} + runAsNonRoot: {{ .Values.securityContext.runAsNonRoot }} + {{- end }} + {{- if .Values.securityContext.supplementalGroups }} + supplementalGroups: {{ .Values.securityContext.supplementalGroups }} + {{- end }} + {{- else if .Values.persistence.enabled }} + initContainers: + - name: volume-permissions + image: {{ template "stirlingpdf.volumePermissions.image" . }} + imagePullPolicy: "{{ .Values.volumePermissions.image.pullPolicy }}" + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 10 }} + command: ['sh', '-c', 'chown -R {{ .Values.securityContext.fsGroup }}:{{ .Values.securityContext.fsGroup }} {{ .Values.persistence.path }}'] + volumeMounts: + - mountPath: {{ .Values.persistence.path }} + name: storage-volume + {{- end }} +{{- include "stirlingpdf.imagePullSecrets" . | indent 6 }} + containers: + - name: {{ .Chart.Name }} + image: {{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 10 }} +{{- if .Values.envs }} + env: +{{ toYaml .Values.envs | indent 8 }} +{{- end }} +{{- if .Values.extraArgs }} + args: +{{ toYaml .Values.extraArgs | indent 8 }} +{{- end }} + ports: + - name: http + containerPort: 8080 + livenessProbe: + httpGet: + path: / + port: http +{{ toYaml .Values.probes.livenessHttpGetConfig | indent 12 }} +{{ toYaml .Values.probes.liveness | indent 10 }} + readinessProbe: + httpGet: + path: / + port: http +{{ toYaml .Values.probes.readinessHttpGetConfig | indent 12 }} +{{ toYaml .Values.probes.readiness | indent 10 }} + volumeMounts: +{{- if .Values.deployment.extraVolumeMounts }} + {{- toYaml .Values.deployment.extraVolumeMounts | nindent 8 }} +{{- end }} +{{- if .Values.deployment.sidecarContainers }} +{{- range $name, $spec := .Values.deployment.sidecarContainers }} + - name: {{ $name }} +{{- toYaml $spec | nindent 8 }} +{{- end }} +{{- end }} + {{- with .Values.resources }} + resources: +{{ toYaml . | indent 10 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.schedulerName }} + schedulerName: {{ .Values.schedulerName }} + {{- end }} + serviceAccountName: {{ include "stirlingpdf.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} + volumes: + {{- if .Values.deployment.extraVolumes }} + {{- toYaml .Values.deployment.extraVolumes | nindent 6 }} + {{- end }} + - name: storage-volume + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim | default (include "stirlingpdf.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end }} diff --git a/chart/stirling-pdf/templates/ingress.yaml b/chart/stirling-pdf/templates/ingress.yaml new file mode 100644 index 00000000..c09fef68 --- /dev/null +++ b/chart/stirling-pdf/templates/ingress.yaml @@ -0,0 +1,85 @@ +{{- if .Values.ingress.enabled }} +{{- $servicePort := .Values.service.externalPort -}} +{{- $serviceName := include "stirlingpdf.fullname" . -}} +{{- $ingressExtraPaths := .Values.ingress.extraPaths -}} +--- +{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion }} +apiVersion: extensions/v1beta1 +{{- else if semverCompare "<1.19-0" .Capabilities.KubeVersion.GitVersion }} +apiVersion: networking.k8s.io/v1beta1 +{{- else }} +apiVersion: networking.k8s.io/v1 +{{- end }} +kind: Ingress +metadata: + name: {{ include "stirlingpdf.fullname" . }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "stirlingpdf.labels" . | nindent 4 }} + {{- with .Values.ingress.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.ingressClassName }} + ingressClassName: {{ . }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .name }} + http: + paths: + {{- range $ingressExtraPaths }} + - path: {{ default "/" .path | quote }} + backend: + {{- if semverCompare "<1.19-0" $.Capabilities.KubeVersion.GitVersion }} + {{- if $.Values.service.servicename }} + serviceName: {{ $.Values.service.servicename }} + {{- else }} + serviceName: {{ default $serviceName .service }} + {{- end }} + servicePort: {{ default $servicePort .port }} + {{- else }} + service: + {{- if $.Values.service.servicename }} + name: {{ $.Values.service.servicename }} + {{- else }} + name: {{ default $serviceName .service }} + {{- end }} + port: + number: {{ default $servicePort .port }} + pathType: {{ default $.Values.ingress.pathType .pathType }} + {{- end }} + {{- end }} + - path: {{ default "/" .path | quote }} + backend: + {{- if semverCompare "<1.19-0" $.Capabilities.KubeVersion.GitVersion }} + {{- if $.Values.service.servicename }} + serviceName: {{ $.Values.service.servicename }} + {{- else }} + serviceName: {{ default $serviceName .service }} + {{- end }} + servicePort: {{ default $servicePort .servicePort }} + {{- else }} + service: + {{- if $.Values.service.servicename }} + name: {{ $.Values.service.servicename }} + {{- else }} + name: {{ default $serviceName .service }} + {{- end }} + port: + number: {{ default $servicePort .port }} + pathType: {{ $.Values.ingress.pathType }} + {{- end }} + {{- end }} + tls: + {{- range .Values.ingress.hosts }} + {{- if .tls }} + - hosts: + - {{ .name }} + secretName: {{ .tlsSecret }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/chart/stirling-pdf/templates/pv.yaml b/chart/stirling-pdf/templates/pv.yaml new file mode 100644 index 00000000..aa99c6a9 --- /dev/null +++ b/chart/stirling-pdf/templates/pv.yaml @@ -0,0 +1,16 @@ +{{- if .Values.persistence.pv.enabled -}} +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ .Values.persistence.pv.pvname | default (include "stirlingpdf.fullname" .) }} + labels: + {{- include "stirlingpdf.labels" . | nindent 4 }} +spec: + capacity: + storage: {{ .Values.persistence.pv.capacity.storage }} + accessModes: + - {{ .Values.persistence.pv.accessMode | quote }} + nfs: + server: {{ .Values.persistence.pv.nfs.server }} + path: {{ .Values.persistence.pv.nfs.path | quote }} +{{- end }} \ No newline at end of file diff --git a/chart/stirling-pdf/templates/pvc.yaml b/chart/stirling-pdf/templates/pvc.yaml new file mode 100644 index 00000000..f7a21722 --- /dev/null +++ b/chart/stirling-pdf/templates/pvc.yaml @@ -0,0 +1,27 @@ +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ include "stirlingpdf.fullname" . }} + labels: + {{- include "stirlingpdf.labels" . | nindent 4 }} + {{- with .Values.persistence.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} +{{- if .Values.persistence.storageClass }} +{{- if (eq "-" .Values.persistence.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.persistence.storageClass }}" +{{- end }} +{{- if .Values.persistence.volumeName }} + volumeName: "{{ .Values.persistence.volumeName }}" +{{- end }} +{{- end }} +{{- end }} diff --git a/chart/stirling-pdf/templates/service.yaml b/chart/stirling-pdf/templates/service.yaml new file mode 100644 index 00000000..a529c3f2 --- /dev/null +++ b/chart/stirling-pdf/templates/service.yaml @@ -0,0 +1,48 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.service.servicename | default (include "stirlingpdf.fullname" .) }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "stirlingpdf.labels" . | nindent 4 }} + {{- with .Values.service.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + {{- if (or (eq .Values.service.type "LoadBalancer") (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort)))) }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} + {{- end }} + {{- if (and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerIP) }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- if (and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerSourceRanges) }} + loadBalancerSourceRanges: + {{- with .Values.service.loadBalancerSourceRanges }} +{{ toYaml . | indent 2 }} + {{- end }} + {{- end }} + {{- if eq .Values.service.type "ClusterIP" }} + {{- if .Values.service.clusterIP }} + clusterIP: {{ .Values.service.clusterIP }} + {{- end }} + {{- end }} + ports: + - port: {{ .Values.service.externalPort }} +{{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }} + nodePort: {{.Values.service.nodePort}} +{{- end }} +{{- if .Values.service.targetPort }} + targetPort: {{ .Values.service.targetPort }} + name: {{ .Values.service.targetPort }} +{{- else }} + targetPort: http + name: http +{{- end }} + protocol: TCP + + selector: + {{- include "stirlingpdf.selectorLabels" . | nindent 4 }} diff --git a/chart/stirling-pdf/templates/serviceaccount.yaml b/chart/stirling-pdf/templates/serviceaccount.yaml new file mode 100644 index 00000000..7156e4a4 --- /dev/null +++ b/chart/stirling-pdf/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "stirlingpdf.serviceAccountName" . }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{ toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "stirlingpdf.labels" . | nindent 4 }} +{{- end }} diff --git a/chart/stirling-pdf/templates/servicemonitor.yaml b/chart/stirling-pdf/templates/servicemonitor.yaml new file mode 100644 index 00000000..ca0d31bb --- /dev/null +++ b/chart/stirling-pdf/templates/servicemonitor.yaml @@ -0,0 +1,31 @@ +{{- if and ( .Capabilities.APIVersions.Has "monitoring.coreos.com/v1" ) ( .Values.serviceMonitor.enabled ) }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "stirlingpdf.fullname" . }} + namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }} + labels: + {{- include "stirlingpdf.labels" . | nindent 4 }} + {{- with .Values.serviceMonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + endpoints: + - targetPort: 8080 +{{- if .Values.serviceMonitor.interval }} + interval: {{ .Values.serviceMonitor.interval }} +{{- end }} +{{- if .Values.serviceMonitor.metricsPath }} + path: {{ .Values.serviceMonitor.metricsPath }} +{{- end }} +{{- if .Values.serviceMonitor.timeout }} + scrapeTimeout: {{ .Values.serviceMonitor.timeout }} +{{- end }} + jobLabel: {{ include "stirlingpdf.fullname" . }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "stirlingpdf.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/chart/stirling-pdf/values.yaml b/chart/stirling-pdf/values.yaml new file mode 100644 index 00000000..3c4ca888 --- /dev/null +++ b/chart/stirling-pdf/values.yaml @@ -0,0 +1,239 @@ +extraArgs: [] + # - --storage-timestamp-tolerance 1s +replicaCount: 1 +strategy: + type: RollingUpdate +image: + repository: frooodle/s-pdf + # took Chart appVersion by default + tag: ~ + pullPolicy: IfNotPresent +secret: + labels: {} +## Labels to apply to all resources +## +commonLabels: {} +# team_name: dev + +envs: [] +# - name: PP_HOME_NAME +# value: "Stirling PDF" +# - name: APP_HOME_DESCRIPTION +# value: "Your locally hosted one-stop-shop for all your PDF needs." +# - name: APP_NAVBAR_NAME +# value: "Stirling PDF" +# - name: ALLOW_GOOGLE_VISIBILITY +# value: "true" +# - name: APP_ROOT_PATH +# value: "/" +# - name: APP_LOCALE +# value: "en_GB" + +deployment: + ## stirling-pdf Deployment annotations + annotations: {} + # name: value + labels: {} + # name: value + # additional volumes + extraVolumes: [] + # - name: nginx-config + # secret: + # secretName: nginx-config + # additional volumes to mount + extraVolumeMounts: [] + ## sidecarContainers for the stirling-pdf + # Can be used to add a proxy to the pod that does + # scanning for secrets, signing, authentication, validation + # of the chart's content, send notifications... + sidecarContainers: {} + ## Example sidecarContainer which uses an extraVolume from above and + ## a named port that can be referenced in the service as targetPort. + # proxy: + # image: nginx:latest + # ports: + # - name: proxy + # containerPort: 8081 + # volumeMounts: + # - name: nginx-config + # readOnly: true + # mountPath: /etc/nginx + +## Pod annotations +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +## Read more about kube2iam to provide access to s3 https://github.com/jtblin/kube2iam +## +podAnnotations: {} + # iam.amazonaws.com/role: role-arn + +## Pod labels +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: {} + # name: value + +service: + servicename: + type: ClusterIP + externalTrafficPolicy: Local + ## Uses pre-assigned IP address from cloud provider + ## Only valid if service.type: LoadBalancer + loadBalancerIP: + ## Limits which cidr blocks can connect to service's load balancer + ## Only valid if service.type: LoadBalancer + loadBalancerSourceRanges: [] + # clusterIP: None + externalPort: 8080 + ## targetPort of the container to use. If a sidecar should handle the + ## requests first, use the named port from the sidecar. See sidecar example + ## from deployment above. Leave empty to use stirling-pdf directly. + targetPort: + nodePort: + annotations: {} + labels: {} + +serviceMonitor: + enabled: false + # namespace: prometheus + labels: {} + metricsPath: "/metrics" + # timeout: 60 + # interval: 60 + +resources: {} +# limits: +# cpu: 100m +# memory: 128Mi +# requests: +# cpu: 80m +# memory: 64Mi + +probes: + liveness: + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + livenessHttpGetConfig: + scheme: HTTP + readiness: + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + readinessHttpGetConfig: + scheme: HTTP + +serviceAccount: + create: true + name: "" + automountServiceAccountToken: false + ## Annotations for the Service Account + annotations: {} + +# UID/GID 1000 is the default user "stirling-pdf" used in +# the container image starting in v0.8.0 and above. This +# is required for local persistent storage. If your cluster +# does not allow this, try setting securityContext: {} +securityContext: + enabled: true + fsGroup: 1000 + ## Optionally, specify supplementalGroups and/or + ## runAsNonRoot for security purposes + # runAsNonRoot: true + # supplementalGroups: [1000] + +containerSecurityContext: {} + +priorityClassName: "" + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +persistence: + enabled: false + accessMode: ReadWriteOnce + size: 8Gi + labels: {} + # name: value + path: /tmp + ## A manually managed Persistent Volume and Claim + ## Requires persistence.enabled: true + ## If defined, PVC must be created manually before volume will be bound + # existingClaim: + + ## stirling-pdf data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + # volumeName: + pv: + enabled: false + pvname: + capacity: + storage: 8Gi + accessMode: ReadWriteOnce + nfs: + server: + path: + +## Init containers parameters: +## volumePermissions: Change the owner of the persistent volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + image: + registry: docker.io + repository: bitnami/minideb + tag: buster + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + +## Ingress for load balancer +ingress: + enabled: false + pathType: "ImplementationSpecific" + ## stirling-pdf Ingress labels + ## + labels: {} + # dns: "route53" + + ## stirling-pdf Ingress annotations + ## + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + + ## stirling-pdf Ingress hostnames + ## Must be provided if Ingress is enabled + ## + hosts: [] + # - name: stirling-pdf.domain1.com + # path: / + # tls: false + # - name: stirling-pdf.domain2.com + # path: / + # + # ## Set this to true in order to enable TLS on the ingress record + # tls: true + # + # ## If TLS is set to true, you must declare what secret will store the key/certificate for TLS + # ## Secrets must be added manually to the namespace + # tlsSecret: stirling-pdf.domain2-tls + + # For Kubernetes >= 1.18 you should specify the ingress-controller via the field ingressClassName + # See https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/#specifying-the-class-of-an-ingress + ingressClassName: + diff --git a/images/login-dark.png b/images/login-dark.png new file mode 100644 index 00000000..672a6967 Binary files /dev/null and b/images/login-dark.png differ diff --git a/images/login-light.png b/images/login-light.png new file mode 100644 index 00000000..7b845c61 Binary files /dev/null and b/images/login-light.png differ diff --git a/images/stirling-home.png b/images/stirling-home.png index 27241bf4..c01af6f9 100644 Binary files a/images/stirling-home.png and b/images/stirling-home.png differ diff --git a/scripts/detect-blank-pages.py b/scripts/detect-blank-pages.py index 6712dc12..474c2735 100644 --- a/scripts/detect-blank-pages.py +++ b/scripts/detect-blank-pages.py @@ -1,5 +1,4 @@ import cv2 -import numpy as np import sys import argparse diff --git a/scripts/init.sh b/scripts/init.sh index 99ff2226..e65914c4 100644 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -16,6 +16,25 @@ if [[ -n "$TESSERACT_LANGS" ]]; then done fi +# Check for DOCKER_ENABLE_SECURITY and download the appropriate JAR if required +if [ "$DOCKER_ENABLE_SECURITY" = "true" ] && [ "$VERSION_TAG" != "alpha" ]; then + if [ ! -f app-security.jar ]; then + echo "Trying to download from: https://github.com/Frooodle/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar" + curl -L -o app-security.jar https://github.com/Frooodle/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar + + # If the first download attempt failed, try with the 'v' prefix + if [ $? -ne 0 ]; then + echo "Trying to download from: https://github.com/Frooodle/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar" + curl -L -o app-security.jar https://github.com/Frooodle/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar + fi + + if [ $? -eq 0 ]; then # checks if curl was successful + rm -f app.jar + ln -s app-security.jar app.jar + fi + fi +fi + # Run the main command exec "$@" \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index f9512295..5dd7fed3 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -1,20 +1,19 @@ package stirling.software.SPDF; -import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collections; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.core.env.Environment; -import org.springframework.scheduling.annotation.EnableScheduling; import jakarta.annotation.PostConstruct; +import stirling.software.SPDF.config.ConfigInitializer; import stirling.software.SPDF.utils.GeneralUtils; - @SpringBootApplication + //@EnableScheduling public class SPdfApplication { @@ -48,7 +47,15 @@ public class SPdfApplication { } public static void main(String[] args) { - SpringApplication.run(SPdfApplication.class, args); + SpringApplication app = new SpringApplication(SPdfApplication.class); + app.addInitializers(new ConfigInitializer()); + if (Files.exists(Paths.get("configs/settings.yml"))) { + app.setDefaultProperties(Collections.singletonMap("spring.config.additional-location", "file:configs/settings.yml")); + } else { + System.out.println("External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); + } + app.run(args); + try { Thread.sleep(1000); } catch (InterruptedException e) { @@ -58,7 +65,6 @@ public class SPdfApplication { GeneralUtils.createDir("customFiles/static/"); GeneralUtils.createDir("customFiles/templates/"); - GeneralUtils.createDir("config"); diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java index 430bcae8..5d4bad43 100644 --- a/src/main/java/stirling/software/SPDF/config/AppConfig.java +++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java @@ -1,42 +1,55 @@ -package stirling.software.SPDF.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class AppConfig { - @Bean(name = "appName") - public String appName() { - String appName = System.getProperty("APP_HOME_NAME"); - if (appName == null) - appName = System.getenv("APP_HOME_NAME"); - return (appName != null) ? appName : "Stirling PDF"; - } - - @Bean(name = "appVersion") - public String appVersion() { - String version = getClass().getPackage().getImplementationVersion(); - return (version != null) ? version : "0.0.0"; - } - - @Bean(name = "homeText") - public String homeText() { - String homeText = System.getProperty("APP_HOME_DESCRIPTION"); - if (homeText == null) - homeText = System.getenv("APP_HOME_DESCRIPTION"); - return (homeText != null) ? homeText : "null"; - } - - @Bean(name = "navBarText") - public String navBarText() { - String navBarText = System.getProperty("APP_NAVBAR_NAME"); - if (navBarText == null) - navBarText = System.getenv("APP_NAVBAR_NAME"); - if (navBarText == null) - navBarText = System.getProperty("APP_HOME_NAME"); - if (navBarText == null) - navBarText = System.getenv("APP_HOME_NAME"); - - return (navBarText != null) ? navBarText : "Stirling PDF"; - } +package stirling.software.SPDF.config; + +import org.springframework.beans.PropertyEditorRegistrar; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import stirling.software.SPDF.model.ApplicationProperties; +@Configuration +public class AppConfig { + + + @Autowired + ApplicationProperties applicationProperties; + + @Bean(name = "loginEnabled") + public boolean loginEnabled() { + return applicationProperties.getSecurity().getEnableLogin(); + } + + @Bean(name = "appName") + public String appName() { + String homeTitle = applicationProperties.getUi().getAppName(); + return (homeTitle != null) ? homeTitle : "Stirling PDF"; + } + + @Bean(name = "appVersion") + public String appVersion() { + String version = getClass().getPackage().getImplementationVersion(); + return (version != null) ? version : "0.0.0"; + } + + @Bean(name = "homeText") + public String homeText() { + return (applicationProperties.getUi().getHomeDescription() != null) ? applicationProperties.getUi().getHomeDescription() : "null"; + } + + + @Bean(name = "navBarText") + public String navBarText() { + String defaultNavBar = applicationProperties.getUi().getAppNameNavbar() != null ? applicationProperties.getUi().getAppNameNavbar() : applicationProperties.getUi().getAppName(); + return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF"; + } + + @Bean(name = "rateLimit") + public boolean rateLimit() { + String appName = System.getProperty("rateLimit"); + if (appName == null) + appName = System.getenv("rateLimit"); + System.out.println("rateLimit=" + appName); + return (appName != null) ? Boolean.valueOf(appName) : false; + } + + } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/Beans.java index f8ff302e..d1c6a03b 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/Beans.java @@ -2,6 +2,7 @@ package stirling.software.SPDF.config; import java.util.Locale; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.LocaleResolver; @@ -10,9 +11,14 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.SessionLocaleResolver; +import stirling.software.SPDF.model.ApplicationProperties; + @Configuration public class Beans implements WebMvcConfigurer { - + + @Autowired + ApplicationProperties applicationProperties; + @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(localeChangeInterceptor()); @@ -29,10 +35,9 @@ public class Beans implements WebMvcConfigurer { @Bean public LocaleResolver localeResolver() { SessionLocaleResolver slr = new SessionLocaleResolver(); - - String appLocaleEnv = System.getProperty("APP_LOCALE"); - if (appLocaleEnv == null) - appLocaleEnv = System.getenv("APP_LOCALE"); + + + String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale(); Locale defaultLocale = Locale.UK; // Fallback to UK locale if environment variable is not set if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) { diff --git a/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java b/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java index efa084b3..894d50d8 100644 --- a/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java +++ b/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java @@ -1,79 +1,68 @@ -package stirling.software.SPDF.config; - -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.Arrays; -import java.util.List; -import java.util.HashMap; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -public class CleanUrlInterceptor implements HandlerInterceptor { - - private static final List ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints"); - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { - String queryString = request.getQueryString(); - if (queryString != null && !queryString.isEmpty()) { - String requestURI = request.getRequestURI(); - - Map parameters = new HashMap<>(); - - // Keep only the allowed parameters - String[] queryParameters = queryString.split("&"); - for (String param : queryParameters) { - String[] keyValue = param.split("="); - if (keyValue.length != 2) { - continue; - } - if (ALLOWED_PARAMS.contains(keyValue[0])) { - parameters.put(keyValue[0], keyValue[1]); - } - } - - // If there are any parameters that are not allowed - if (parameters.size() != queryParameters.length) { - // Construct new query string - StringBuilder newQueryString = new StringBuilder(); - for (Map.Entry entry : parameters.entrySet()) { - if (newQueryString.length() > 0) { - newQueryString.append("&"); - } - newQueryString.append(entry.getKey()).append("=").append(entry.getValue()); - } - - // Redirect to the URL with only allowed query parameters - String redirectUrl = requestURI + "?" + newQueryString; - response.sendRedirect(redirectUrl); - return false; - } - } - return true; - } - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, - ModelAndView modelAndView) { - } - - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, - Exception ex) { - } -} +package stirling.software.SPDF.config; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class CleanUrlInterceptor implements HandlerInterceptor { + + private static final List ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints", "logout", "error", "file", "messageType"); + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + String queryString = request.getQueryString(); + if (queryString != null && !queryString.isEmpty()) { + String requestURI = request.getRequestURI(); + Map parameters = new HashMap<>(); + + // Keep only the allowed parameters + String[] queryParameters = queryString.split("&"); + for (String param : queryParameters) { + String[] keyValue = param.split("="); + if (keyValue.length != 2) { + continue; + } + if (ALLOWED_PARAMS.contains(keyValue[0])) { + parameters.put(keyValue[0], keyValue[1]); + } + } + + // If there are any parameters that are not allowed + if (parameters.size() != queryParameters.length) { + // Construct new query string + StringBuilder newQueryString = new StringBuilder(); + for (Map.Entry entry : parameters.entrySet()) { + if (newQueryString.length() > 0) { + newQueryString.append("&"); + } + newQueryString.append(entry.getKey()).append("=").append(entry.getValue()); + } + + // Redirect to the URL with only allowed query parameters + String redirectUrl = requestURI + "?" + newQueryString; + response.sendRedirect(redirectUrl); + return false; + } + } + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, + ModelAndView modelAndView) { + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, + Exception ex) { + } +} diff --git a/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java b/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java new file mode 100644 index 00000000..862f5718 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java @@ -0,0 +1,129 @@ +package stirling.software.SPDF.config; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; + +public class ConfigInitializer implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + try { + ensureConfigExists(); + } catch (IOException e) { + throw new RuntimeException("Failed to initialize application configuration", e); + } + } + + public void ensureConfigExists() throws IOException { + // Define the path to the external config directory + Path destPath = Paths.get("configs", "settings.yml"); + + // Check if the file already exists + if (Files.notExists(destPath)) { + // Ensure the destination directory exists + Files.createDirectories(destPath.getParent()); + + // Copy the resource from classpath to the external directory + try (InputStream in = getClass().getClassLoader().getResourceAsStream("settings.yml.template")) { + if (in != null) { + Files.copy(in, destPath); + } else { + throw new FileNotFoundException("Resource file not found: settings.yml.template"); + } + } + } else { + // If user file exists, we need to merge it with the template from the classpath + List templateLines; + try (InputStream in = getClass().getClassLoader().getResourceAsStream("settings.yml.template")) { + templateLines = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)).lines() + .collect(Collectors.toList()); + } + + mergeYamlFiles(templateLines, destPath, destPath); + } + } + + public void mergeYamlFiles(List templateLines, Path userFilePath, Path outputPath) throws IOException { + List userLines = Files.readAllLines(userFilePath); + List mergedLines = new ArrayList<>(); + boolean insideAutoGenerated = false; + boolean beforeFirstKey = true; + + Function isCommented = line -> line.trim().startsWith("#"); + Function extractKey = line -> { + String[] parts = line.split(":"); + return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : ""; + }; + + Set userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet()); + + for (String line : templateLines) { + String key = extractKey.apply(line); + + if (line.trim().equalsIgnoreCase("AutomaticallyGenerated:")) { + insideAutoGenerated = true; + mergedLines.add(line); + continue; + } else if (insideAutoGenerated && line.trim().isEmpty()) { + insideAutoGenerated = false; + mergedLines.add(line); + continue; + } + + if (beforeFirstKey && (isCommented.apply(line) || line.trim().isEmpty())) { + // Handle top comments and empty lines before the first key. + mergedLines.add(line); + continue; + } + + if (!key.isEmpty()) + beforeFirstKey = false; + + if (userKeys.contains(key)) { + // If user has any version (commented or uncommented) of this key, skip the + // template line + Optional userValue = userLines.stream() + .filter(l -> extractKey.apply(l).equalsIgnoreCase(key) && !isCommented.apply(l)).findFirst(); + if (userValue.isPresent()) + mergedLines.add(userValue.get()); + continue; + } + + if (isCommented.apply(line) || line.trim().isEmpty() || !userKeys.contains(key)) { + mergedLines.add(line); // If line is commented, empty or key not present in user's file, retain the + // template line + continue; + } + } + + // Add any additional uncommented user lines that are not present in the + // template + for (String userLine : userLines) { + String userKey = extractKey.apply(userLine); + boolean isPresentInTemplate = templateLines.stream().map(extractKey) + .anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey)); + if (!isPresentInTemplate && !isCommented.apply(userLine)) { + mergedLines.add(userLine); + } + } + + Files.write(outputPath, mergedLines, StandardCharsets.UTF_8); + } + +} \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index 11f7b8c8..ee17e0b9 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -1,20 +1,28 @@ package stirling.software.SPDF.config; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; + +import stirling.software.SPDF.model.ApplicationProperties; @Service public class EndpointConfiguration { private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); private Map endpointStatuses = new ConcurrentHashMap<>(); private Map> endpointGroups = new ConcurrentHashMap<>(); - public EndpointConfiguration() { + private final ApplicationProperties applicationProperties; + + @Autowired + public EndpointConfiguration(ApplicationProperties applicationProperties) { + this.applicationProperties = applicationProperties; init(); processEnvironmentConfigs(); } @@ -198,21 +206,19 @@ public class EndpointConfiguration { } - + private void processEnvironmentConfigs() { - String endpointsToRemove = System.getenv("ENDPOINTS_TO_REMOVE"); - String groupsToRemove = System.getenv("GROUPS_TO_REMOVE"); + List endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); + List groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); if (endpointsToRemove != null) { - String[] endpoints = endpointsToRemove.split(","); - for (String endpoint : endpoints) { + for (String endpoint : endpointsToRemove) { disableEndpoint(endpoint.trim()); } } if (groupsToRemove != null) { - String[] groups = groupsToRemove.split(","); - for (String group : groups) { + for (String group : groupsToRemove) { disableGroup(group.trim()); } } diff --git a/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java b/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java index 5befd511..77191a41 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java @@ -1,26 +1,26 @@ -package stirling.software.SPDF.config; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@Component -public class EndpointInterceptor implements HandlerInterceptor { - - @Autowired - private EndpointConfiguration endpointConfiguration; - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { - String requestURI = request.getRequestURI(); - if (!endpointConfiguration.isEndpointEnabled(requestURI)) { - response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled"); - return false; - } - return true; - } +package stirling.software.SPDF.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class EndpointInterceptor implements HandlerInterceptor { + + @Autowired + private EndpointConfiguration endpointConfiguration; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + String requestURI = request.getRequestURI(); + if (!endpointConfiguration.isEndpointEnabled(requestURI)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled"); + return false; + } + return true; + } } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/config/MetricsConfig.java b/src/main/java/stirling/software/SPDF/config/MetricsConfig.java index 25d6d8d6..1cdc99e3 100644 --- a/src/main/java/stirling/software/SPDF/config/MetricsConfig.java +++ b/src/main/java/stirling/software/SPDF/config/MetricsConfig.java @@ -1,24 +1,24 @@ -package stirling.software.SPDF.config; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import io.micrometer.core.instrument.Meter; -import io.micrometer.core.instrument.config.MeterFilter; -import io.micrometer.core.instrument.config.MeterFilterReply; - -@Configuration -public class MetricsConfig { - - @Bean - public MeterFilter meterFilter() { - return new MeterFilter() { - @Override - public MeterFilterReply accept(Meter.Id id) { - if (id.getName().equals("http.requests")) { - return MeterFilterReply.NEUTRAL; - } - return MeterFilterReply.DENY; - } - }; - } +package stirling.software.SPDF.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.config.MeterFilter; +import io.micrometer.core.instrument.config.MeterFilterReply; + +@Configuration +public class MetricsConfig { + + @Bean + public MeterFilter meterFilter() { + return new MeterFilter() { + @Override + public MeterFilterReply accept(Meter.Id id) { + if (id.getName().equals("http.requests")) { + return MeterFilterReply.NEUTRAL; + } + return MeterFilterReply.DENY; + } + }; + } } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java index 326ba09d..6ee59db7 100644 --- a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java +++ b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java @@ -1,48 +1,48 @@ -package stirling.software.SPDF.config; - -import java.io.IOException; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.MeterRegistry; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@Component -public class MetricsFilter extends OncePerRequestFilter { - - private final MeterRegistry meterRegistry; - - @Autowired - public MetricsFilter(MeterRegistry meterRegistry) { - this.meterRegistry = meterRegistry; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - String uri = request.getRequestURI(); - - //System.out.println("uri="+uri + ", method=" + request.getMethod() ); - // Ignore static resources - if (!(uri.startsWith("/js") || uri.startsWith("api-docs") || uri.endsWith("robots.txt") || uri.startsWith("/images") || uri.endsWith(".png") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) { - Counter counter = Counter.builder("http.requests") - .tag("uri", uri) - .tag("method", request.getMethod()) - .register(meterRegistry); - - counter.increment(); - //System.out.println("Counted"); - } - - filterChain.doFilter(request, response); - } - - - -} +package stirling.software.SPDF.config; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class MetricsFilter extends OncePerRequestFilter { + + private final MeterRegistry meterRegistry; + + @Autowired + public MetricsFilter(MeterRegistry meterRegistry) { + this.meterRegistry = meterRegistry; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String uri = request.getRequestURI(); + + //System.out.println("uri="+uri + ", method=" + request.getMethod() ); + // Ignore static resources + if (!(uri.startsWith("/js") || uri.startsWith("api-docs") || uri.endsWith("robots.txt") || uri.startsWith("/images") || uri.endsWith(".png") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) { + Counter counter = Counter.builder("http.requests") + .tag("uri", uri) + .tag("method", request.getMethod()) + .register(meterRegistry); + + counter.increment(); + //System.out.println("Counted"); + } + + filterChain.doFilter(request, response); + } + + + +} diff --git a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java index d7495aca..2583277e 100644 --- a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java +++ b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java @@ -1,36 +1,27 @@ -package stirling.software.SPDF.config; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; - -@Configuration -public class OpenApiConfig { - - @Bean - public OpenAPI customOpenAPI() { - String version = getClass().getPackage().getImplementationVersion(); - if (version == null) { - Properties props = new Properties(); - try (InputStream input = getClass().getClassLoader().getResourceAsStream("version.properties")) { - props.load(input); - version = props.getProperty("version"); - } catch (IOException ex) { - ex.printStackTrace(); - version = "1.0.0"; // default version if all else fails - } - } - - return new OpenAPI().components(new Components()).info( - new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); - } - - -} +package stirling.software.SPDF.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; + +@Configuration +public class OpenApiConfig { + + @Bean + public OpenAPI customOpenAPI() { + String version = getClass().getPackage().getImplementationVersion(); + if (version == null) { + + version = "1.0.0"; // default version if all else fails + + } + + return new OpenAPI().components(new Components()).info( + new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); + } + + +} diff --git a/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java b/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java index a25cbbbf..77b69c88 100644 --- a/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java +++ b/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java @@ -1,20 +1,20 @@ -package stirling.software.SPDF.config; - - -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; - -@Component -public class StartupApplicationListener implements ApplicationListener { - - public static LocalDateTime startTime; - - @Override - public void onApplicationEvent(ContextRefreshedEvent event) { - startTime = LocalDateTime.now(); - } -} - +package stirling.software.SPDF.config; + + +import java.time.LocalDateTime; + +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +@Component +public class StartupApplicationListener implements ApplicationListener { + + public static LocalDateTime startTime; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + startTime = LocalDateTime.now(); + } +} + diff --git a/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java b/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java index 10a88e97..dca3f91c 100644 --- a/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java +++ b/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java @@ -1,27 +1,27 @@ -package stirling.software.SPDF.config; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebMvcConfig implements WebMvcConfigurer { - - @Autowired - private EndpointInterceptor endpointInterceptor; - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(endpointInterceptor); - } - - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - // Handler for external static resources - registry.addResourceHandler("/**") - .addResourceLocations("file:customFiles/static/", "classpath:/static/") - .setCachePeriod(0); // Optional: disable caching - } -} +package stirling.software.SPDF.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Autowired + private EndpointInterceptor endpointInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(endpointInterceptor); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // Handler for external static resources + registry.addResourceHandler("/**") + .addResourceLocations("file:customFiles/static/", "classpath:/static/"); + //.setCachePeriod(0); // Optional: disable caching + } +} diff --git a/src/main/java/stirling/software/SPDF/config/YamlPropertySourceFactory.java b/src/main/java/stirling/software/SPDF/config/YamlPropertySourceFactory.java new file mode 100644 index 00000000..982cee94 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/YamlPropertySourceFactory.java @@ -0,0 +1,23 @@ +package stirling.software.SPDF.config; + +import java.io.IOException; +import java.util.Properties; + +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.core.io.support.PropertySourceFactory; +public class YamlPropertySourceFactory implements PropertySourceFactory { + + @Override + public PropertySource createPropertySource(String name, EncodedResource encodedResource) + throws IOException { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(encodedResource.getResource()); + + Properties properties = factory.getObject(); + + return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties); + } +} \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java new file mode 100644 index 00000000..9f52a5e3 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java @@ -0,0 +1,26 @@ +package stirling.software.SPDF.config.security; + +import java.io.IOException; + +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) + throws IOException, ServletException { + if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) { + setDefaultFailureUrl("/login?error=badcredentials"); + } else if (exception.getClass().isAssignableFrom(LockedException.class)) { + setDefaultFailureUrl("/login?error=locked"); + } + super.onAuthenticationFailure(request, response, exception); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java b/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java new file mode 100644 index 00000000..7ae1680b --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java @@ -0,0 +1,45 @@ +package stirling.software.SPDF.config.security; + +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import stirling.software.SPDF.model.Authority; +import stirling.software.SPDF.model.User; +import stirling.software.SPDF.repository.UserRepository; + +@Service +public class CustomUserDetailsService implements UserDetailsService { + + @Autowired + private UserRepository userRepository; + + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("No user found with username: " + username)); + + return new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), + user.isEnabled(), + true, true, true, + getAuthorities(user.getAuthorities()) + ); + } + + private Collection getAuthorities(Set authorities) { + return authorities.stream() + .map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java b/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java new file mode 100644 index 00000000..8e612464 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java @@ -0,0 +1,53 @@ +package stirling.software.SPDF.config.security; + +import java.io.IOException; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +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 stirling.software.SPDF.model.User; + +@Component +public class FirstLoginFilter extends OncePerRequestFilter { + + @Autowired + @Lazy + private UserService userService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String method = request.getMethod(); + String requestURI = request.getRequestURI(); + // Check if the request is for static resources + boolean isStaticResource = requestURI.startsWith("/css/") + || requestURI.startsWith("/js/") + || requestURI.startsWith("/images/") + || requestURI.startsWith("/public/") + || requestURI.endsWith(".svg"); + + // If it's a static resource, just continue the filter chain and skip the logic below + if (isStaticResource) { + filterChain.doFilter(request, response); + return; + } + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + Optional user = userService.findByUsername(authentication.getName()); + if ("GET".equalsIgnoreCase(method) && user.isPresent() && user.get().isFirstLogin() && !"/change-creds".equals(requestURI)) { + response.sendRedirect("/change-creds"); + return; + } + } + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java new file mode 100644 index 00000000..842375d8 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java @@ -0,0 +1,85 @@ +package stirling.software.SPDF.config.security; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.Role; +@Component +public class InitialSecuritySetup { + + @Autowired + private UserService userService; + + + @Autowired + ApplicationProperties applicationProperties; + + @PostConstruct + public void init() { + if (!userService.hasUsers()) { + + + String initialUsername = applicationProperties.getSecurity().getInitialLogin().getUsername(); + String initialPassword = applicationProperties.getSecurity().getInitialLogin().getPassword(); + if (initialUsername != null && initialPassword != null) { + userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId()); + } else { + initialUsername = "admin"; + initialPassword = "stirling"; + userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId(), true); + } + + + } + } + + + + @PostConstruct + public void initSecretKey() throws IOException { + String secretKey = applicationProperties.getAutomaticallyGenerated().getKey(); + if (secretKey == null || secretKey.isEmpty()) { + secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key + saveKeyToConfig(secretKey); + } + } + + private void saveKeyToConfig(String key) throws IOException { + Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml + List lines = Files.readAllLines(path); + boolean keyFound = false; + + // Search for the existing key to replace it or place to add it + for (int i = 0; i < lines.size(); i++) { + if (lines.get(i).startsWith("AutomaticallyGenerated:")) { + keyFound = true; + if (i + 1 < lines.size() && lines.get(i + 1).trim().startsWith("key:")) { + lines.set(i + 1, " key: " + key); + break; + } else { + lines.add(i + 1, " key: " + key); + break; + } + } + } + + // If the section doesn't exist, append it + if (!keyFound) { + lines.add("# Automatically Generated Settings (Do Not Edit Directly)"); + lines.add("AutomaticallyGenerated:"); + lines.add(" key: " + key); + } + + // Write back to the file + Files.write(path, lines); + } +} \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java new file mode 100644 index 00000000..dfe782ac --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -0,0 +1,106 @@ +package stirling.software.SPDF.config.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import stirling.software.SPDF.repository.JPATokenRepositoryImpl; +@Configuration +@EnableWebSecurity() +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SecurityConfiguration { + + @Autowired + private UserDetailsService userDetailsService; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + @Autowired + @Lazy + private UserService userService; + + @Autowired + @Qualifier("loginEnabled") + public boolean loginEnabledValue; + + @Autowired + private UserAuthenticationFilter userAuthenticationFilter; + + @Autowired + private FirstLoginFilter firstLoginFilter; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + if(loginEnabledValue) { + + http.csrf(csrf -> csrf.disable()); + http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); + http + .formLogin(formLogin -> formLogin + .loginPage("/login") + .defaultSuccessUrl("/") + .failureHandler(new CustomAuthenticationFailureHandler()) + .permitAll() + ) + .logout(logout -> logout + .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + .logoutSuccessUrl("/login?logout=true") + .invalidateHttpSession(true) // Invalidate session + .deleteCookies("JSESSIONID", "remember-me") + ).rememberMe(rememberMeConfigurer -> rememberMeConfigurer // Use the configurator directly + .key("uniqueAndSecret") + .tokenRepository(persistentTokenRepository()) + .tokenValiditySeconds(1209600) // 2 weeks + ) + .authorizeHttpRequests(authz -> authz + .requestMatchers(req -> req.getRequestURI().startsWith("/login") || req.getRequestURI().endsWith(".svg") || req.getRequestURI().startsWith("/register") || req.getRequestURI().startsWith("/error") || req.getRequestURI().startsWith("/images/") || req.getRequestURI().startsWith("/public/") || req.getRequestURI().startsWith("/css/") || req.getRequestURI().startsWith("/js/")) + .permitAll() + .anyRequest().authenticated() + ) + .userDetailsService(userDetailsService) + .authenticationProvider(authenticationProvider()); + } else { + http.csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(authz -> authz + .anyRequest().permitAll() + ); + } + return http.build(); + } + + + + @Bean + public DaoAuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + @Bean + public PersistentTokenRepository persistentTokenRepository() { + return new JPATokenRepositoryImpl(); + } + + + +} + diff --git a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java new file mode 100644 index 00000000..eca7f70e --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java @@ -0,0 +1,113 @@ +package stirling.software.SPDF.config.security; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; +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 stirling.software.SPDF.model.ApiKeyAuthenticationToken; +@Component +public class UserAuthenticationFilter extends OncePerRequestFilter { + + @Autowired + private UserDetailsService userDetailsService; + + @Autowired + @Lazy + private UserService userService; + + + @Autowired + @Qualifier("loginEnabled") + public boolean loginEnabledValue; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + if (!loginEnabledValue) { + // If login is not enabled, just pass all requests without authentication + filterChain.doFilter(request, response); + return; + } + String requestURI = request.getRequestURI(); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + // Check for API key in the request headers if no authentication exists + if (authentication == null || !authentication.isAuthenticated()) { + String apiKey = request.getHeader("X-API-Key"); + if (apiKey != null && !apiKey.trim().isEmpty()) { + try { + // Use API key to authenticate. This requires you to have an authentication provider for API keys. + UserDetails userDetails = userService.loadUserByApiKey(apiKey); + if(userDetails == null) + { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter().write("Invalid API Key."); + return; + } + authentication = new ApiKeyAuthenticationToken(userDetails, apiKey, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } catch (AuthenticationException e) { + // If API key authentication fails, deny the request + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter().write("Invalid API Key."); + return; + } + } + } + + // If we still don't have any authentication, deny the request + if (authentication == null || !authentication.isAuthenticated()) { + String method = request.getMethod(); + if ("GET".equalsIgnoreCase(method) && !"/login".equals(requestURI)) { + response.sendRedirect("/login"); // redirect to the login page + return; + } else { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter().write("Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected"); + return; + } + } + + filterChain.doFilter(request, response); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + String uri = request.getRequestURI(); + + String[] permitAllPatterns = { + "/login", + "/register", + "/error", + "/images/", + "/public/", + "/css/", + "/js/" + }; + + for (String pattern : permitAllPatterns) { + if (uri.startsWith(pattern) || uri.endsWith(".svg")) { + return true; + } + } + + return false; + } + +} diff --git a/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java new file mode 100644 index 00000000..f23e5ce3 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java @@ -0,0 +1,125 @@ +package stirling.software.SPDF.config.security; + +import java.io.IOException; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Bucket; +import io.github.bucket4j.ConsumptionProbe; +import io.github.bucket4j.Refill; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import stirling.software.SPDF.model.Role; +@Component +public class UserBasedRateLimitingFilter extends OncePerRequestFilter { + + private final Map apiBuckets = new ConcurrentHashMap<>(); + private final Map webBuckets = new ConcurrentHashMap<>(); + + @Autowired + private UserDetailsService userDetailsService; + + @Autowired + @Qualifier("rateLimit") + public boolean rateLimit; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + if (!rateLimit) { + // If rateLimit is not enabled, just pass all requests without rate limiting + filterChain.doFilter(request, response); + return; + } + + String method = request.getMethod(); + if (!"POST".equalsIgnoreCase(method)) { + // If the request is not a POST, just pass it through without rate limiting + filterChain.doFilter(request, response); + return; + } + + String identifier = null; + + // Check for API key in the request headers + String apiKey = request.getHeader("X-API-Key"); + if (apiKey != null && !apiKey.trim().isEmpty()) { + identifier = "API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames + } else { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + identifier = userDetails.getUsername(); + } + } + + // If neither API key nor an authenticated user is present, use IP address + if (identifier == null) { + identifier = request.getRemoteAddr(); + } + + Role userRole = getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication()); + + if (request.getHeader("X-API-Key") != null) { + // It's an API call + processRequest(userRole.getApiCallsPerDay(), identifier, apiBuckets, request, response, filterChain); + } else { + // It's a Web UI call + processRequest(userRole.getWebCallsPerDay(), identifier, webBuckets, request, response, filterChain); + } + } + + private Role getRoleFromAuthentication(Authentication authentication) { + if (authentication != null && authentication.isAuthenticated()) { + for (GrantedAuthority authority : authentication.getAuthorities()) { + try { + return Role.fromString(authority.getAuthority()); + } catch (IllegalArgumentException ex) { + // Ignore and continue to next authority. + } + } + } + throw new IllegalStateException("User does not have a valid role."); + } + + private void processRequest(int limitPerDay, String identifier, Map buckets, + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws IOException, ServletException { + Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay)); + ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1); + + if (probe.isConsumed()) { + response.setHeader("X-Rate-Limit-Remaining", Long.toString(probe.getRemainingTokens())); + filterChain.doFilter(request, response); + } else { + long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000; + response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); + response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill)); + response.getWriter().write("Rate limit exceeded for POST requests."); + } + } + + private Bucket createUserBucket(int limitPerDay) { + Bandwidth limit = Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1))); + return Bucket.builder().addLimit(limit).build(); + } +} + + + diff --git a/src/main/java/stirling/software/SPDF/config/security/UserService.java b/src/main/java/stirling/software/SPDF/config/security/UserService.java new file mode 100644 index 00000000..46c5aeff --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/UserService.java @@ -0,0 +1,191 @@ +package stirling.software.SPDF.config.security; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import stirling.software.SPDF.model.Authority; +import stirling.software.SPDF.model.User; +import stirling.software.SPDF.repository.UserRepository; +@Service +public class UserService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + public Authentication getAuthentication(String apiKey) { + User user = getUserByApiKey(apiKey); + if (user == null) { + throw new UsernameNotFoundException("API key is not valid"); + } + + // Convert the user into an Authentication object + return new UsernamePasswordAuthenticationToken( + user, // principal (typically the user) + null, // credentials (we don't expose the password or API key here) + getAuthorities(user) // user's authorities (roles/permissions) + ); + } + + private Collection getAuthorities(User user) { + // Convert each Authority object into a SimpleGrantedAuthority object. + return user.getAuthorities().stream() + .map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority())) + .collect(Collectors.toList()); + + + } + + private String generateApiKey() { + String apiKey; + do { + apiKey = UUID.randomUUID().toString(); + } while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness + return apiKey; + } + + public User addApiKeyToUser(String username) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + + user.setApiKey(generateApiKey()); + return userRepository.save(user); + } + + public User refreshApiKeyForUser(String username) { + return addApiKeyToUser(username); // reuse the add API key method for refreshing + } + + public String getApiKeyForUser(String username) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + return user.getApiKey(); + } + + public boolean isValidApiKey(String apiKey) { + return userRepository.findByApiKey(apiKey) != null; + } + + public User getUserByApiKey(String apiKey) { + return userRepository.findByApiKey(apiKey); + } + + public UserDetails loadUserByApiKey(String apiKey) { + User userOptional = userRepository.findByApiKey(apiKey); + if (userOptional != null) { + User user = userOptional; + // Convert your User entity to a UserDetails object with authorities + return new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), // you might not need this for API key auth + getAuthorities(user) + ); + } + return null; // or throw an exception + } + + + public boolean validateApiKeyForUser(String username, String apiKey) { + Optional userOpt = userRepository.findByUsername(username); + return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey); + } + + public void saveUser(String username, String password) { + User user = new User(); + user.setUsername(username); + user.setPassword(passwordEncoder.encode(password)); + user.setEnabled(true); + userRepository.save(user); + } + + public void saveUser(String username, String password, String role, boolean firstLogin) { + User user = new User(); + user.setUsername(username); + user.setPassword(passwordEncoder.encode(password)); + user.addAuthority(new Authority(role, user)); + user.setEnabled(true); + user.setFirstLogin(firstLogin); + userRepository.save(user); + } + + public void saveUser(String username, String password, String role) { + User user = new User(); + user.setUsername(username); + user.setPassword(passwordEncoder.encode(password)); + user.addAuthority(new Authority(role, user)); + user.setEnabled(true); + user.setFirstLogin(false); + userRepository.save(user); + } + + public void deleteUser(String username) { + Optional userOpt = userRepository.findByUsername(username); + if (userOpt.isPresent()) { + userRepository.delete(userOpt.get()); + } + } + + public boolean usernameExists(String username) { + return userRepository.findByUsername(username).isPresent(); + } + + public boolean hasUsers() { + return userRepository.count() > 0; + } + + public void updateUserSettings(String username, Map updates) { + Optional userOpt = userRepository.findByUsername(username); + if (userOpt.isPresent()) { + User user = userOpt.get(); + Map settingsMap = user.getSettings(); + + if(settingsMap == null) { + settingsMap = new HashMap(); + } + settingsMap.clear(); + settingsMap.putAll(updates); + user.setSettings(settingsMap); + + userRepository.save(user); + } + } + + public Optional findByUsername(String username) { + return userRepository.findByUsername(username); + } + + public void changeUsername(User user, String newUsername) { + user.setUsername(newUsername); + userRepository.save(user); + } + + public void changePassword(User user, String newPassword) { + user.setPassword(passwordEncoder.encode(newPassword)); + userRepository.save(user); + } + + public void changeFirstUse(User user, boolean firstUse) { + user.setFirstLogin(firstUse); + userRepository.save(user); + } + + + public boolean isPasswordCorrect(User user, String currentPassword) { + return passwordEncoder.matches(currentPassword, user.getPassword()); + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/CropController.java b/src/main/java/stirling/software/SPDF/controller/api/CropController.java index 670ef39e..d2723cce 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/CropController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/CropController.java @@ -1,132 +1,86 @@ package stirling.software.SPDF.controller.api; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDPage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import stirling.software.SPDF.utils.GeneralUtils; -import stirling.software.SPDF.utils.WebResponseUtils; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import org.apache.pdfbox.multipdf.LayerUtility; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; -import com.itextpdf.kernel.geom.PageSize; -import com.itextpdf.kernel.geom.Rectangle; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfPage; -import com.itextpdf.kernel.pdf.PdfReader; -import com.itextpdf.kernel.pdf.PdfWriter; -import com.itextpdf.kernel.pdf.canvas.PdfCanvas; -import com.itextpdf.kernel.pdf.canvas.parser.EventType; -import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor; -import com.itextpdf.kernel.pdf.canvas.parser.data.IEventData; -import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo; -import com.itextpdf.kernel.pdf.canvas.parser.listener.IEventListener; -import com.itextpdf.kernel.pdf.xobject.PdfFormXObject; - -import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.general.CropPdfForm; import stirling.software.SPDF.utils.WebResponseUtils; @RestController +@RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class CropController { - private static final Logger logger = LoggerFactory.getLogger(CropController.class); - + private static final Logger logger = LoggerFactory.getLogger(CropController.class); @PostMapping(value = "/crop", consumes = "multipart/form-data") @Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO") - public ResponseEntity cropPdf( - @Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file, - @Parameter(description = "The x-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("x") float x, - @Parameter(description = "The y-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("y") float y, - @Parameter(description = "The width of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("width") float width, - @Parameter(description = "The height of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("height") float height) throws IOException { - byte[] bytes = file.getBytes(); - System.out.println("x=" + x + ", " + "y=" + y + ", " + "width=" + width + ", " +"height=" + height ); - PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes)); - PdfDocument pdfDoc = new PdfDocument(reader); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PdfWriter writer = new PdfWriter(baos); - PdfDocument outputPdf = new PdfDocument(writer); - - int totalPages = pdfDoc.getNumberOfPages(); - - for (int i = 1; i <= totalPages; i++) { - PdfPage page = outputPdf.addNewPage(new PageSize(width, height)); - PdfCanvas pdfCanvas = new PdfCanvas(page); + public ResponseEntity cropPdf(@ModelAttribute CropPdfForm form) + throws IOException { - PdfFormXObject formXObject = pdfDoc.getPage(i).copyAsFormXObject(outputPdf); - // Save the graphics state, apply the transformations, add the object, and then - // restore the graphics state - pdfCanvas.saveState(); - pdfCanvas.rectangle(x, y, width, height); - pdfCanvas.clip(); - pdfCanvas.addXObject(formXObject, -x, -y); - pdfCanvas.restoreState(); - } - + - outputPdf.close(); - byte[] pdfContent = baos.toByteArray(); - pdfDoc.close(); - return WebResponseUtils.bytesToWebResponse(pdfContent, - file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf"); +PDDocument sourceDocument = PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes())); + +PDDocument newDocument = new PDDocument(); + +int totalPages = sourceDocument.getNumberOfPages(); + +LayerUtility layerUtility = new LayerUtility(newDocument); + +for (int i = 0; i < totalPages; i++) { + PDPage sourcePage = sourceDocument.getPage(i); + + // Create a new page with the size of the source page + PDPage newPage = new PDPage(sourcePage.getMediaBox()); + newDocument.addPage(newPage); + PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage); + + // Import the source page as a form XObject + PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i); + + contentStream.saveGraphicsState(); + + // Define the crop area + contentStream.addRect(form.getX(), form.getY(), form.getWidth(), form.getHeight()); + contentStream.clip(); + + // Draw the entire formXObject + contentStream.drawForm(formXObject); + + contentStream.restoreGraphicsState(); + + contentStream.close(); + + // Now, set the new page's media box to the cropped size + newPage.setMediaBox(new PDRectangle(form.getX(), form.getY(), form.getWidth(), form.getHeight())); +} + +ByteArrayOutputStream baos = new ByteArrayOutputStream(); +newDocument.save(baos); +newDocument.close(); +sourceDocument.close(); + +byte[] pdfContent = baos.toByteArray(); +return WebResponseUtils.bytesToWebResponse(pdfContent, form.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf"); } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java index 7639d154..4727e195 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -1,80 +1,115 @@ package stirling.software.SPDF.controller.api; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDPageTree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.general.MergePdfsRequest; import stirling.software.SPDF.utils.WebResponseUtils; @RestController +@RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class MergeController { private static final Logger logger = LoggerFactory.getLogger(MergeController.class); - private PDDocument mergeDocuments(List documents) throws IOException { - // Create a new empty document - PDDocument mergedDoc = new PDDocument(); - // Iterate over the list of documents and add their pages to the merged document - for (PDDocument doc : documents) { - // Get all pages from the current document - PDPageTree pages = doc.getPages(); - // Iterate over the pages and add them to the merged document - for (PDPage page : pages) { - mergedDoc.addPage(page); - } - - +private PDDocument mergeDocuments(List documents) throws IOException { + PDDocument mergedDoc = new PDDocument(); + for (PDDocument doc : documents) { + for (PDPage page : doc.getPages()) { + mergedDoc.addPage(page); } + } + return mergedDoc; +} - // Return the merged document - return mergedDoc; +private Comparator getSortComparator(String sortType) { + switch (sortType) { + case "byFileName": + return Comparator.comparing(MultipartFile::getOriginalFilename); + case "byDateModified": + return (file1, file2) -> { + try { + BasicFileAttributes attr1 = Files.readAttributes(Paths.get(file1.getOriginalFilename()), BasicFileAttributes.class); + BasicFileAttributes attr2 = Files.readAttributes(Paths.get(file2.getOriginalFilename()), BasicFileAttributes.class); + return attr1.lastModifiedTime().compareTo(attr2.lastModifiedTime()); + } catch (IOException e) { + return 0; // If there's an error, treat them as equal + } + }; + case "byDateCreated": + return (file1, file2) -> { + try { + BasicFileAttributes attr1 = Files.readAttributes(Paths.get(file1.getOriginalFilename()), BasicFileAttributes.class); + BasicFileAttributes attr2 = Files.readAttributes(Paths.get(file2.getOriginalFilename()), BasicFileAttributes.class); + return attr1.creationTime().compareTo(attr2.creationTime()); + } catch (IOException e) { + return 0; // If there's an error, treat them as equal + } + }; + case "byPDFTitle": + return (file1, file2) -> { + try (PDDocument doc1 = PDDocument.load(file1.getInputStream()); + PDDocument doc2 = PDDocument.load(file2.getInputStream())) { + String title1 = doc1.getDocumentInformation().getTitle(); + String title2 = doc2.getDocumentInformation().getTitle(); + return title1.compareTo(title2); + } catch (IOException e) { + return 0; + } + }; + case "orderProvided": + default: + return (file1, file2) -> 0; // Default is the order provided + } +} + +@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs") +@Operation(summary = "Merge multiple PDF files into one", + description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO") +public ResponseEntity mergePdfs(@ModelAttribute MergePdfsRequest form) throws IOException { + + MultipartFile[] files = form.getFileInput(); + Arrays.sort(files, getSortComparator(form.getSortType())); + + List documents = new ArrayList<>(); + for (MultipartFile file : files) { + try (InputStream is = file.getInputStream()) { + documents.add(PDDocument.load(is)); + } } - @PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs") - @Operation( - summary = "Merge multiple PDF files into one", - description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO" - ) - public ResponseEntity mergePdfs( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF files to be merged into a single file", required = true) - MultipartFile[] files) throws IOException { - // Read the input PDF files into PDDocument objects - List documents = new ArrayList<>(); - - // Loop through the files array and read each file into a PDDocument - for (MultipartFile file : files) { - documents.add(PDDocument.load(file.getInputStream())); - } - - PDDocument mergedDoc = mergeDocuments(documents); - - - // Return the merged PDF as a response + try (PDDocument mergedDoc = mergeDocuments(documents)) { ResponseEntity response = WebResponseUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf"); - - for (PDDocument doc : documents) { - // Close the document after processing - doc.close(); - } - return response; + } finally { + for (PDDocument doc : documents) { + if (doc != null) { + doc.close(); + } + } } +} } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java b/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java index 9bf0a3d7..ebe81ffd 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java @@ -1,101 +1,124 @@ package stirling.software.SPDF.controller.api; -import java.io.ByteArrayInputStream; + +import java.awt.Color; import java.io.ByteArrayOutputStream; import java.io.IOException; +import org.apache.pdfbox.multipdf.LayerUtility; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.apache.pdfbox.util.Matrix; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import com.itextpdf.kernel.geom.PageSize; -import com.itextpdf.kernel.geom.Rectangle; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfPage; -import com.itextpdf.kernel.pdf.PdfReader; -import com.itextpdf.kernel.pdf.PdfWriter; -import com.itextpdf.kernel.pdf.canvas.PdfCanvas; -import com.itextpdf.kernel.pdf.xobject.PdfFormXObject; - import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest; import stirling.software.SPDF.utils.WebResponseUtils; @RestController +@RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class MultiPageLayoutController { private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class); @PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data") - @Operation(summary = "Merge multiple pages of a PDF document into a single page", description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO") - public ResponseEntity mergeMultiplePagesIntoOne( - @Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file, - @Parameter(description = "The number of pages to fit onto a single sheet in the output PDF. Acceptable values are 2, 3, 4, 9, 16.", required = true, schema = @Schema(type = "integer", allowableValues = { - "2", "3", "4", "9", "16" })) @RequestParam("pagesPerSheet") int pagesPerSheet) - throws IOException { + @Operation( + summary = "Merge multiple pages of a PDF document into a single page", + description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO" + ) + public ResponseEntity mergeMultiplePagesIntoOne(@ModelAttribute MergeMultiplePagesRequest request) + throws IOException { - if (pagesPerSheet != 2 && pagesPerSheet != 3 - && pagesPerSheet != (int) Math.sqrt(pagesPerSheet) * Math.sqrt(pagesPerSheet)) { - throw new IllegalArgumentException("pagesPerSheet must be 2, 3 or a perfect square"); - } + int pagesPerSheet = request.getPagesPerSheet(); + MultipartFile file = request.getFileInput(); + boolean addBorder = request.isAddBorder(); + + if (pagesPerSheet != 2 && pagesPerSheet != 3 && pagesPerSheet != (int) Math.sqrt(pagesPerSheet) * Math.sqrt(pagesPerSheet)) { + throw new IllegalArgumentException("pagesPerSheet must be 2, 3 or a perfect square"); + } - int cols = pagesPerSheet == 2 || pagesPerSheet == 3 ? pagesPerSheet : (int) Math.sqrt(pagesPerSheet); - int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet); + int cols = pagesPerSheet == 2 || pagesPerSheet == 3 ? pagesPerSheet : (int) Math.sqrt(pagesPerSheet); + int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet); - byte[] bytes = file.getBytes(); - PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes)); - PdfDocument pdfDoc = new PdfDocument(reader); + PDDocument sourceDocument = PDDocument.load(file.getInputStream()); + PDDocument newDocument = new PDDocument(); + PDPage newPage = new PDPage(PDRectangle.A4); + newDocument.addPage(newPage); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PdfWriter writer = new PdfWriter(baos); - PdfDocument outputPdf = new PdfDocument(writer); - PageSize pageSize = new PageSize(PageSize.A4.rotate()); + int totalPages = sourceDocument.getNumberOfPages(); + float cellWidth = newPage.getMediaBox().getWidth() / cols; + float cellHeight = newPage.getMediaBox().getHeight() / rows; - int totalPages = pdfDoc.getNumberOfPages(); - float cellWidth = pageSize.getWidth() / cols; - float cellHeight = pageSize.getHeight() / rows; + PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true); + LayerUtility layerUtility = new LayerUtility(newDocument); - for (int i = 1; i <= totalPages; i += pagesPerSheet) { - PdfPage page = outputPdf.addNewPage(pageSize); - PdfCanvas pdfCanvas = new PdfCanvas(page); + float borderThickness = 1.5f; // Specify border thickness as required + contentStream.setLineWidth(borderThickness); + contentStream.setStrokingColor(Color.BLACK); + + for (int i = 0; i < totalPages; i++) { + if (i != 0 && i % pagesPerSheet == 0) { + // Close the current content stream and create a new page and content stream + contentStream.close(); + newPage = new PDPage(PDRectangle.A4); + newDocument.addPage(newPage); + contentStream = new PDPageContentStream(newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true); + } - for (int row = 0; row < rows; row++) { - for (int col = 0; col < cols; col++) { - int index = i + row * cols + col; - if (index <= totalPages) { - // Get the page and calculate scaling factors - Rectangle rect = pdfDoc.getPage(index).getPageSize(); - float scaleWidth = cellWidth / rect.getWidth(); - float scaleHeight = cellHeight / rect.getHeight(); - float scale = Math.min(scaleWidth, scaleHeight); + PDPage sourcePage = sourceDocument.getPage(i); + PDRectangle rect = sourcePage.getMediaBox(); + float scaleWidth = cellWidth / rect.getWidth(); + float scaleHeight = cellHeight / rect.getHeight(); + float scale = Math.min(scaleWidth, scaleHeight); - PdfFormXObject formXObject = pdfDoc.getPage(index).copyAsFormXObject(outputPdf); - float x = col * cellWidth + (cellWidth - rect.getWidth() * scale) / 2; - float y = (rows - 1 - row) * cellHeight + (cellHeight - rect.getHeight() * scale) / 2; + int adjustedPageIndex = i % pagesPerSheet; // This will reset the index for every new page + int rowIndex = adjustedPageIndex / cols; + int colIndex = adjustedPageIndex % cols; - // Save the graphics state, apply the transformations, add the object, and then - // restore the graphics state - pdfCanvas.saveState(); - pdfCanvas.concatMatrix(scale, 0, 0, scale, x, y); - pdfCanvas.addXObject(formXObject, 0, 0); - pdfCanvas.restoreState(); - } - } - } - } + float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2; + float y = newPage.getMediaBox().getHeight() - ((rowIndex + 1) * cellHeight - (cellHeight - rect.getHeight() * scale) / 2); - outputPdf.close(); - byte[] pdfContent = baos.toByteArray(); - pdfDoc.close(); - - return WebResponseUtils.bytesToWebResponse(pdfContent, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_layoutChanged.pdf"); + contentStream.saveGraphicsState(); + contentStream.transform(Matrix.getTranslateInstance(x, y)); + contentStream.transform(Matrix.getScaleInstance(scale, scale)); + + PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i); + contentStream.drawForm(formXObject); + + contentStream.restoreGraphicsState(); + + if(addBorder) { + // Draw border around each page + float borderX = colIndex * cellWidth; + float borderY = newPage.getMediaBox().getHeight() - (rowIndex + 1) * cellHeight; + contentStream.addRect(borderX, borderY, cellWidth, cellHeight); + contentStream.stroke(); + } + } + + + contentStream.close(); // Close the final content stream + sourceDocument.close(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + newDocument.save(baos); + newDocument.close(); + + byte[] result = baos.toByteArray(); + return WebResponseUtils.bytesToWebResponse(result, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_layoutChanged.pdf"); } + } diff --git a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java index d9cb2686..b76b6cee 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java @@ -9,7 +9,9 @@ import org.apache.pdfbox.pdmodel.PDPage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; @@ -17,12 +19,14 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.SortTypes; +import stirling.software.SPDF.model.api.PDFWithPageNums; +import stirling.software.SPDF.model.api.general.RearrangePagesRequest; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.WebResponseUtils; - @RestController +@RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class RearrangePagesPDFController { @@ -30,11 +34,12 @@ public class RearrangePagesPDFController { @PostMapping(consumes = "multipart/form-data", value = "/remove-pages") @Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete. Input:PDF Output:PDF Type:SISO") - public ResponseEntity deletePages( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file from which pages will be removed") MultipartFile pdfFile, - @RequestParam("pagesToDelete") @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") String pagesToDelete) + public ResponseEntity deletePages(@ModelAttribute PDFWithPageNums request ) throws IOException { + MultipartFile pdfFile = request.getFileInput(); + String pagesToDelete = request.getPageNumbers(); + PDDocument document = PDDocument.load(pdfFile.getBytes()); // Split the page order string into an array of page numbers or range of numbers @@ -51,9 +56,7 @@ public class RearrangePagesPDFController { } - private enum CustomMode { - REVERSE_ORDER, DUPLEX_SORT, BOOKLET_SORT, ODD_EVEN_SPLIT, REMOVE_FIRST, REMOVE_LAST, REMOVE_FIRST_AND_LAST, - } + private List removeFirst(int totalPages) { if (totalPages <= 1) @@ -114,6 +117,18 @@ public class RearrangePagesPDFController { return newPageOrder; } + private List sideStitchBooklet(int totalPages) { + List newPageOrder = new ArrayList<>(); + for (int i = 0; i < (totalPages + 3) / 4; i++) { + int begin = i * 4; + newPageOrder.add(Math.min(begin + 3, totalPages - 1)); + newPageOrder.add(Math.min(begin, totalPages - 1)); + newPageOrder.add(Math.min(begin + 1, totalPages - 1)); + newPageOrder.add(Math.min(begin + 2, totalPages - 1)); + } + return newPageOrder; + } + private List oddEvenSplit(int totalPages) { List newPageOrder = new ArrayList<>(); for (int i = 1; i <= totalPages; i += 2) { @@ -125,9 +140,9 @@ public class RearrangePagesPDFController { return newPageOrder; } - private List processCustomMode(String customMode, int totalPages) { + private List processSortTypes(String sortTypes, int totalPages) { try { - CustomMode mode = CustomMode.valueOf(customMode.toUpperCase()); + SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase()); switch (mode) { case REVERSE_ORDER: return reverseOrder(totalPages); @@ -135,6 +150,8 @@ public class RearrangePagesPDFController { return duplexSort(totalPages); case BOOKLET_SORT: return bookletSort(totalPages); + case SIDE_STITCH_BOOKLET_SORT: + return sideStitchBooklet(totalPages); case ODD_EVEN_SPLIT: return oddEvenSplit(totalPages); case REMOVE_FIRST: @@ -154,16 +171,10 @@ public class RearrangePagesPDFController { @PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages") @Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode. Input:PDF Output:PDF") - public ResponseEntity rearrangePages( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to rearrange pages") MultipartFile pdfFile, - @RequestParam(required = false, value = "pageOrder") @Parameter(description = "The new page order as a comma-separated list of page numbers, page ranges (e.g., '1,3,5-7'), or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')") String pageOrder, - @RequestParam(required = false, value = "customMode") @Parameter(schema = @Schema(implementation = CustomMode.class, description = "The custom mode for page rearrangement. " - + "Valid values are:\n" + "REVERSE_ORDER: Reverses the order of all pages.\n" - + "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). " - + "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n" - + "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n" - + "REMOVE_FIRST: Removes the first page.\n" + "REMOVE_LAST: Removes the last page.\n" - + "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n")) String customMode) { + public ResponseEntity rearrangePages(@ModelAttribute RearrangePagesRequest request) throws IOException { + MultipartFile pdfFile = request.getFileInput(); + String pageOrder = request.getPageNumbers(); + String sortType = request.getCustomMode(); try { // Load the input PDF PDDocument document = PDDocument.load(pdfFile.getInputStream()); @@ -171,15 +182,14 @@ public class RearrangePagesPDFController { // Split the page order string into an array of page numbers or range of numbers String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; int totalPages = document.getNumberOfPages(); - System.out.println("pageOrder=" + pageOrder); - System.out.println("customMode length =" + customMode.length()); List newPageOrder; - if (customMode != null && customMode.length() > 0) { - newPageOrder = processCustomMode(customMode, totalPages); + if (sortType != null && sortType.length() > 0) { + newPageOrder = processSortTypes(sortType, totalPages); } else { newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages); } - + logger.info("newPageOrder = " +newPageOrder); + logger.info("totalPages = " +totalPages); // Create a new list to hold the pages in the new order List newPages = new ArrayList<>(); for (int i = 0; i < newPageOrder.size(); i++) { diff --git a/src/main/java/stirling/software/SPDF/controller/api/RotationController.java b/src/main/java/stirling/software/SPDF/controller/api/RotationController.java index 916d2afb..ed527549 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RotationController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RotationController.java @@ -8,18 +8,19 @@ import org.apache.pdfbox.pdmodel.PDPageTree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.general.RotatePDFRequest; import stirling.software.SPDF.utils.WebResponseUtils; @RestController +@RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class RotationController { @@ -31,13 +32,9 @@ public class RotationController { description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90. Input:PDF Output:PDF Type:SISO" ) public ResponseEntity rotatePDF( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The PDF file to be rotated", required = true) - MultipartFile pdfFile, - @RequestParam("angle") - @Parameter(description = "The angle by which to rotate the PDF file. This should be a multiple of 90.", example = "90", required = true) - Integer angle) throws IOException { - + @ModelAttribute RotatePDFRequest request) throws IOException { + MultipartFile pdfFile = request.getFileInput(); + Integer angle = request.getAngle(); // Load the PDF document PDDocument document = PDDocument.load(pdfFile.getBytes()); diff --git a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java index e3b4434a..743ea6b1 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java @@ -1,48 +1,32 @@ package stirling.software.SPDF.controller.api; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Set; +import org.apache.pdfbox.multipdf.LayerUtility; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.apache.pdfbox.util.Matrix; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import com.itextpdf.kernel.geom.PageSize; -import com.itextpdf.kernel.geom.Rectangle; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfPage; -import com.itextpdf.kernel.pdf.PdfReader; -import com.itextpdf.kernel.pdf.PdfWriter; -import com.itextpdf.kernel.pdf.canvas.PdfCanvas; -import com.itextpdf.kernel.pdf.canvas.parser.EventType; -import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor; -import com.itextpdf.kernel.pdf.canvas.parser.data.IEventData; -import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo; -import com.itextpdf.kernel.pdf.canvas.parser.listener.IEventListener; -import com.itextpdf.kernel.pdf.xobject.PdfFormXObject; - -import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.general.ScalePagesRequest; import stirling.software.SPDF.utils.WebResponseUtils; - @RestController +@RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class ScalePagesController { @@ -50,194 +34,77 @@ public class ScalePagesController { @PostMapping(value = "/scale-pages", consumes = "multipart/form-data") @Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file. Input:PDF Output:PDF Type:SISO") - public ResponseEntity scalePages( - @Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file, - @Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "string", allowableValues = { - "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "B0", "B1", "B2", "B3", "B4", - "B5", "B6", "B7", "B8", "B9", "LETTER", "TABLOID", "LEDGER", "LEGAL", - "EXECUTIVE" })) @RequestParam("pageSize") String targetPageSize, - @Parameter(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.", required = true, schema = @Schema(type = "integer")) @RequestParam("scaleFactor") float scaleFactor) - throws IOException { + public ResponseEntity scalePages(@ModelAttribute ScalePagesRequest request) throws IOException { + MultipartFile file = request.getFileInput(); + String targetPDRectangle = request.getPageSize(); + float scaleFactor = request.getScaleFactor(); - Map sizeMap = new HashMap<>(); + Map sizeMap = new HashMap<>(); // Add A0 - A10 - sizeMap.put("A0", PageSize.A0); - sizeMap.put("A1", PageSize.A1); - sizeMap.put("A2", PageSize.A2); - sizeMap.put("A3", PageSize.A3); - sizeMap.put("A4", PageSize.A4); - sizeMap.put("A5", PageSize.A5); - sizeMap.put("A6", PageSize.A6); - sizeMap.put("A7", PageSize.A7); - sizeMap.put("A8", PageSize.A8); - sizeMap.put("A9", PageSize.A9); - sizeMap.put("A10", PageSize.A10); - // Add B0 - B9 - sizeMap.put("B0", PageSize.B0); - sizeMap.put("B1", PageSize.B1); - sizeMap.put("B2", PageSize.B2); - sizeMap.put("B3", PageSize.B3); - sizeMap.put("B4", PageSize.B4); - sizeMap.put("B5", PageSize.B5); - sizeMap.put("B6", PageSize.B6); - sizeMap.put("B7", PageSize.B7); - sizeMap.put("B8", PageSize.B8); - sizeMap.put("B9", PageSize.B9); + sizeMap.put("A0", PDRectangle.A0); + sizeMap.put("A1", PDRectangle.A1); + sizeMap.put("A2", PDRectangle.A2); + sizeMap.put("A3", PDRectangle.A3); + sizeMap.put("A4", PDRectangle.A4); + sizeMap.put("A5", PDRectangle.A5); + sizeMap.put("A6", PDRectangle.A6); + // Add other sizes - sizeMap.put("LETTER", PageSize.LETTER); - sizeMap.put("TABLOID", PageSize.TABLOID); - sizeMap.put("LEDGER", PageSize.LEDGER); - sizeMap.put("LEGAL", PageSize.LEGAL); - sizeMap.put("EXECUTIVE", PageSize.EXECUTIVE); + sizeMap.put("LETTER", PDRectangle.LETTER); + sizeMap.put("LEGAL", PDRectangle.LEGAL); - if (!sizeMap.containsKey(targetPageSize)) { + if (!sizeMap.containsKey(targetPDRectangle)) { throw new IllegalArgumentException( - "Invalid pageSize. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10"); + "Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10"); } - PageSize pageSize = sizeMap.get(targetPageSize); + PDRectangle targetSize = sizeMap.get(targetPDRectangle); - byte[] bytes = file.getBytes(); - PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes)); - PdfDocument pdfDoc = new PdfDocument(reader); + PDDocument sourceDocument = PDDocument.load(file.getBytes()); + PDDocument outputDocument = new PDDocument(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PdfWriter writer = new PdfWriter(baos); - PdfDocument outputPdf = new PdfDocument(writer); + int totalPages = sourceDocument.getNumberOfPages(); + for (int i = 0; i < totalPages; i++) { + PDPage sourcePage = sourceDocument.getPage(i); + PDRectangle sourceSize = sourcePage.getMediaBox(); + + float scaleWidth = targetSize.getWidth() / sourceSize.getWidth(); + float scaleHeight = targetSize.getHeight() / sourceSize.getHeight(); + float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor; + + PDPage newPage = new PDPage(targetSize); + outputDocument.addPage(newPage); + + PDPageContentStream contentStream = new PDPageContentStream(outputDocument, newPage, PDPageContentStream.AppendMode.APPEND, true); + + float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2; + float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2; + + contentStream.saveGraphicsState(); + contentStream.transform(Matrix.getTranslateInstance(x, y)); + contentStream.transform(Matrix.getScaleInstance(scale, scale)); + + LayerUtility layerUtility = new LayerUtility(outputDocument); + PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, i); + contentStream.drawForm(form); - int totalPages = pdfDoc.getNumberOfPages(); + contentStream.restoreGraphicsState(); + contentStream.close(); + } - for (int i = 1; i <= totalPages; i++) { - PdfPage page = outputPdf.addNewPage(pageSize); - PdfCanvas pdfCanvas = new PdfCanvas(page); - // Get the page and calculate scaling factors - Rectangle rect = pdfDoc.getPage(i).getPageSize(); - float scaleWidth = pageSize.getWidth() / rect.getWidth(); - float scaleHeight = pageSize.getHeight() / rect.getHeight(); - float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor; - System.out.println("Scale: " + scale); - PdfFormXObject formXObject = pdfDoc.getPage(i).copyAsFormXObject(outputPdf); - float x = (pageSize.getWidth() - rect.getWidth() * scale) / 2; // Center Page - float y = (pageSize.getHeight() - rect.getHeight() * scale) / 2; - // Save the graphics state, apply the transformations, add the object, and then - // restore the graphics state - pdfCanvas.saveState(); - pdfCanvas.concatMatrix(scale, 0, 0, scale, x, y); - pdfCanvas.addXObject(formXObject, 0, 0); - pdfCanvas.restoreState(); - } - outputPdf.close(); - byte[] pdfContent = baos.toByteArray(); - pdfDoc.close(); - return WebResponseUtils.bytesToWebResponse(pdfContent, + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputDocument.save(baos); + outputDocument.close(); + sourceDocument.close(); + + + return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf"); } - //TODO - @Hidden - @PostMapping(value = "/auto-crop", consumes = "multipart/form-data") - public ResponseEntity cropPdf(@RequestParam("fileInput") MultipartFile file) throws IOException { - byte[] bytes = file.getBytes(); - PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes)); - PdfDocument pdfDoc = new PdfDocument(reader); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PdfWriter writer = new PdfWriter(baos); - PdfDocument outputPdf = new PdfDocument(writer); - - int totalPages = pdfDoc.getNumberOfPages(); - for (int i = 1; i <= totalPages; i++) { - PdfPage page = pdfDoc.getPage(i); - Rectangle originalMediaBox = page.getMediaBox(); - - Rectangle contentBox = determineContentBox(page); - - // Make sure we don't go outside the original media box. - Rectangle intersection = originalMediaBox.getIntersection(contentBox); - page.setCropBox(intersection); - - // Copy page to the new document - outputPdf.addPage(page.copyTo(outputPdf)); - } - - outputPdf.close(); - byte[] pdfContent = baos.toByteArray(); - pdfDoc.close(); - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" - + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf\"") - .contentType(MediaType.APPLICATION_PDF).body(pdfContent); - } - - private Rectangle determineContentBox(PdfPage page) { - // Extract the text from the page and find the bounding box. - TextBoundingRectangleFinder finder = new TextBoundingRectangleFinder(); - PdfCanvasProcessor processor = new PdfCanvasProcessor(finder); - processor.processPageContent(page); - return finder.getBoundingBox(); - } - - private static class TextBoundingRectangleFinder implements IEventListener { - private List allTextBoxes = new ArrayList<>(); - - public Rectangle getBoundingBox() { - // Sort the text boxes based on their vertical position - allTextBoxes.sort(Comparator.comparingDouble(Rectangle::getTop)); - - // Consider a box an outlier if its top is more than 1.5 times the IQR above the - // third quartile. - int q1Index = allTextBoxes.size() / 4; - int q3Index = 3 * allTextBoxes.size() / 4; - double iqr = allTextBoxes.get(q3Index).getTop() - allTextBoxes.get(q1Index).getTop(); - double threshold = allTextBoxes.get(q3Index).getTop() + 1.5 * iqr; - - // Initialize boundingBox to the first non-outlier box - int i = 0; - while (i < allTextBoxes.size() && allTextBoxes.get(i).getTop() > threshold) { - i++; - } - if (i == allTextBoxes.size()) { - // If all boxes are outliers, just return the first one - return allTextBoxes.get(0); - } - Rectangle boundingBox = allTextBoxes.get(i); - - // Extend the bounding box to include all non-outlier boxes - for (; i < allTextBoxes.size(); i++) { - Rectangle textBoundingBox = allTextBoxes.get(i); - if (textBoundingBox.getTop() > threshold) { - // This box is an outlier, skip it - continue; - } - float left = Math.min(boundingBox.getLeft(), textBoundingBox.getLeft()); - float bottom = Math.min(boundingBox.getBottom(), textBoundingBox.getBottom()); - float right = Math.max(boundingBox.getRight(), textBoundingBox.getRight()); - float top = Math.max(boundingBox.getTop(), textBoundingBox.getTop()); - - // Add a small padding around the bounding box - float padding = 10; - boundingBox = new Rectangle(left - padding, bottom - padding, right - left + 2 * padding, - top - bottom + 2 * padding); - } - return boundingBox; - } - - @Override - public void eventOccurred(IEventData data, EventType type) { - if (type == EventType.RENDER_TEXT) { - TextRenderInfo renderInfo = (TextRenderInfo) data; - allTextBoxes.add(renderInfo.getBaseline().getBoundingRectangle()); - } - } - - @Override - public Set getSupportedEvents() { - return Collections.singleton(EventType.RENDER_TEXT); - } - } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java index abc201ab..649dc8ec 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java @@ -17,19 +17,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; -import stirling.software.SPDF.utils.GeneralUtils; +import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.utils.WebResponseUtils; - +import org.apache.pdfbox.multipdf.Splitter; @RestController +@RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class SplitPDFController { @@ -38,57 +38,36 @@ public class SplitPDFController { @PostMapping(consumes = "multipart/form-data", value = "/split-pages") @Operation(summary = "Split a PDF file into separate documents", description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO") - public ResponseEntity splitPdf( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to be split") - MultipartFile file, - @RequestParam("pages") - @Parameter(description = "The pages to be included in separate documents. Specify individual page numbers (e.g., '1,3,5'), ranges (e.g., '1-3,5-7'), or 'all' for every page.") - String pages) throws IOException { - // parse user input - + public ResponseEntity splitPdf(@ModelAttribute PDFWithPageNums request) throws IOException { + MultipartFile file = request.getFileInput(); + String pages = request.getPageNumbers(); // open the pdf document InputStream inputStream = file.getInputStream(); PDDocument document = PDDocument.load(inputStream); - List pageNumbers = new ArrayList<>(); - pages = pages.replaceAll("\\s+", ""); // remove whitespaces - if (pages.toLowerCase().equals("all")) { - for (int i = 0; i < document.getNumberOfPages(); i++) { - pageNumbers.add(i); - } - } else { - String[] splitPoints = pages.split(","); - for (String splitPoint : splitPoints) { - List orderedPages = GeneralUtils.parsePageList(new String[] {splitPoint}, document.getNumberOfPages()); - pageNumbers.addAll(orderedPages); - } - // Add the last page as a split point - pageNumbers.add(document.getNumberOfPages() - 1); - } - + List pageNumbers = request.getPageNumbersList(document); + if(!pageNumbers.contains(document.getNumberOfPages() - 1)) + pageNumbers.add(document.getNumberOfPages()- 1); logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(","))); - // split the document + Splitter splitter = new Splitter(); List splitDocumentsBoas = new ArrayList<>(); - int previousPageNumber = 0; + + int previousPageNumber = 1; // PDFBox uses 1-based indexing for pages. for (int splitPoint : pageNumbers) { - try (PDDocument splitDocument = new PDDocument()) { - for (int i = previousPageNumber; i <= splitPoint; i++) { - PDPage page = document.getPage(i); - splitDocument.addPage(page); - logger.debug("Adding page {} to split document", i); - } - previousPageNumber = splitPoint + 1; + splitPoint = splitPoint + 1; + splitter.setStartPage(previousPageNumber); + splitter.setEndPage(splitPoint); + List splitDocuments = splitter.split(document); + for (PDDocument splitDoc : splitDocuments) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - splitDocument.save(baos); - + splitDoc.save(baos); splitDocumentsBoas.add(baos); - } catch (Exception e) { - logger.error("Failed splitting documents and saving them", e); - throw e; + splitDoc.close(); } + + previousPageNumber = splitPoint + 1; } diff --git a/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java b/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java index 2c249b85..22bf1d70 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java @@ -1,36 +1,29 @@ package stirling.software.SPDF.controller.api; -import java.io.IOException; +import java.awt.geom.AffineTransform; import java.io.ByteArrayOutputStream; -import com.itextpdf.kernel.pdf.*; -import com.itextpdf.kernel.pdf.xobject.PdfFormXObject; -import com.itextpdf.kernel.geom.PageSize; -import com.itextpdf.kernel.geom.Rectangle; -import com.itextpdf.layout.Document; -import com.itextpdf.layout.element.Image; -import java.util.ArrayList; -import java.util.List; +import java.io.IOException; +import org.apache.pdfbox.multipdf.LayerUtility; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDPageTree; +import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.utils.WebResponseUtils; -import org.apache.pdfbox.pdmodel.*; -import org.apache.pdfbox.multipdf.PDFMergerUtility; @RestController +@RequestMapping("/api/v1/general") @Tag(name = "General", description = "General APIs") public class ToSinglePageController { @@ -42,45 +35,52 @@ public class ToSinglePageController { summary = "Convert a multi-page PDF into a single long page PDF", description = "This endpoint converts a multi-page PDF document into a single paged PDF document. The width of the single page will be same as the input's width, but the height will be the sum of all the pages' heights. Input:PDF Output:PDF Type:SISO" ) - public ResponseEntity pdfToSinglePage( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input multi-page PDF file to be converted into a single page", required = true) - MultipartFile file) throws IOException { + public ResponseEntity pdfToSinglePage(@ModelAttribute PDFFile request) throws IOException { - PdfReader reader = new PdfReader(file.getInputStream()); - PdfDocument sourceDocument = new PdfDocument(reader); - - float totalHeight = 0; - float width = 0; + // Load the source document + PDDocument sourceDocument = PDDocument.load(request.getFileInput().getInputStream()); - for (int i = 1; i <= sourceDocument.getNumberOfPages(); i++) { - Rectangle pageSize = sourceDocument.getPage(i).getPageSize(); - totalHeight += pageSize.getHeight(); - if(width < pageSize.getWidth()) - width = pageSize.getWidth(); - } + // Calculate total height and max width + float totalHeight = 0; + float maxWidth = 0; + for (PDPage page : sourceDocument.getPages()) { + PDRectangle pageSize = page.getMediaBox(); + totalHeight += pageSize.getHeight(); + maxWidth = Math.max(maxWidth, pageSize.getWidth()); + } - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PdfWriter writer = new PdfWriter(baos); - PdfDocument newDocument = new PdfDocument(writer); - PageSize newPageSize = new PageSize(width, totalHeight); - newDocument.addNewPage(newPageSize); + // Create new document and page with calculated dimensions + PDDocument newDocument = new PDDocument(); + PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight)); + newDocument.addPage(newPage); - Document layoutDoc = new Document(newDocument); - float yOffset = totalHeight; + // Initialize the content stream of the new page + PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage); + contentStream.close(); + + LayerUtility layerUtility = new LayerUtility(newDocument); + float yOffset = totalHeight; - for (int i = 1; i <= sourceDocument.getNumberOfPages(); i++) { - PdfFormXObject pageCopy = sourceDocument.getPage(i).copyAsFormXObject(newDocument); - Image copiedPage = new Image(pageCopy); - copiedPage.setFixedPosition(0, yOffset - sourceDocument.getPage(i).getPageSize().getHeight()); - yOffset -= sourceDocument.getPage(i).getPageSize().getHeight(); - layoutDoc.add(copiedPage); - } + // For each page, copy its content to the new page at the correct offset + for (PDPage page : sourceDocument.getPages()) { + PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, sourceDocument.getPages().indexOf(page)); + AffineTransform af = AffineTransform.getTranslateInstance(0, yOffset - page.getMediaBox().getHeight()); + layerUtility.wrapInSaveRestore(newPage); + String defaultLayerName = "Layer" + sourceDocument.getPages().indexOf(page); + layerUtility.appendFormAsLayer(newPage, form, af, defaultLayerName); + yOffset -= page.getMediaBox().getHeight(); + } - layoutDoc.close(); - sourceDocument.close(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + newDocument.save(baos); + newDocument.close(); + sourceDocument.close(); - byte[] result = baos.toByteArray(); - return WebResponseUtils.bytesToWebResponse(result, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_singlePage.pdf"); + byte[] result = baos.toByteArray(); + return WebResponseUtils.bytesToWebResponse(result, request.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_singlePage.pdf"); + + + + } } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/controller/api/UserController.java b/src/main/java/stirling/software/SPDF/controller/api/UserController.java new file mode 100644 index 00000000..bf451567 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/UserController.java @@ -0,0 +1,234 @@ +package stirling.software.SPDF.controller.api; + +import java.security.Principal; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import org.springframework.web.servlet.view.RedirectView; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import stirling.software.SPDF.config.security.UserService; +import stirling.software.SPDF.model.User; + +@Controller +@RequestMapping("/api/v1/user") +public class UserController { + + @Autowired + private UserService userService; + + @PostMapping("/register") + public String register(@RequestParam String username, @RequestParam String password, Model model) { + if(userService.usernameExists(username)) { + model.addAttribute("error", "Username already exists"); + return "register"; + } + + userService.saveUser(username, password); + return "redirect:/login?registered=true"; + } + + @PostMapping("/change-username-and-password") + public RedirectView changeUsernameAndPassword(Principal principal, + @RequestParam String currentPassword, + @RequestParam String newUsername, + @RequestParam String newPassword, + HttpServletRequest request, + HttpServletResponse response, + RedirectAttributes redirectAttributes) { + if (principal == null) { + return new RedirectView("/change-creds?messageType=notAuthenticated"); + } + + Optional userOpt = userService.findByUsername(principal.getName()); + + if (userOpt == null || userOpt.isEmpty()) { + return new RedirectView("/change-creds?messageType=userNotFound"); + } + + User user = userOpt.get(); + + if (!userService.isPasswordCorrect(user, currentPassword)) { + return new RedirectView("/change-creds?messageType=incorrectPassword"); + } + + if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) { + return new RedirectView("/change-creds?messageType=usernameExists"); + } + + + userService.changePassword(user, newPassword); + if(newUsername != null && newUsername.length() > 0 && !user.getUsername().equals(newUsername)) { + userService.changeUsername(user, newUsername); + } + userService.changeFirstUse(user, false); + + // Logout using Spring's utility + new SecurityContextLogoutHandler().logout(request, response, null); + + return new RedirectView("/login?messageType=credsUpdated"); + } + + + + @PostMapping("/change-username") + public RedirectView changeUsername(Principal principal, + @RequestParam String currentPassword, + @RequestParam String newUsername, + HttpServletRequest request, + HttpServletResponse response, + RedirectAttributes redirectAttributes) { + if (principal == null) { + return new RedirectView("/account?messageType=notAuthenticated"); + } + + Optional userOpt = userService.findByUsername(principal.getName()); + + if (userOpt == null || userOpt.isEmpty()) { + return new RedirectView("/account?messageType=userNotFound"); + } + + User user = userOpt.get(); + + if (!userService.isPasswordCorrect(user, currentPassword)) { + return new RedirectView("/account?messageType=incorrectPassword"); + } + + if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) { + return new RedirectView("/account?messageType=usernameExists"); + } + + if(newUsername != null && newUsername.length() > 0) { + userService.changeUsername(user, newUsername); + } + + // Logout using Spring's utility + new SecurityContextLogoutHandler().logout(request, response, null); + + return new RedirectView("/login?messageType=credsUpdated"); + } + + @PostMapping("/change-password") + public RedirectView changePassword(Principal principal, + @RequestParam String currentPassword, + @RequestParam String newPassword, + HttpServletRequest request, + HttpServletResponse response, + RedirectAttributes redirectAttributes) { + if (principal == null) { + return new RedirectView("/account?messageType=notAuthenticated"); + } + + Optional userOpt = userService.findByUsername(principal.getName()); + + if (userOpt == null || userOpt.isEmpty()) { + return new RedirectView("/account?messageType=userNotFound"); + } + + User user = userOpt.get(); + + if (!userService.isPasswordCorrect(user, currentPassword)) { + return new RedirectView("/account?messageType=incorrectPassword"); + } + + userService.changePassword(user, newPassword); + + // Logout using Spring's utility + new SecurityContextLogoutHandler().logout(request, response, null); + + return new RedirectView("/login?messageType=credsUpdated"); + } + + + @PostMapping("/updateUserSettings") + public String updateUserSettings(HttpServletRequest request, Principal principal) { + Map paramMap = request.getParameterMap(); + Map updates = new HashMap<>(); + + System.out.println("Received parameter map: " + paramMap); + + for (Map.Entry entry : paramMap.entrySet()) { + updates.put(entry.getKey(), entry.getValue()[0]); + } + + System.out.println("Processed updates: " + updates); + + // Assuming you have a method in userService to update the settings for a user + userService.updateUserSettings(principal.getName(), updates); + + return "redirect:/account"; // Redirect to a page of your choice after updating + } + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @PostMapping("/admin/saveUser") + public RedirectView saveUser(@RequestParam String username, @RequestParam String password, @RequestParam String role, + @RequestParam(name = "forceChange", required = false, defaultValue = "false") boolean forceChange) { + + if(userService.usernameExists(username)) { + return new RedirectView("/addUsers?messageType=usernameExists"); + } + userService.saveUser(username, password, role, forceChange); + return new RedirectView("/addUsers"); // Redirect to account page after adding the user + } + + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @PostMapping("/admin/deleteUser/{username}") + public String deleteUser(@PathVariable String username, Authentication authentication) { + + // Get the currently authenticated username + String currentUsername = authentication.getName(); + + // Check if the provided username matches the current session's username + if (currentUsername.equals(username)) { + throw new IllegalArgumentException("Cannot delete currently logined in user."); + } + + userService.deleteUser(username); + return "redirect:/addUsers"; + } + + @PostMapping("/get-api-key") + public ResponseEntity getApiKey(Principal principal) { + if (principal == null) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("User not authenticated."); + } + String username = principal.getName(); + String apiKey = userService.getApiKeyForUser(username); + if (apiKey == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("API key not found for user."); + } + return ResponseEntity.ok(apiKey); + } + + @PostMapping("/update-api-key") + public ResponseEntity updateApiKey(Principal principal) { + if (principal == null) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("User not authenticated."); + } + String username = principal.getName(); + User user = userService.refreshApiKeyForUser(username); + String apiKey = user.getApiKey(); + if (apiKey == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("API key not found for user."); + } + return ResponseEntity.ok(apiKey); + } + + +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEpubToPdf.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEpubToPdf.java index 6705bee8..e821a36a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEpubToPdf.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertEpubToPdf.java @@ -1,36 +1,5 @@ package stirling.software.SPDF.controller.api.converters; -import java.io.IOException; - -import org.commonmark.node.Node; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.HtmlRenderer; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import io.swagger.v3.oas.annotations.Hidden; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import stirling.software.SPDF.utils.FileToPdf; -import stirling.software.SPDF.utils.WebResponseUtils; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.http.ResponseEntity; - -import io.swagger.v3.oas.annotations.Operation; - -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.DocumentBuilder; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; @@ -41,7 +10,29 @@ import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.GeneralFile; +import stirling.software.SPDF.utils.FileToPdf; +import stirling.software.SPDF.utils.WebResponseUtils; + @RestController +@RequestMapping("/api/v1/convert") @Tag(name = "Convert", description = "Convert APIs") public class ConvertEpubToPdf { //TODO @@ -52,9 +43,9 @@ public class ConvertEpubToPdf { description = "This endpoint takes an EPUB file input and converts it to a single PDF." ) public ResponseEntity epubToSinglePdf( - @RequestPart(required = true, value = "fileInput") MultipartFile fileInput) + @ModelAttribute GeneralFile request) throws Exception { - + MultipartFile fileInput = request.getFileInput(); if (fileInput == null) { throw new IllegalArgumentException("Please provide an EPUB file for conversion."); } @@ -83,7 +74,7 @@ public class ConvertEpubToPdf { // Assuming a pseudo-code function that merges multiple PDFs into one. private byte[] mergeMultiplePdfsIntoOne(List individualPdfs) { - // You can use a library such as iText or PDFBox to perform the merging here. + // You can use a library such as PDFBox to perform the merging here. // Return the byte[] of the merged PDF. return null; } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java index e054d7f0..5839dd2d 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java @@ -1,42 +1,33 @@ package stirling.software.SPDF.controller.api.converters; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.GeneralFile; import stirling.software.SPDF.utils.FileToPdf; -import stirling.software.SPDF.utils.GeneralUtils; -import stirling.software.SPDF.utils.ProcessExecutor; -import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @Tag(name = "Convert", description = "Convert APIs") +@RequestMapping("/api/v1/convert") public class ConvertHtmlToPDF { - @PostMapping(consumes = "multipart/form-data", value = "/html-to-pdf") + @PostMapping(consumes = "multipart/form-data", value = "/html/pdf") @Operation( summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF", description = "This endpoint takes an HTML or ZIP file input and converts it to a PDF format." ) public ResponseEntity HtmlToPdf( - @RequestPart(required = true, value = "fileInput") MultipartFile fileInput) throws IOException, InterruptedException { + @ModelAttribute GeneralFile request) + throws Exception { + MultipartFile fileInput = request.getFileInput(); if (fileInput == null) { throw new IllegalArgumentException("Please provide an HTML or ZIP file for conversion."); diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java index d4964196..0064a3ab 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java @@ -11,44 +11,35 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.converters.ConvertToImageRequest; +import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.WebResponseUtils; @RestController +@RequestMapping("/api/v1/convert") @Tag(name = "Convert", description = "Convert APIs") public class ConvertImgPDFController { private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class); - @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-img") + @PostMapping(consumes = "multipart/form-data", value = "/pdf/img") @Operation(summary = "Convert PDF to image(s)", description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images. Input:PDF Output:Image Type:SI-Conditional") - public ResponseEntity convertToImage( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to be converted") - MultipartFile file, - @RequestParam("imageFormat") - @Parameter(description = "The output image format", schema = @Schema(allowableValues = {"png", "jpeg", "jpg", "gif"})) - String imageFormat, - @RequestParam("singleOrMultiple") - @Parameter(description = "Choose between a single image containing all pages or separate images for each page", schema = @Schema(allowableValues = {"single", "multiple"})) - String singleOrMultiple, - @RequestParam("colorType") - @Parameter(description = "The color type of the output image(s)", schema = @Schema(allowableValues = {"color", "greyscale", "blackwhite"})) - String colorType, - @RequestParam("dpi") - @Parameter(description = "The DPI (dots per inch) for the output image(s)") - String dpi) throws IOException { - + public ResponseEntity convertToImage(@ModelAttribute ConvertToImageRequest request) throws IOException { + MultipartFile file = request.getFileInput(); + String imageFormat = request.getImageFormat(); + String singleOrMultiple = request.getSingleOrMultiple(); + String colorType = request.getColorType(); + String dpi = request.getDpi(); + byte[] pdfBytes = file.getBytes(); ImageType colorTypeResult = ImageType.RGB; if ("greyscale".equals(colorType)) { @@ -83,24 +74,17 @@ public class ConvertImgPDFController { } } - @PostMapping(consumes = "multipart/form-data", value = "/img-to-pdf") + @PostMapping(consumes = "multipart/form-data", value = "/img/pdf") @Operation(summary = "Convert images to a PDF file", description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:SISO?") - public ResponseEntity convertToPdf( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input images to be converted to a PDF file") - MultipartFile[] file, - @RequestParam(defaultValue = "false", name = "stretchToFit") - @Parameter(description = "Whether to stretch the images to fit the PDF page or maintain the aspect ratio", example = "false") - boolean stretchToFit, - @RequestParam("colorType") - @Parameter(description = "The color type of the output image(s)", schema = @Schema(allowableValues = {"color", "greyscale", "blackwhite"})) - String colorType, - @RequestParam(defaultValue = "false", name = "autoRotate") - @Parameter(description = "Whether to automatically rotate the images to better fit the PDF page", example = "true") - boolean autoRotate) throws IOException { + public ResponseEntity convertToPdf(@ModelAttribute ConvertToPdfRequest request) throws IOException { + MultipartFile[] file = request.getFileInput(); + String fitOption = request.getFitOption(); + String colorType = request.getColorType(); + boolean autoRotate = request.isAutoRotate(); + // Convert the file to PDF and get the resulting bytes - byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate, colorType); + byte[] bytes = PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType); return WebResponseUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf"); } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java index c1bc1b73..4191ecdf 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java @@ -1,33 +1,35 @@ package stirling.software.SPDF.controller.api.converters; -import java.io.IOException; - import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.GeneralFile; import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @Tag(name = "Convert", description = "Convert APIs") +@RequestMapping("/api/v1/convert") public class ConvertMarkdownToPdf { - @PostMapping(consumes = "multipart/form-data", value = "/markdown-to-pdf") + @PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf") @Operation( summary = "Convert a Markdown file to PDF", description = "This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format." ) public ResponseEntity markdownToPdf( - @RequestPart(required = true, value = "fileInput") MultipartFile fileInput) - throws IOException, InterruptedException { + @ModelAttribute GeneralFile request) + throws Exception { + MultipartFile fileInput = request.getFileInput(); if (fileInput == null) { throw new IllegalArgumentException("Please provide a Markdown file for conversion."); diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java index 3b9f278f..e1c18a49 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java @@ -10,20 +10,22 @@ import java.util.List; import org.apache.commons.io.FilenameUtils; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.GeneralFile; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @Tag(name = "Convert", description = "Convert APIs") +@RequestMapping("/api/v1/convert") public class ConvertOfficeController { public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { @@ -58,19 +60,14 @@ public class ConvertOfficeController { return fileExtension.matches(extensionPattern); } - @PostMapping(consumes = "multipart/form-data", value = "/file-to-pdf") + @PostMapping(consumes = "multipart/form-data", value = "/file/pdf") @Operation( summary = "Convert a file to a PDF using LibreOffice", description = "This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF Type:SISO" ) - public ResponseEntity processFileToPDF( - @RequestPart(required = true, value = "fileInput") - @Parameter( - description = "The input file to be converted to a PDF file using LibreOffice", - required = true - ) - MultipartFile inputFile - ) throws IOException, InterruptedException { + public ResponseEntity processFileToPDF(@ModelAttribute GeneralFile request) + throws Exception { + MultipartFile inputFile = request.getFileInput(); // unused but can start server instance if startup time is to long // LibreOfficeListener.getInstance().start(); diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java index 7c99ee4d..11279a27 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java @@ -3,69 +3,67 @@ package stirling.software.SPDF.controller.api.converters; import java.io.IOException; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.PDFFile; +import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest; +import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest; +import stirling.software.SPDF.model.api.converters.PdfToWordRequest; import stirling.software.SPDF.utils.PDFToFile; @RestController +@RequestMapping("/api/v1/convert") @Tag(name = "Convert", description = "Convert APIs") public class ConvertPDFToOffice { - @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-html") + @PostMapping(consumes = "multipart/form-data", value = "/pdf/html") @Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO") - public ResponseEntity processPdfToHTML( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to HTML format", required = true) MultipartFile inputFile) - throws IOException, InterruptedException { + public ResponseEntity processPdfToHTML(@ModelAttribute PDFFile request) + throws Exception { + MultipartFile inputFile = request.getFileInput(); PDFToFile pdfToFile = new PDFToFile(); return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import"); } - @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-presentation") + @PostMapping(consumes = "multipart/form-data", value = "/pdf/presentation") @Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format. Input:PDF Output:PPT Type:SISO") - public ResponseEntity processPdfToPresentation( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile, - @RequestParam("outputFormat") @Parameter(description = "The output Presentation format", schema = @Schema(allowableValues = { - "ppt", "pptx", "odp" })) String outputFormat) - throws IOException, InterruptedException { + public ResponseEntity processPdfToPresentation(@ModelAttribute PdfToPresentationRequest request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String outputFormat = request.getOutputFormat(); PDFToFile pdfToFile = new PDFToFile(); return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import"); } - @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-text") + @PostMapping(consumes = "multipart/form-data", value = "/pdf/text") @Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format. Input:PDF Output:TXT Type:SISO") - public ResponseEntity processPdfToRTForTXT( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile, - @RequestParam("outputFormat") @Parameter(description = "The output Text or RTF format", schema = @Schema(allowableValues = { - "rtf", "txt:Text" })) String outputFormat) - throws IOException, InterruptedException { + public ResponseEntity processPdfToRTForTXT(@ModelAttribute PdfToTextOrRTFRequest request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String outputFormat = request.getOutputFormat(); + PDFToFile pdfToFile = new PDFToFile(); return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); } - @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-word") + @PostMapping(consumes = "multipart/form-data", value = "/pdf/word") @Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format. Input:PDF Output:WORD Type:SISO") - public ResponseEntity processPdfToWord( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile, - @RequestParam("outputFormat") @Parameter(description = "The output Word document format", schema = @Schema(allowableValues = { - "doc", "docx", "odt" })) String outputFormat) - throws IOException, InterruptedException { + public ResponseEntity processPdfToWord(@ModelAttribute PdfToWordRequest request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String outputFormat = request.getOutputFormat(); PDFToFile pdfToFile = new PDFToFile(); return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); } - @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-xml") + @PostMapping(consumes = "multipart/form-data", value = "/pdf/xml") @Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML Type:SISO") - public ResponseEntity processPdfToXML( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to an XML file", required = true) MultipartFile inputFile) - throws IOException, InterruptedException { + public ResponseEntity processPdfToXML(@ModelAttribute PDFFile request) + throws Exception { + MultipartFile inputFile = request.getFileInput(); PDFToFile pdfToFile = new PDFToFile(); return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import"); diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java index 6a99090b..32ccb84a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java @@ -1,37 +1,37 @@ package stirling.software.SPDF.controller.api.converters; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController +@RequestMapping("/api/v1/convert") @Tag(name = "Convert", description = "Convert APIs") public class ConvertPDFToPDFA { - @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa") + @PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa") @Operation( summary = "Convert a PDF to a PDF/A", 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" ) - public ResponseEntity pdfToPdfA( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true) - MultipartFile inputFile) throws IOException, InterruptedException { + public ResponseEntity pdfToPdfA(@ModelAttribute PDFFile request) + throws Exception { + MultipartFile inputFile = request.getFileInput(); // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java index 7b8ba3a7..7c81edf2 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java @@ -7,15 +7,14 @@ import java.util.ArrayList; import java.util.List; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.converters.UrlToPdfRequest; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; @@ -23,17 +22,16 @@ import stirling.software.SPDF.utils.WebResponseUtils; @RestController @Tag(name = "Convert", description = "Convert APIs") +@RequestMapping("/api/v1/convert") public class ConvertWebsiteToPDF { - @PostMapping(consumes = "multipart/form-data", value = "/url-to-pdf") + @PostMapping(consumes = "multipart/form-data", value = "/url/pdf") @Operation( summary = "Convert a URL to a PDF", description = "This endpoint fetches content from a URL and converts it to a PDF format." ) - public ResponseEntity urlToPdf( - @RequestParam(required = true, value = "urlInput") - @Parameter(description = "The input URL to be converted to a PDF file", required = true) - String URL) throws IOException, InterruptedException { + public ResponseEntity urlToPdf(@ModelAttribute UrlToPdfRequest request) throws IOException, InterruptedException { + String URL = request.getUrlInput(); // Validate the URL format if(!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) { diff --git a/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java b/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java index 3409e1d3..bdbb6613 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java @@ -1,39 +1,40 @@ package stirling.software.SPDF.controller.api.filters; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.PDFComparisonAndCount; +import stirling.software.SPDF.model.api.PDFWithPageNums; +import stirling.software.SPDF.model.api.filter.ContainsTextRequest; +import stirling.software.SPDF.model.api.filter.FileSizeRequest; +import stirling.software.SPDF.model.api.filter.PageRotationRequest; +import stirling.software.SPDF.model.api.filter.PageSizeRequest; import stirling.software.SPDF.utils.PdfUtils; -import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.WebResponseUtils; -import io.swagger.v3.oas.annotations.media.Schema; @RestController +@RequestMapping("/api/v1/filter") @Tag(name = "Filter", description = "Filter APIs") public class FilterController { @PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text") @Operation(summary = "Checks if a PDF contains set text, returns true if does", description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity containsText( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true) MultipartFile inputFile, - @Parameter(description = "The text to check for", required = true) String text, - @Parameter(description = "The page number to check for text on accepts 'All', ranges like '1-4'", required = false) String pageNumber) - throws IOException, InterruptedException { + public ResponseEntity containsText(@ModelAttribute ContainsTextRequest request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String text = request.getText(); + String pageNumber = request.getPageNumbers(); + PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); if (PdfUtils.hasText(pdfDocument, pageNumber, text)) return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename()); @@ -43,10 +44,11 @@ public class FilterController { // TODO @PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image") @Operation(summary = "Checks if a PDF contains an image", description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity containsImage( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true) MultipartFile inputFile, - @Parameter(description = "The page number to check for image on accepts 'All', ranges like '1-4'", required = false) String pageNumber) + public ResponseEntity containsImage(@ModelAttribute PDFWithPageNums request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String pageNumber = request.getPageNumbers(); + PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); if (PdfUtils.hasImages(pdfDocument, pageNumber)) return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename()); @@ -55,12 +57,10 @@ public class FilterController { @PostMapping(consumes = "multipart/form-data", value = "/filter-page-count") @Operation(summary = "Checks if a PDF is greater, less or equal to a setPageCount", description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity pageCount( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile, - @Parameter(description = "Page Count", required = true) String pageCount, - @Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = { - "Greater", "Equal", "Less" })) String comparator) - throws IOException, InterruptedException { + public ResponseEntity pageCount(@ModelAttribute PDFComparisonAndCount request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String pageCount = request.getPageCount(); + String comparator = request.getComparator(); // Load the PDF PDDocument document = PDDocument.load(inputFile.getInputStream()); int actualPageCount = document.getNumberOfPages(); @@ -88,12 +88,10 @@ public class FilterController { @PostMapping(consumes = "multipart/form-data", value = "/filter-page-size") @Operation(summary = "Checks if a PDF is of a certain size", description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity pageSize( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile, - @Parameter(description = "Standard Page Size", required = true) String standardPageSize, - @Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = { - "Greater", "Equal", "Less" })) String comparator) - throws IOException, InterruptedException { + public ResponseEntity pageSize(@ModelAttribute PageSizeRequest request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String standardPageSize = request.getStandardPageSize(); + String comparator = request.getComparator(); // Load the PDF PDDocument document = PDDocument.load(inputFile.getInputStream()); @@ -131,12 +129,10 @@ public class FilterController { @PostMapping(consumes = "multipart/form-data", value = "/filter-file-size") @Operation(summary = "Checks if a PDF is a set file size", description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity fileSize( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile, - @Parameter(description = "File Size", required = true) String fileSize, - @Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = { - "Greater", "Equal", "Less" })) String comparator) - throws IOException, InterruptedException { + public ResponseEntity fileSize(@ModelAttribute FileSizeRequest request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String fileSize = request.getFileSize(); + String comparator = request.getComparator(); // Get the file size long actualFileSize = inputFile.getSize(); @@ -164,12 +160,10 @@ public class FilterController { @PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation") @Operation(summary = "Checks if a PDF is of a certain rotation", description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity pageRotation( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile, - @Parameter(description = "Rotation in degrees", required = true) int rotation, - @Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = { - "Greater", "Equal", "Less" })) String comparator) - throws IOException, InterruptedException { + public ResponseEntity pageRotation(@ModelAttribute PageRotationRequest request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + int rotation = request.getRotation(); + String comparator = request.getComparator(); // Load the PDF PDDocument document = PDDocument.load(inputFile.getInputStream()); diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/AutoRenameController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java similarity index 63% rename from src/main/java/stirling/software/SPDF/controller/api/other/AutoRenameController.java rename to src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java index 66fd70a6..fe8337d2 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/AutoRenameController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java @@ -1,77 +1,29 @@ -package stirling.software.SPDF.controller.api.other; +package stirling.software.SPDF.controller.api.misc; import java.io.IOException; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.text.PDFTextStripper; +import org.apache.pdfbox.text.TextPosition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; -import stirling.software.SPDF.utils.GeneralUtils; -import stirling.software.SPDF.utils.PdfUtils; +import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest; import stirling.software.SPDF.utils.WebResponseUtils; -import org.apache.pdfbox.pdmodel.*; -import org.apache.pdfbox.pdmodel.common.*; -import org.apache.pdfbox.pdmodel.PDPageContentStream.*; -import org.springframework.web.bind.annotation.*; -import org.springframework.http.*; -import org.springframework.web.multipart.MultipartFile; -import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.media.*; -import io.swagger.v3.oas.annotations.parameters.*; -import org.apache.pdfbox.pdmodel.font.PDType1Font; -import org.apache.pdfbox.text.TextPosition; -import org.apache.tomcat.util.http.ResponseUtil; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URLEncoder; -import java.util.List; -import java.util.ArrayList; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.multipart.MultipartFile; - -import com.itextpdf.io.font.constants.StandardFonts; -import com.itextpdf.kernel.font.PdfFont; -import com.itextpdf.kernel.font.PdfFontFactory; -import com.itextpdf.kernel.geom.Rectangle; -import com.itextpdf.kernel.pdf.PdfReader; -import com.itextpdf.kernel.pdf.PdfWriter; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfPage; -import com.itextpdf.kernel.pdf.canvas.PdfCanvas; -import com.itextpdf.layout.Canvas; -import com.itextpdf.layout.element.Paragraph; -import com.itextpdf.layout.properties.TextAlignment; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Schema; - -import java.io.*; -import org.apache.pdfbox.pdmodel.*; -import org.apache.pdfbox.text.*; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.media.Schema; -import org.springframework.http.ResponseEntity; @RestController -@Tag(name = "Other", description = "Other APIs") +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class AutoRenameController { private static final Logger logger = LoggerFactory.getLogger(AutoRenameController.class); @@ -81,10 +33,9 @@ public class AutoRenameController { @PostMapping(consumes = "multipart/form-data", value = "/auto-rename") @Operation(summary = "Extract header from PDF file", description = "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO") - public ResponseEntity extractHeader( - @RequestPart(value = "fileInput") @Parameter(description = "The input PDF file from which the header is to be extracted.", required = true) MultipartFile file, - @RequestParam(required = false, defaultValue = "false") @Parameter(description = "Flag indicating whether to use the first text as a fallback if no suitable title is found. Defaults to false.", required = false) Boolean useFirstTextAsFallback) - throws Exception { + public ResponseEntity extractHeader(@ModelAttribute ExtractHeaderRequest request) throws Exception { + MultipartFile file = request.getFileInput(); + Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback(); PDDocument document = PDDocument.load(file.getInputStream()); PDFTextStripper reader = new PDFTextStripper() { diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/AutoSplitPdfController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java similarity index 89% rename from src/main/java/stirling/software/SPDF/controller/api/other/AutoSplitPdfController.java rename to src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java index 9e08e9a7..b4b9b951 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/AutoSplitPdfController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller.api.other; +package stirling.software.SPDF.controller.api.misc; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; @@ -16,8 +16,9 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.rendering.PDFRenderer; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -29,21 +30,23 @@ import com.google.zxing.PlanarYUVLuminanceSource; import com.google.zxing.Result; import com.google.zxing.common.HybridBinarizer; -import stirling.software.SPDF.utils.WebResponseUtils; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest; +import stirling.software.SPDF.utils.WebResponseUtils; @RestController +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class AutoSplitPdfController { private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF"; @PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data") @Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP Type:SISO") - public ResponseEntity autoSplitPdf( - @RequestParam("fileInput") @Parameter(description = "The input PDF file which needs to be split into separate documents based on QR code boundaries.", required = true) MultipartFile file, - @RequestParam(value ="duplexMode",defaultValue = "false") @Parameter(description = "Flag indicating if the duplex mode is active, where the page after the divider also gets removed.", required = false) boolean duplexMode) - throws IOException { + public ResponseEntity autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) throws IOException { + MultipartFile file = request.getFileInput(); + boolean duplexMode = request.isDuplexMode(); InputStream inputStream = file.getInputStream(); PDDocument document = PDDocument.load(inputStream); diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/BlankPageController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java similarity index 83% rename from src/main/java/stirling/software/SPDF/controller/api/other/BlankPageController.java rename to src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java index 4f505b73..c52ff61a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/BlankPageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller.api.other; +package stirling.software.SPDF.controller.api.misc; import java.awt.image.BufferedImage; import java.io.IOException; @@ -20,22 +20,23 @@ import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.text.PDFTextStripper; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController -@Tag(name = "Other", description = "Other APIs") +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class BlankPageController { @PostMapping(consumes = "multipart/form-data", value = "/remove-blanks") @@ -43,16 +44,10 @@ public class BlankPageController { summary = "Remove blank pages from a PDF file", description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages. Input:PDF Output:PDF Type:SISO" ) - public ResponseEntity removeBlankPages( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file from which blank pages will be removed", required = true) - MultipartFile inputFile, - @RequestParam(defaultValue = "10", name = "threshold") - @Parameter(description = "The threshold value to determine blank pages", example = "10") - int threshold, - @RequestParam(defaultValue = "99.9", name = "whitePercent") - @Parameter(description = "The percentage of white color on a page to consider it as blank", example = "99.9") - float whitePercent) throws IOException, InterruptedException { + public ResponseEntity removeBlankPages(@ModelAttribute RemoveBlankPagesRequest request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + int threshold = request.getThreshold(); + float whitePercent = request.getWhitePercent(); PDDocument document = null; try { diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java similarity index 90% rename from src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java rename to src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java index 381a6821..dd864bc1 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller.api.other; +package stirling.software.SPDF.controller.api.misc; import java.awt.Image; import java.awt.image.BufferedImage; @@ -22,35 +22,34 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.misc.OptimizePdfRequest; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController -@Tag(name = "Other", description = "Other APIs") +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class CompressController { private static final Logger logger = LoggerFactory.getLogger(CompressController.class); @PostMapping(consumes = "multipart/form-data", value = "/compress-pdf") @Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters. Input:PDF Output:PDF Type:SISO") - public ResponseEntity optimizePdf( - @RequestPart(value = "fileInput") @Parameter(description = "The input PDF file to be optimized.", required = true) MultipartFile inputFile, - @RequestParam(required = false, value = "optimizeLevel") @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", schema = @Schema(allowableValues = { - "1", "2", "3", "4", "5" })) Integer optimizeLevel, - @RequestParam(value = "expectedOutputSize", required = false) @Parameter(description = "The expected output size, e.g. '100MB', '25KB', etc.", required = false) String expectedOutputSizeString) - throws Exception { + public ResponseEntity optimizePdf(@ModelAttribute OptimizePdfRequest request) throws Exception { + MultipartFile inputFile = request.getFileInput(); + Integer optimizeLevel = request.getOptimizeLevel(); + String expectedOutputSizeString = request.getExpectedOutputSize(); + if(expectedOutputSizeString == null && optimizeLevel == null) { throw new Exception("Both expected output size and optimize level are not specified"); diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImageScansController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java similarity index 71% rename from src/main/java/stirling/software/SPDF/controller/api/other/ExtractImageScansController.java rename to src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java index 55ff446c..d5906970 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImageScansController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller.api.other; +package stirling.software.SPDF.controller.api.misc; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; @@ -24,20 +24,21 @@ import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; - @RestController -@Tag(name = "Other", description = "Other APIs") +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class ExtractImageScansController { private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class); @@ -46,26 +47,16 @@ public class ExtractImageScansController { @Operation(summary = "Extract image scans from an input file", description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP Type:SIMO") public ResponseEntity extractImageScans( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input file containing image scans") - MultipartFile inputFile, - @RequestParam(name = "angle_threshold", defaultValue = "5") - @Parameter(description = "The angle threshold for the image scan extraction", example = "5") - int angleThreshold, - @RequestParam(name = "tolerance", defaultValue = "20") - @Parameter(description = "The tolerance for the image scan extraction", example = "20") - int tolerance, - @RequestParam(name = "min_area", defaultValue = "8000") - @Parameter(description = "The minimum area for the image scan extraction", example = "8000") - int minArea, - @RequestParam(name = "min_contour_area", defaultValue = "500") - @Parameter(description = "The minimum contour area for the image scan extraction", example = "500") - int minContourArea, - @RequestParam(name = "border_size", defaultValue = "1") - @Parameter(description = "The border size for the image scan extraction", example = "1") - int borderSize) throws IOException, InterruptedException { - - String fileName = inputFile.getOriginalFilename(); + @RequestBody( + description = "Form data containing file and extraction parameters", + required = true, + content = @Content( + mediaType = "multipart/form-data", + schema = @Schema(implementation = ExtractImageScansRequest.class) // This should represent your form's structure + ) + ) + ExtractImageScansRequest form) throws IOException, InterruptedException { + String fileName = form.getFileInput().getOriginalFilename(); String extension = fileName.substring(fileName.lastIndexOf(".") + 1); List images = new ArrayList<>(); @@ -73,7 +64,7 @@ public class ExtractImageScansController { // Check if input file is a PDF if (extension.equalsIgnoreCase("pdf")) { // Load PDF document - try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputFile.getBytes()))) { + try (PDDocument document = PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) { PDFRenderer pdfRenderer = new PDFRenderer(document); int pageCount = document.getNumberOfPages(); images = new ArrayList<>(); @@ -93,7 +84,7 @@ public class ExtractImageScansController { } } else { Path tempInputFile = Files.createTempFile("input_", "." + extension); - Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + Files.copy(form.getFileInput().getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); // Add input file path to images list images.add(tempInputFile.toString()); } @@ -109,11 +100,11 @@ public class ExtractImageScansController { "./scripts/split_photos.py", images.get(i), tempDir.toString(), - "--angle_threshold", String.valueOf(angleThreshold), - "--tolerance", String.valueOf(tolerance), - "--min_area", String.valueOf(minArea), - "--min_contour_area", String.valueOf(minContourArea), - "--border_size", String.valueOf(borderSize) + "--angle_threshold", String.valueOf(form.getAngleThreshold()), + "--tolerance", String.valueOf(form.getTolerance()), + "--min_area", String.valueOf(form.getMinArea()), + "--min_contour_area", String.valueOf(form.getMinContourArea()), + "--border_size", String.valueOf(form.getBorderSize()) )); diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImagesController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java similarity index 81% rename from src/main/java/stirling/software/SPDF/controller/api/other/ExtractImagesController.java rename to src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java index c10dd7b4..b24ac892 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java @@ -1,5 +1,8 @@ -package stirling.software.SPDF.controller.api.other; - +package stirling.software.SPDF.controller.api.misc; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; @@ -20,19 +23,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.PDFWithImageFormatRequest; import stirling.software.SPDF.utils.WebResponseUtils; @RestController -@Tag(name = "Other", description = "Other APIs") +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class ExtractImagesController { private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class); @@ -40,13 +43,9 @@ public class ExtractImagesController { @PostMapping(consumes = "multipart/form-data", value = "/extract-images") @Operation(summary = "Extract images from a PDF file", description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO") - public ResponseEntity extractImages( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file containing images") - MultipartFile file, - @RequestParam("format") - @Parameter(description = "The output image format e.g., 'png', 'jpeg', or 'gif'", schema = @Schema(allowableValues = {"png", "jpeg", "gif"})) - String format) throws IOException { + public ResponseEntity extractImages(@ModelAttribute PDFWithImageFormatRequest request) throws IOException { + MultipartFile file = request.getFileInput(); + String format = request.getFormat(); System.out.println(System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format); PDDocument document = PDDocument.load(file.getBytes()); @@ -62,7 +61,8 @@ public class ExtractImagesController { int imageIndex = 1; String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", ""); - int pageNum = 1; + int pageNum = 0; + Set processedImages = new HashSet<>(); // Iterate over each page for (PDPage page : document.getPages()) { ++pageNum; @@ -70,7 +70,12 @@ public class ExtractImagesController { for (COSName name : page.getResources().getXObjectNames()) { if (page.getResources().isImageXObject(name)) { PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name); - + int imageHash = image.hashCode(); + if(processedImages.contains(imageHash)) { + continue; // Skip already processed images + } + processedImages.add(imageHash); + // Convert image to desired format RenderedImage renderedImage = image.getImage(); BufferedImage bufferedImage = null; diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanControllerWIP.java b/src/main/java/stirling/software/SPDF/controller/api/misc/FakeScanControllerWIP.java similarity index 91% rename from src/main/java/stirling/software/SPDF/controller/api/other/FakeScanControllerWIP.java rename to src/main/java/stirling/software/SPDF/controller/api/misc/FakeScanControllerWIP.java index 7a25d28c..68e026ab 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanControllerWIP.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/FakeScanControllerWIP.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller.api.other; +package stirling.software.SPDF.controller.api.misc; import java.awt.Color; import java.awt.geom.AffineTransform; @@ -9,6 +9,7 @@ import java.awt.image.BufferedImageOp; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import java.awt.image.RescaleOp; +import java.io.ByteArrayOutputStream; //Required for file input/output import java.io.File; import java.io.IOException; @@ -29,21 +30,21 @@ import org.apache.pdfbox.rendering.PDFRenderer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import com.itextpdf.io.source.ByteArrayOutputStream; - import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.utils.WebResponseUtils; @RestController -@Tag(name = "Other", description = "Other APIs") +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class FakeScanControllerWIP { private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class); @@ -55,10 +56,8 @@ public class FakeScanControllerWIP { summary = "Repair a PDF file", 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." ) - public ResponseEntity repairPdf( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to be repaired", required = true) - MultipartFile inputFile) throws IOException, InterruptedException { + public ResponseEntity repairPdf(@ModelAttribute PDFFile request) throws IOException { + MultipartFile inputFile = request.getFileInput(); PDDocument document = PDDocument.load(inputFile.getBytes()); PDFRenderer pdfRenderer = new PDFRenderer(document); diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/MetadataController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java similarity index 68% rename from src/main/java/stirling/software/SPDF/controller/api/other/MetadataController.java rename to src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java index e3979d10..027c6240 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/MetadataController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller.api.other; +package stirling.software.SPDF.controller.api.misc; import java.io.IOException; import java.text.ParseException; @@ -11,19 +11,20 @@ import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentInformation; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.misc.MetadataRequest; import stirling.software.SPDF.utils.WebResponseUtils; @RestController -@Tag(name = "Other", description = "Other APIs") +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class MetadataController { @@ -41,44 +42,28 @@ public class MetadataController { @PostMapping(consumes = "multipart/form-data", value = "/update-metadata") @Operation(summary = "Update metadata of a PDF file", description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields. Input:PDF Output:PDF Type:SISO") - public ResponseEntity metadata( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to update metadata") - MultipartFile pdfFile, - @RequestParam(value = "deleteAll", required = false, defaultValue = "false") - @Parameter(description = "Delete all metadata if set to true") - Boolean deleteAll, - @RequestParam(value = "author", required = false) - @Parameter(description = "The author of the document") - String author, - @RequestParam(value = "creationDate", required = false) - @Parameter(description = "The creation date of the document (format: yyyy/MM/dd HH:mm:ss)") - String creationDate, - @RequestParam(value = "creator", required = false) - @Parameter(description = "The creator of the document") - String creator, - @RequestParam(value = "keywords", required = false) - @Parameter(description = "The keywords for the document") - String keywords, - @RequestParam(value = "modificationDate", required = false) - @Parameter(description = "The modification date of the document (format: yyyy/MM/dd HH:mm:ss)") - String modificationDate, - @RequestParam(value = "producer", required = false) - @Parameter(description = "The producer of the document") - String producer, - @RequestParam(value = "subject", required = false) - @Parameter(description = "The subject of the document") - String subject, - @RequestParam(value = "title", required = false) - @Parameter(description = "The title of the document") - String title, - @RequestParam(value = "trapped", required = false) - @Parameter(description = "The trapped status of the document") - String trapped, - @Parameter(description = "Map list of key and value of custom parameters, note these must start with customKey and customValue if they are non standard") - @RequestParam Map allRequestParams) - throws IOException { + public ResponseEntity metadata(@ModelAttribute MetadataRequest request) throws IOException { + + // Extract PDF file from the request object + MultipartFile pdfFile = request.getFileInput(); + // Extract metadata information + Boolean deleteAll = request.isDeleteAll(); + String author = request.getAuthor(); + String creationDate = request.getCreationDate(); + String creator = request.getCreator(); + String keywords = request.getKeywords(); + String modificationDate = request.getModificationDate(); + String producer = request.getProducer(); + String subject = request.getSubject(); + String title = request.getTitle(); + String trapped = request.getTrapped(); + + // Extract additional custom parameters + Map allRequestParams = request.getAllRequestParams(); + if(allRequestParams == null) { + allRequestParams = new java.util.HashMap(); + } // Load the PDF file into a PDDocument PDDocument document = PDDocument.load(pdfFile.getBytes()); diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/OCRController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java similarity index 76% rename from src/main/java/stirling/software/SPDF/controller/api/other/OCRController.java rename to src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java index d6009c8a..5ee06c19 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/OCRController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller.api.other; +package stirling.software.SPDF.controller.api.misc; import java.io.File; import java.io.FileOutputStream; @@ -18,22 +18,22 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController -@Tag(name = "Other", description = "Other APIs") +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class OCRController { private static final Logger logger = LoggerFactory.getLogger(OCRController.class); @@ -51,35 +51,16 @@ public class OCRController { @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 processPdfWithOCR( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to be processed with OCR") - MultipartFile inputFile, - @RequestParam("languages") - @Parameter(description = "List of languages to use in OCR processing") - List selectedLanguages, - @RequestParam(name = "sidecar", required = false) - @Parameter(description = "Include OCR text in a sidecar text file if set to true") - Boolean sidecar, - @RequestParam(name = "deskew", required = false) - @Parameter(description = "Deskew the input file if set to true") - Boolean deskew, - @RequestParam(name = "clean", required = false) - @Parameter(description = "Clean the input file if set to true") - Boolean clean, - @RequestParam(name = "clean-final", required = false) - @Parameter(description = "Clean the final output if set to true") - Boolean cleanFinal, - @RequestParam(name = "ocrType", required = false) - @Parameter(description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'", schema = @Schema(allowableValues = {"skip-text", "force-ocr", "Normal"})) - String ocrType, - @RequestParam(name = "ocrRenderType", required = false, defaultValue = "hocr") - @Parameter(description = "Specify the OCR render type, either 'hocr' or 'sandwich'", schema = @Schema(allowableValues = {"hocr", "sandwich"})) - String ocrRenderType, - @RequestParam(name = "removeImagesAfter", required = false) - @Parameter(description = "Remove images from the output PDF if set to true") - Boolean removeImagesAfter) throws IOException, InterruptedException { - + public ResponseEntity processPdfWithOCR(@ModelAttribute ProcessPdfWithOcrRequest request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + List selectedLanguages = request.getLanguages(); + Boolean sidecar = request.isSidecar(); + Boolean deskew = request.isDeskew(); + Boolean clean = request.isClean(); + Boolean cleanFinal = request.isCleanFinal(); + 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."); diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/OverlayImageController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java similarity index 57% rename from src/main/java/stirling/software/SPDF/controller/api/other/OverlayImageController.java rename to src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java index 61768f6a..e28f7535 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/OverlayImageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller.api.other; +package stirling.software.SPDF.controller.api.misc; import java.io.IOException; @@ -6,20 +6,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.misc.OverlayImageRequest; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.WebResponseUtils; @RestController -@Tag(name = "Other", description = "Other APIs") +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class OverlayImageController { private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class); @@ -29,22 +30,12 @@ public class OverlayImageController { summary = "Overlay image onto a PDF file", description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:MF-SISO" ) - public ResponseEntity overlayImage( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to overlay the image onto.", required = true) - MultipartFile pdfFile, - @RequestParam("fileInput2") - @Parameter(description = "The image file to be overlaid onto the PDF.", required = true) - MultipartFile imageFile, - @RequestParam("x") - @Parameter(description = "The x-coordinate at which to place the top-left corner of the image.", example = "0") - float x, - @RequestParam("y") - @Parameter(description = "The y-coordinate at which to place the top-left corner of the image.", example = "0") - float y, - @RequestParam("everyPage") - @Parameter(description = "Whether to overlay the image onto every page of the PDF.", example = "false") - boolean everyPage) { + public ResponseEntity overlayImage(@ModelAttribute OverlayImageRequest request) { + MultipartFile pdfFile = request.getFileInput(); + MultipartFile imageFile = request.getImageFile(); + float x = request.getX(); + float y = request.getY(); + boolean everyPage = request.isEveryPage(); try { byte[] pdfBytes = pdfFile.getBytes(); byte[] imageBytes = imageFile.getBytes(); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java new file mode 100644 index 00000000..61a1ec97 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java @@ -0,0 +1,135 @@ +package stirling.software.SPDF.controller.api.misc; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest; +import stirling.software.SPDF.utils.GeneralUtils; +import stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") +public class PageNumbersController { + + private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class); + + @PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data") + @Operation(summary = "Add page numbers to a PDF document", description = "This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO") + public ResponseEntity addPageNumbers(@ModelAttribute AddPageNumbersRequest request) throws IOException { + MultipartFile file = request.getFileInput(); + String customMargin = request.getCustomMargin(); + int position = request.getPosition(); + int startingNumber = request.getStartingNumber(); + String pagesToNumber = request.getPagesToNumber(); + String customText = request.getCustomText(); + int pageNumber = startingNumber; + byte[] fileBytes = file.getBytes(); + PDDocument document = PDDocument.load(fileBytes); + + float marginFactor; + switch (customMargin.toLowerCase()) { + case "small": + marginFactor = 0.02f; + break; + case "medium": + marginFactor = 0.035f; + break; + case "large": + marginFactor = 0.05f; + break; + case "x-large": + marginFactor = 0.075f; + break; + + + default: + marginFactor = 0.035f; + break; + } + + float fontSize = 12.0f; + PDType1Font font = PDType1Font.HELVETICA; + if(pagesToNumber == null || pagesToNumber.length() == 0) { + pagesToNumber = "all"; + } + if(customText == null || customText.length() == 0) { + customText = "{n}"; + } + List pagesToNumberList = GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages()); + + for (int i : pagesToNumberList) { + PDPage page = document.getPage(i); + PDRectangle pageSize = page.getMediaBox(); + + String text = customText != null ? customText.replace("{n}", String.valueOf(pageNumber)).replace("{total}", String.valueOf(document.getNumberOfPages())).replace("{filename}", file.getOriginalFilename().replaceFirst("[.][^.]+$", "")) : String.valueOf(pageNumber); + + float x, y; + + int xGroup = (position - 1) % 3; + int yGroup = 2 - (position - 1) / 3; + + switch (xGroup) { + case 0: // left + x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth(); + break; + case 1: // center + x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2); + break; + default: // right + x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth(); + break; + } + + switch (yGroup) { + case 0: // bottom + y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight(); + break; + case 1: // middle + y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2); + break; + default: // top + y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight(); + break; + } + + PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true); + contentStream.beginText(); + contentStream.setFont(font, fontSize); + contentStream.newLineAtOffset(x, y); + contentStream.showText(text); + contentStream.endText(); + contentStream.close(); + + pageNumber++; + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + document.close(); + + return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", MediaType.APPLICATION_PDF); + + } + + + +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/RepairController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java similarity index 82% rename from src/main/java/stirling/software/SPDF/controller/api/other/RepairController.java rename to src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java index 52644080..f9ae541d 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/RepairController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller.api.other; +package stirling.software.SPDF.controller.api.misc; import java.io.IOException; import java.nio.file.Files; @@ -9,20 +9,22 @@ import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController -@Tag(name = "Other", description = "Other APIs") +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class RepairController { private static final Logger logger = LoggerFactory.getLogger(RepairController.class); @@ -32,11 +34,8 @@ public class RepairController { summary = "Repair a PDF file", 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" ) - public ResponseEntity repairPdf( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to be repaired", required = true) - MultipartFile inputFile) throws IOException, InterruptedException { - + public ResponseEntity repairPdf(@ModelAttribute PDFFile request) throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); inputFile.transferTo(tempInputFile.toFile()); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java new file mode 100644 index 00000000..27431346 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java @@ -0,0 +1,61 @@ +package stirling.software.SPDF.controller.api.misc; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.PDFFile; +import stirling.software.SPDF.utils.WebResponseUtils; +@RestController +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") +public class ShowJavascript { + + private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class); + @PostMapping(consumes = "multipart/form-data", value = "/show-javascript") + public ResponseEntity extractHeader(@ModelAttribute PDFFile request) throws Exception { + MultipartFile inputFile = request.getFileInput(); + String script = ""; + + try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { + + if(document.getDocumentCatalog() != null && document.getDocumentCatalog().getNames() != null) { + PDNameTreeNode jsTree = document.getDocumentCatalog().getNames().getJavaScript(); + + if (jsTree != null) { + Map jsEntries = jsTree.getNames(); + + for (Map.Entry entry : jsEntries.entrySet()) { + String name = entry.getKey(); + PDActionJavaScript jsAction = entry.getValue(); + String jsCodeStr = jsAction.getAction(); + + script += "// File: " + inputFile.getOriginalFilename() + ", Script: " + name + "\n" + jsCodeStr + "\n"; + } + } + } + + if (script.isEmpty()) { + script = "PDF '" + inputFile.getOriginalFilename() + "' does not contain Javascript"; + } + + return WebResponseUtils.bytesToWebResponse(script.getBytes(StandardCharsets.UTF_8), inputFile.getOriginalFilename() + ".js"); + } + } + + + + +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/PageNumbersController.java b/src/main/java/stirling/software/SPDF/controller/api/other/PageNumbersController.java deleted file mode 100644 index 9096e64e..00000000 --- a/src/main/java/stirling/software/SPDF/controller/api/other/PageNumbersController.java +++ /dev/null @@ -1,174 +0,0 @@ -package stirling.software.SPDF.controller.api.other; - -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import stirling.software.SPDF.utils.GeneralUtils; -import stirling.software.SPDF.utils.PdfUtils; -import stirling.software.SPDF.utils.WebResponseUtils; -import org.apache.pdfbox.pdmodel.*; -import org.apache.pdfbox.pdmodel.common.*; -import org.apache.pdfbox.pdmodel.PDPageContentStream.*; -import org.springframework.web.bind.annotation.*; -import org.springframework.http.*; -import org.springframework.web.multipart.MultipartFile; -import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.media.*; -import io.swagger.v3.oas.annotations.parameters.*; -import org.apache.pdfbox.pdmodel.font.PDType1Font; -import org.apache.tomcat.util.http.ResponseUtil; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URLEncoder; -import java.util.List; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.multipart.MultipartFile; - -import com.itextpdf.io.font.constants.StandardFonts; -import com.itextpdf.kernel.font.PdfFont; -import com.itextpdf.kernel.font.PdfFontFactory; -import com.itextpdf.kernel.geom.Rectangle; -import com.itextpdf.kernel.pdf.PdfReader; -import com.itextpdf.kernel.pdf.PdfWriter; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfPage; -import com.itextpdf.kernel.pdf.canvas.PdfCanvas; -import com.itextpdf.layout.Canvas; -import com.itextpdf.layout.element.Paragraph; -import com.itextpdf.layout.properties.TextAlignment; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Schema; - -import java.io.*; - -@RestController -@Tag(name = "Other", description = "Other APIs") -public class PageNumbersController { - - private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class); - - @PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data") - @Operation(summary = "Add page numbers to a PDF document", description = "This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO") - public ResponseEntity addPageNumbers( - @Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file, - @Parameter(description = "Custom margin: small/medium/large", required = true, schema = @Schema(type = "string", allowableValues = {"small", "medium", "large"})) @RequestParam("customMargin") String customMargin, - @Parameter(description = "Position: 1 of 9 positions", required = true, schema = @Schema(type = "integer", minimum = "1", maximum = "9")) @RequestParam("position") int position, - @Parameter(description = "Starting number", required = true, schema = @Schema(type = "integer", minimum = "1")) @RequestParam("startingNumber") int startingNumber, - @Parameter(description = "Which pages to number, default all", required = false, schema = @Schema(type = "string")) @RequestParam(value = "pagesToNumber", required = false) String pagesToNumber, - @Parameter(description = "Custom text: defaults to just number but can have things like \"Page {n} of {p}\"", required = false, schema = @Schema(type = "string")) @RequestParam(value = "customText", required = false) String customText) - throws IOException { - - byte[] fileBytes = file.getBytes(); - ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes); - - int pageNumber = startingNumber; - float marginFactor; - switch (customMargin.toLowerCase()) { - case "small": - marginFactor = 0.02f; - break; - case "medium": - marginFactor = 0.035f; - break; - case "large": - marginFactor = 0.05f; - break; - case "x-large": - marginFactor = 0.1f; - break; - default: - marginFactor = 0.035f; - break; - } - - float fontSize = 12.0f; - - PdfReader reader = new PdfReader(bais); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PdfWriter writer = new PdfWriter(baos); - - PdfDocument pdfDoc = new PdfDocument(reader, writer); - - List pagesToNumberList = GeneralUtils.parsePageList(pagesToNumber.split(","), pdfDoc.getNumberOfPages()); - - for (int i : pagesToNumberList) { - PdfPage page = pdfDoc.getPage(i+1); - Rectangle pageSize = page.getPageSize(); - PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), pdfDoc); - - String text = customText != null ? customText.replace("{n}", String.valueOf(pageNumber)).replace("{total}", String.valueOf(pdfDoc.getNumberOfPages())) : String.valueOf(pageNumber); - - PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA); - float textWidth = font.getWidth(text, fontSize); - float textHeight = font.getAscent(text, fontSize) - font.getDescent(text, fontSize); - - float x, y; - TextAlignment alignment; - - int xGroup = (position - 1) % 3; - int yGroup = 2 - (position - 1) / 3; - - switch (xGroup) { - case 0: // left - x = pageSize.getLeft() + marginFactor * pageSize.getWidth(); - alignment = TextAlignment.LEFT; - break; - case 1: // center - x = pageSize.getLeft() + (pageSize.getWidth()) / 2; - alignment = TextAlignment.CENTER; - break; - default: // right - x = pageSize.getRight() - marginFactor * pageSize.getWidth(); - alignment = TextAlignment.RIGHT; - break; - } - - switch (yGroup) { - case 0: // bottom - y = pageSize.getBottom() + marginFactor * pageSize.getHeight(); - break; - case 1: // middle - y = pageSize.getBottom() + (pageSize.getHeight() ) / 2; - break; - default: // top - y = pageSize.getTop() - marginFactor * pageSize.getHeight(); - break; - } - - new Canvas(pdfCanvas, page.getPageSize()) - .showTextAligned(new Paragraph(text).setFont(font).setFontSize(fontSize), x, y, alignment); - - pageNumber++; - } - - - pdfDoc.close(); - byte[] resultBytes = baos.toByteArray(); - - return WebResponseUtils.bytesToWebResponse(resultBytes, URLEncoder.encode(file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", "UTF-8"), MediaType.APPLICATION_PDF); - - } - - - -} diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/ShowJavascript.java b/src/main/java/stirling/software/SPDF/controller/api/other/ShowJavascript.java deleted file mode 100644 index 49ce28c9..00000000 --- a/src/main/java/stirling/software/SPDF/controller/api/other/ShowJavascript.java +++ /dev/null @@ -1,141 +0,0 @@ -package stirling.software.SPDF.controller.api.other; - -import java.io.IOException; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import stirling.software.SPDF.utils.GeneralUtils; -import stirling.software.SPDF.utils.PdfUtils; -import stirling.software.SPDF.utils.WebResponseUtils; -import org.apache.pdfbox.pdmodel.*; -import org.apache.pdfbox.pdmodel.common.*; -import org.apache.pdfbox.pdmodel.PDPageContentStream.*; -import org.springframework.web.bind.annotation.*; -import org.springframework.http.*; -import org.springframework.web.multipart.MultipartFile; -import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.media.*; -import io.swagger.v3.oas.annotations.parameters.*; -import org.apache.pdfbox.pdmodel.font.PDType1Font; -import org.apache.pdfbox.text.TextPosition; -import org.apache.tomcat.util.http.ResponseUtil; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.ArrayList; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.multipart.MultipartFile; - -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.itextpdf.io.font.constants.StandardFonts; -import com.itextpdf.kernel.font.PdfFont; -import com.itextpdf.kernel.font.PdfFontFactory; -import com.itextpdf.kernel.geom.Rectangle; -import com.itextpdf.kernel.pdf.PdfReader; -import com.itextpdf.kernel.pdf.PdfStream; -import com.itextpdf.kernel.pdf.PdfWriter; -import com.itextpdf.kernel.pdf.PdfArray; -import com.itextpdf.kernel.pdf.PdfDictionary; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfName; -import com.itextpdf.kernel.pdf.PdfObject; -import com.itextpdf.kernel.pdf.PdfPage; -import com.itextpdf.kernel.pdf.canvas.PdfCanvas; -import com.itextpdf.layout.Canvas; -import com.itextpdf.layout.element.Paragraph; -import com.itextpdf.layout.properties.TextAlignment; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Schema; - -import java.io.*; -import org.apache.pdfbox.pdmodel.*; -import org.apache.pdfbox.text.*; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.media.Schema; -import org.springframework.http.ResponseEntity; -@RestController -@Tag(name = "Other", description = "Other APIs") -public class ShowJavascript { - - private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class); - @PostMapping(consumes = "multipart/form-data", value = "/show-javascript") - @Operation(summary = "Extract header from PDF file", description = "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO") - public ResponseEntity extractHeader( - @RequestPart(value = "fileInput") @Parameter(description = "The input PDF file from which the javascript is to be extracted.", required = true) MultipartFile inputFile) - throws Exception { - - try ( - PdfDocument itextDoc = new PdfDocument(new PdfReader(inputFile.getInputStream())) - ) { - - String name = ""; - String script = ""; - String entryName = "File: "+inputFile.getOriginalFilename() + ", Script: "; - //Javascript - PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names); - if (namesDict != null) { - PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript); - if (javascriptDict != null) { - - PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names); - for (int i = 0; i < namesArray.size(); i += 2) { - if(namesArray.getAsString(i) != null) - name = namesArray.getAsString(i).toString(); - - PdfObject jsCode = namesArray.get(i+1); - if (jsCode instanceof PdfStream) { - byte[] jsCodeBytes = ((PdfStream)jsCode).getBytes(); - String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8); - script = "//" + entryName + name + "\n" +jsCodeStr; - - } else if (jsCode instanceof PdfDictionary) { - // If the JS code is in a dictionary, you'll need to know the key to use. - // Assuming the key is PdfName.JS: - PdfStream jsCodeStream = ((PdfDictionary)jsCode).getAsStream(PdfName.JS); - if (jsCodeStream != null) { - byte[] jsCodeBytes = jsCodeStream.getBytes(); - String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8); - script = "//" + entryName + name + "\n" +jsCodeStr; - } - } - } - - } - } - if(script.equals("")) { - script = "PDF '" +inputFile.getOriginalFilename() + "' does not contain Javascript"; - } - return WebResponseUtils.bytesToWebResponse(script.getBytes(), name + ".js"); - } - - } - - - - -} diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java index 244cb807..c12fe724 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java @@ -3,8 +3,10 @@ package stirling.software.SPDF.controller.api.pipeline; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; @@ -20,8 +22,7 @@ import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; -import java.io.FileOutputStream; -import java.io.OutputStream; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -36,9 +37,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; @@ -47,11 +48,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineOperation; +import stirling.software.SPDF.model.api.HandleDataRequest; import stirling.software.SPDF.utils.WebResponseUtils; @RestController +@RequestMapping("/api/v1/pipeline") @Tag(name = "Pipeline", description = "Pipeline APIs") public class PipelineController { @@ -91,6 +95,10 @@ public class PipelineController { } } + @Autowired + ApplicationProperties applicationProperties; + + private void handleDirectory(Path dir) throws Exception { logger.info("Handling directory: {}", dir); Path jsonFile = dir.resolve(jsonFileName); @@ -182,8 +190,7 @@ public class PipelineController { // {filename} {folder} {date} {tmime} {pipeline} String outputDir = config.getOutputDir(); - // Check if the environment variable 'automatedOutputFolder' is set - String outputFolder = System.getenv("automatedOutputFolder"); + String outputFolder = applicationProperties.getAutoPipeline().getOutputFolder(); if (outputFolder == null || outputFolder.isEmpty()) { // If the environment variable is not set, use the default value @@ -413,8 +420,9 @@ public class PipelineController { } @PostMapping("/handleData") - public ResponseEntity handleData(@RequestPart("fileInput") MultipartFile[] files, - @RequestParam("json") String jsonString) { + public ResponseEntity handleData(@ModelAttribute HandleDataRequest request) { + MultipartFile[] files = request.getFileInputs(); + String jsonString = request.getJsonString(); logger.info("Received POST request to /handleData with {} files", files.length); try { List outputFiles = handleFiles(files, jsonString); diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index 55000dc7..b0e5f2aa 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -3,294 +3,256 @@ package stirling.software.SPDF.controller.api.security; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.security.KeyFactory; import java.security.KeyStore; -import java.security.Principal; import java.security.PrivateKey; import java.security.Security; -import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.PKCS8EncodedKeySpec; import java.text.SimpleDateFormat; -import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; import java.util.Date; -import java.util.List; +import org.apache.commons.io.IOUtils; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; +import org.bouncycastle.cert.jcajce.JcaCertStore; +import org.bouncycastle.cms.CMSProcessableByteArray; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.CMSSignedDataGenerator; +import org.bouncycastle.cms.CMSTypedData; +import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.util.io.pem.PemReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import com.itextpdf.io.font.constants.StandardFonts; -import com.itextpdf.kernel.font.PdfFont; -import com.itextpdf.kernel.font.PdfFontFactory; -import com.itextpdf.kernel.geom.Rectangle; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfPage; -import com.itextpdf.kernel.pdf.PdfReader; -import com.itextpdf.kernel.pdf.StampingProperties; -import com.itextpdf.signatures.BouncyCastleDigest; -import com.itextpdf.signatures.DigestAlgorithms; -import com.itextpdf.signatures.IExternalDigest; -import com.itextpdf.signatures.IExternalSignature; -import com.itextpdf.signatures.PdfPKCS7; -import com.itextpdf.signatures.PdfSignatureAppearance; -import com.itextpdf.signatures.PdfSigner; -import com.itextpdf.signatures.PrivateKeySignature; -import com.itextpdf.signatures.SignatureUtil; - import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController +@RequestMapping("/api/v1/security") @Tag(name = "Security", description = "Security APIs") public class CertSignController { - private static final Logger logger = LoggerFactory.getLogger(CertSignController.class); + private static final Logger logger = LoggerFactory.getLogger(CertSignController.class); - static { - Security.addProvider(new BouncyCastleProvider()); - } + static { + Security.addProvider(new BouncyCastleProvider()); + } - @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:MF-SISO") - public ResponseEntity signPDF( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to be signed") - MultipartFile pdf, + @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:MF-SISO") + public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) throws Exception { + MultipartFile pdf = request.getFileInput(); + String certType = request.getCertType(); + MultipartFile privateKeyFile = request.getPrivateKeyFile(); + MultipartFile certFile = request.getCertFile(); + MultipartFile p12File = request.getP12File(); + String password = request.getPassword(); + Boolean showSignature = request.isShowSignature(); + String reason = request.getReason(); + String location = request.getLocation(); + String name = request.getName(); + Integer pageNumber = request.getPageNumber(); - @RequestParam(value = "certType", required = false) - @Parameter(description = "The type of the digital certificate", schema = @Schema(allowableValues = {"PKCS12", "PEM"})) - String certType, + PrivateKey privateKey = null; + X509Certificate cert = null; - @RequestParam(value = "key", required = false) - @Parameter(description = "The private key for the digital certificate (required for PEM type certificates)") - MultipartFile privateKeyFile, + if (certType != null) { + logger.info("Cert type provided: {}", certType); + switch (certType) { + case "PKCS12": + if (p12File != null) { + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray()); + String alias = ks.aliases().nextElement(); + if (!ks.isKeyEntry(alias)) { + throw new IllegalArgumentException("The provided PKCS12 file does not contain a private key."); + } + privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray()); + cert = (X509Certificate) ks.getCertificate(alias); + } + break; + case "PEM": + if (privateKeyFile != null && certFile != null) { + // Load private key + KeyFactory keyFactory = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME); + if (isPEM(privateKeyFile.getBytes())) { + privateKey = keyFactory + .generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes()))); + } else { + privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes())); + } - @RequestParam(value = "cert", required = false) - @Parameter(description = "The digital certificate (required for PEM type certificates)") - MultipartFile certFile, + // Load certificate + CertificateFactory certFactory = CertificateFactory.getInstance("X.509", + BouncyCastleProvider.PROVIDER_NAME); + if (isPEM(certFile.getBytes())) { + cert = (X509Certificate) certFactory + .generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes()))); + } else { + cert = (X509Certificate) certFactory + .generateCertificate(new ByteArrayInputStream(certFile.getBytes())); + } + } + break; + } + } + PDSignature signature = new PDSignature(); + signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter + signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_SHA1); + signature.setName(name); + signature.setLocation(location); + signature.setReason(reason); + signature.setSignDate(Calendar.getInstance()); + + // Load the PDF + try (PDDocument document = PDDocument.load(pdf.getBytes())) { + logger.info("Successfully loaded the provided PDF"); + SignatureOptions signatureOptions = new SignatureOptions(); - @RequestParam(value = "p12", required = false) - @Parameter(description = "The PKCS12 keystore file (required for PKCS12 type certificates)") - MultipartFile p12File, + // If you want to show the signature - @RequestParam(value = "password", required = false) - @Parameter(description = "The password for the keystore or the private key") - String password, + // ATTEMPT 2 + if (showSignature != null && showSignature) { + PDPage page = document.getPage(pageNumber - 1); - @RequestParam(value = "showSignature", required = false) - @Parameter(description = "Whether to visually show the signature in the PDF file") - Boolean showSignature, + PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); + if (acroForm == null) { + acroForm = new PDAcroForm(document); + document.getDocumentCatalog().setAcroForm(acroForm); + } - @RequestParam(value = "reason", required = false) - @Parameter(description = "The reason for signing the PDF") - String reason, + // Create a new signature field and widget - @RequestParam(value = "location", required = false) - @Parameter(description = "The location where the PDF is signed") - String location, + PDSignatureField signatureField = new PDSignatureField(acroForm); + PDAnnotationWidget widget = signatureField.getWidgets().get(0); + PDRectangle rect = new PDRectangle(100, 100, 200, 50); // Define the rectangle size here + widget.setRectangle(rect); + page.getAnnotations().add(widget); - @RequestParam(value = "name", required = false) - @Parameter(description = "The name of the signer") - String name, +// Set the appearance for the signature field + PDAppearanceDictionary appearanceDict = new PDAppearanceDictionary(); + PDAppearanceStream appearanceStream = new PDAppearanceStream(document); + appearanceStream.setResources(new PDResources()); + appearanceStream.setBBox(rect); + appearanceDict.setNormalAppearance(appearanceStream); + widget.setAppearance(appearanceDict); - @RequestParam(value = "pageNumber", required = false) - @Parameter(description = "The page number where the signature should be visible. This is required if showSignature is set to true") - Integer pageNumber) throws Exception { - - BouncyCastleProvider provider = new BouncyCastleProvider(); - Security.addProvider(provider); + try (PDPageContentStream contentStream = new PDPageContentStream(document, appearanceStream)) { + contentStream.beginText(); + contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12); + contentStream.newLineAtOffset(110, 130); + contentStream.showText("Digitally signed by: " + (name != null ? name : "Unknown")); + contentStream.newLineAtOffset(0, -15); + contentStream.showText("Date: " + new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z").format(new Date())); + contentStream.newLineAtOffset(0, -15); + if (reason != null && !reason.isEmpty()) { + contentStream.showText("Reason: " + reason); + contentStream.newLineAtOffset(0, -15); + } + if (location != null && !location.isEmpty()) { + contentStream.showText("Location: " + location); + contentStream.newLineAtOffset(0, -15); + } + contentStream.endText(); + } - PrivateKey privateKey = null; - X509Certificate cert = null; - - if (certType != null) { - switch (certType) { - case "PKCS12": - if (p12File != null) { - KeyStore ks = KeyStore.getInstance("PKCS12"); - ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray()); - String alias = ks.aliases().nextElement(); - privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray()); - cert = (X509Certificate) ks.getCertificate(alias); - } - break; - case "PEM": - if (privateKeyFile != null && certFile != null) { - // Load private key - KeyFactory keyFactory = KeyFactory.getInstance("RSA", provider); - if (isPEM(privateKeyFile.getBytes())) { - privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes()))); - } else { - privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes())); - } + // Add the widget annotation to the page + page.getAnnotations().add(widget); - // Load certificate - CertificateFactory certFactory = CertificateFactory.getInstance("X.509", provider); - if (isPEM(certFile.getBytes())) { - cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes()))); - } else { - cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certFile.getBytes())); - } - } - break; - } - } + // Add the signature field to the acroform + acroForm.getFields().add(signatureField); - Principal principal = cert.getSubjectDN(); - String dn = principal.getName(); + // Handle multiple signatures by ensuring a unique field name + String baseFieldName = "Signature"; + String signatureFieldName = baseFieldName; + int suffix = 1; + while (acroForm.getField(signatureFieldName) != null) { + suffix++; + signatureFieldName = baseFieldName + suffix; + } + signatureField.setPartialName(signatureFieldName); + } + + document.addSignature(signature, signatureOptions); + logger.info("Signature added to the PDF document"); + // External signing + ExternalSigningSupport externalSigning = document + .saveIncrementalForExternalSigning(new ByteArrayOutputStream()); - // Extract the "CN" (Common Name) field from the distinguished name (if it's present) - String cn = null; - for (String part : dn.split(",")) { - if (part.trim().startsWith("CN=")) { - cn = part.trim().substring("CN=".length()); - break; - } - } - - // Set up the PDF reader and stamper - PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf.getBytes())); - ByteArrayOutputStream signedPdf = new ByteArrayOutputStream(); - PdfSigner signer = new PdfSigner(reader, signedPdf, new StampingProperties()); + byte[] content = IOUtils.toByteArray(externalSigning.getContent()); - // Set up the signing appearance - PdfSignatureAppearance appearance = signer.getSignatureAppearance() - .setReason("Test") - .setLocation("TestLocation"); + // Using BouncyCastle to sign + CMSTypedData cmsData = new CMSProcessableByteArray(content); - if (showSignature != null && showSignature) { - float fontSize = 4; // the font size of the signature - float marginRight = 36; // Margin from the right - float marginBottom = 36; // Margin from the bottom - String signingDate = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z").format(new Date()); + CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); + ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA") + .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(privateKey); - // Prepare the text for the digital signature - StringBuilder layer2TextBuilder = new StringBuilder(String.format("Digitally signed by: %s\nDate: %s", - name != null ? name : "Unknown", signingDate)); + gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder( + new JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build()) + .build(signer, cert)); - if (reason != null && !reason.isEmpty()) { - layer2TextBuilder.append("\nReason: ").append(reason); - } + gen.addCertificates(new JcaCertStore(Collections.singletonList(cert))); + CMSSignedData signedData = gen.generate(cmsData, false); - if (location != null && !location.isEmpty()) { - layer2TextBuilder.append("\nLocation: ").append(location); - } - String layer2Text = layer2TextBuilder.toString(); - // Get the PDF font and measure the width and height of the text block - PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD); - float textWidth = Arrays.stream(layer2Text.split("\n")) - .map(line -> font.getWidth(line, fontSize)) - .max(Float::compare) - .orElse(0f); - int numLines = layer2Text.split("\n").length; - float textHeight = numLines * fontSize; + byte[] cmsSignature = signedData.getEncoded(); + logger.info("About to sign content using BouncyCastle"); + externalSigning.setSignature(cmsSignature); + logger.info("Signature set successfully"); - // Calculate the signature rectangle size - float sigWidth = textWidth + marginRight * 2; - float sigHeight = textHeight + marginBottom * 2; + // After setting the signature, return the resultant PDF + try (ByteArrayOutputStream signedPdfOutput = new ByteArrayOutputStream()) { + document.save(signedPdfOutput); + return WebResponseUtils.boasToWebResponse(signedPdfOutput, + pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_signed.pdf"); - // Get the page size - PdfPage page = signer.getDocument().getPage(1); - Rectangle pageSize = page.getPageSize(); + } catch (Exception e) { + e.printStackTrace(); + } + } catch (Exception e) { + e.printStackTrace(); + } - // Define the position and dimension of the signature field - Rectangle rect = new Rectangle( - pageSize.getRight() - sigWidth - marginRight, - pageSize.getBottom() + marginBottom, - sigWidth, - sigHeight - ); + return null; + } - // Configure the appearance of the digital signature - appearance.setPageRect(rect) - .setContact(name != null ? name : "") - .setPageNumber(pageNumber) - .setReason(reason != null ? reason : "") - .setLocation(location != null ? location : "") - .setReuseAppearance(false) - .setLayer2Text(layer2Text.toString()); - - signer.setFieldName("sig"); - } else { - appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION); - } - - // Set up the signer - PrivateKeySignature pks = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName()); - IExternalSignature pss = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName()); - IExternalDigest digest = new BouncyCastleDigest(); - - // Call iTex7 to sign the PDF - signer.signDetached(digest, pks, new Certificate[] {cert}, null, null, null, 0, PdfSigner.CryptoStandard.CMS); - - - System.out.println("Signed PDF size: " + signedPdf.size()); - - System.out.println("PDF signed = " + isPdfSigned(signedPdf.toByteArray())); - return WebResponseUtils.bytesToWebResponse(signedPdf.toByteArray(), "example.pdf"); - } - -public boolean isPdfSigned(byte[] pdfData) throws IOException { - InputStream pdfStream = new ByteArrayInputStream(pdfData); - PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfStream)); - SignatureUtil signatureUtil = new SignatureUtil(pdfDoc); - List names = signatureUtil.getSignatureNames(); - - boolean isSigned = false; - - for (String name : names) { - PdfPKCS7 pkcs7 = signatureUtil.readSignatureData(name); - if (pkcs7 != null) { - System.out.println("Signature found."); - - // Log certificate details - Certificate[] signChain = pkcs7.getSignCertificateChain(); - for (Certificate cert : signChain) { - if (cert instanceof X509Certificate) { - X509Certificate x509 = (X509Certificate) cert; - System.out.println("Certificate Details:"); - System.out.println("Subject: " + x509.getSubjectDN()); - System.out.println("Issuer: " + x509.getIssuerDN()); - System.out.println("Serial: " + x509.getSerialNumber()); - System.out.println("Not Before: " + x509.getNotBefore()); - System.out.println("Not After: " + x509.getNotAfter()); - } - } - - isSigned = true; - } - } - - pdfDoc.close(); - - return isSigned; -} - private byte[] parsePEM(byte[] content) throws IOException { - PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content))); - return pemReader.readPemObject().getContent(); - } - - private boolean isPEM(byte[] content) { - String contentStr = new String(content); - return contentStr.contains("-----BEGIN") && contentStr.contains("-----END"); - } - - - + private byte[] parsePEM(byte[] content) throws IOException { + PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content))); + return pemReader.readPemObject().getContent(); + } + private boolean isPEM(byte[] content) { + String contentStr = new String(content); + return contentStr.contains("-----BEGIN") && contentStr.contains("-----END"); + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java index 159dd4ab..791dc736 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java @@ -1,78 +1,81 @@ package stirling.software.SPDF.controller.api.security; -import org.apache.pdfbox.cos.COSArray; -import org.apache.pdfbox.cos.COSBase; -import org.apache.pdfbox.cos.COSDictionary; -import org.apache.pdfbox.cos.COSName; -import org.apache.pdfbox.cos.COSString; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDDocumentInformation; -import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.common.PDRectangle; -import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; -import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureNode; -import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; -import org.apache.pdfbox.pdmodel.encryption.PDEncryption; -import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; -import org.apache.pdfbox.pdmodel.interactive.form.PDField; - -import com.itextpdf.kernel.pdf.PdfObject; -import com.itextpdf.kernel.pdf.PdfOutline; -import com.itextpdf.forms.PdfAcroForm; -import com.itextpdf.forms.fields.PdfFormField; -import com.itextpdf.kernel.geom.Rectangle; -import com.itextpdf.kernel.pdf.PdfArray; -import com.itextpdf.kernel.pdf.PdfCatalog; -import com.itextpdf.kernel.pdf.PdfDictionary; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfDocumentInfo; -import com.itextpdf.kernel.pdf.PdfEncryption; -import com.itextpdf.kernel.pdf.PdfReader; -import com.itextpdf.kernel.pdf.PdfResources; -import com.itextpdf.kernel.pdf.PdfStream; -import com.itextpdf.kernel.pdf.PdfString; -import com.itextpdf.kernel.pdf.PdfName; -import com.itextpdf.kernel.pdf.PdfViewerPreferences; -import com.itextpdf.kernel.pdf.PdfWriter; -import com.itextpdf.kernel.pdf.annot.PdfAnnotation; -import com.itextpdf.kernel.pdf.annot.PdfFileAttachmentAnnotation; -import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation; -import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation; -import com.itextpdf.kernel.pdf.layer.PdfLayer; -import com.itextpdf.kernel.pdf.layer.PdfOCProperties; -import com.itextpdf.kernel.xmp.XMPException; -import com.itextpdf.kernel.xmp.XMPMeta; -import com.itextpdf.kernel.xmp.XMPMetaFactory; -import com.itextpdf.kernel.xmp.options.SerializeOptions; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import stirling.software.SPDF.utils.WebResponseUtils; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.apache.pdfbox.text.PDFTextStripper; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.util.HashMap; -import java.io.FileWriter; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.HashSet; + +import org.apache.pdfbox.cos.COSDocument; +import org.apache.pdfbox.cos.COSInputStream; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.cos.COSObject; +import org.apache.pdfbox.cos.COSStream; +import org.apache.pdfbox.cos.COSString; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentCatalog; +import org.apache.pdfbox.pdmodel.PDDocumentInformation; +import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; +import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; +import org.apache.pdfbox.pdmodel.PDJavascriptNameTreeNode; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.common.PDMetadata; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.common.PDStream; +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; +import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureNode; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; +import org.apache.pdfbox.pdmodel.encryption.AccessPermission; +import org.apache.pdfbox.pdmodel.encryption.PDEncryption; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDFontDescriptor; +import org.apache.pdfbox.pdmodel.graphics.PDXObject; +import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace; +import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup; +import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem; +import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.apache.pdfbox.text.PDFTextStripper; +import org.apache.xmpbox.XMPMetadata; +import org.apache.xmpbox.xml.DomXmpParser; +import org.apache.xmpbox.xml.XmpParsingException; +import org.apache.xmpbox.xml.XmpSerializer; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.PDFFile; +import stirling.software.SPDF.utils.WebResponseUtils; @RestController +@RequestMapping("/api/v1/security") @Tag(name = "Security", description = "Security APIs") public class GetInfoOnPDF { @@ -80,14 +83,11 @@ public class GetInfoOnPDF { @PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf") @Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO") - public ResponseEntity getPdfInfo( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to get info on", required = true) MultipartFile inputFile) + public ResponseEntity getPdfInfo(@ModelAttribute PDFFile request) throws IOException { - + MultipartFile inputFile = request.getFileInput(); try ( PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream()); - PdfDocument itextDoc = new PdfDocument(new PdfReader(inputFile.getInputStream())) ) { ObjectMapper objectMapper = new ObjectMapper(); ObjectNode jsonOutput = objectMapper.createObjectNode(); @@ -135,21 +135,17 @@ public class GetInfoOnPDF { boolean hasCompression = false; String compressionType = "None"; - // Check for object streams - for (int i = 1; i <= itextDoc.getNumberOfPdfObjects(); i++) { - PdfObject obj = itextDoc.getPdfObject(i); - if (obj != null && obj.isStream() && ((PdfStream) obj).get(PdfName.Type) == PdfName.ObjStm) { - hasCompression = true; - compressionType = "Object Streams"; - break; + COSDocument cosDoc = pdfBoxDoc.getDocument(); + for (COSObject cosObject : cosDoc.getObjects()) { + if (cosObject.getObject() instanceof COSStream) { + COSStream cosStream = (COSStream) cosObject.getObject(); + if (COSName.OBJ_STM.equals(cosStream.getItem(COSName.TYPE))) { + hasCompression = true; + compressionType = "Object Streams"; + break; + } } } - - // If not compressed using object streams, check for compressed Xref tables - if (!hasCompression && itextDoc.getReader().hasRebuiltXref()) { - hasCompression = true; - compressionType = "Compressed Xref or Rebuilt Xref"; - } basicInfo.put("Compression", hasCompression); if(hasCompression) basicInfo.put("CompressionType", compressionType); @@ -159,9 +155,8 @@ public class GetInfoOnPDF { basicInfo.put("Number of pages", pdfBoxDoc.getNumberOfPages()); - // Page Mode using iText7 - PdfCatalog catalog = itextDoc.getCatalog(); - PdfName pageMode = catalog.getPdfObject().getAsName(PdfName.PageMode); + PDDocumentCatalog catalog = pdfBoxDoc.getDocumentCatalog(); + String pageMode = catalog.getPageMode().name(); // Document Information using PDFBox docInfoNode.put("PDF version", pdfBoxDoc.getVersion()); @@ -172,49 +167,56 @@ public class GetInfoOnPDF { - PdfAcroForm acroForm = PdfAcroForm.getAcroForm(itextDoc, false); + PDAcroForm acroForm = pdfBoxDoc.getDocumentCatalog().getAcroForm(); + ObjectNode formFieldsNode = objectMapper.createObjectNode(); if (acroForm != null) { - for (Map.Entry entry : acroForm.getFormFields().entrySet()) { - formFieldsNode.put(entry.getKey(), entry.getValue().getValueAsString()); + for (PDField field : acroForm.getFieldTree()) { + formFieldsNode.put(field.getFullyQualifiedName(), field.getValueAsString()); } } jsonOutput.set("FormFields", formFieldsNode); + //embeed files TODO size - ArrayNode embeddedFilesArray = objectMapper.createArrayNode(); - if(itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names) != null) - { - PdfDictionary embeddedFiles = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names) - .getAsDictionary(PdfName.EmbeddedFiles); - if (embeddedFiles != null) { - - PdfArray namesArray = embeddedFiles.getAsArray(PdfName.Names); - if(namesArray != null) { - for (int i = 0; i < namesArray.size(); i += 2) { - ObjectNode embeddedFileNode = objectMapper.createObjectNode(); - embeddedFileNode.put("Name", namesArray.getAsString(i).toString()); - // Add other details if required - embeddedFilesArray.add(embeddedFileNode); + if(catalog.getNames() != null) { + PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles(); + + ArrayNode embeddedFilesArray = objectMapper.createArrayNode(); + if (efTree != null) { + Map efMap = efTree.getNames(); + if (efMap != null) { + for (Map.Entry entry : efMap.entrySet()) { + ObjectNode embeddedFileNode = objectMapper.createObjectNode(); + embeddedFileNode.put("Name", entry.getKey()); + PDEmbeddedFile embeddedFile = entry.getValue().getEmbeddedFile(); + if (embeddedFile != null) { + embeddedFileNode.put("FileSize", embeddedFile.getLength()); // size in bytes + } + embeddedFilesArray.add(embeddedFileNode); + } } - } - + } + other.set("EmbeddedFiles", embeddedFilesArray); } - } - other.set("EmbeddedFiles", embeddedFilesArray); + + //attachments TODO size ArrayNode attachmentsArray = objectMapper.createArrayNode(); - for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) { - for (PdfAnnotation annotation : itextDoc.getPage(pageNum).getAnnotations()) { - if (annotation instanceof PdfFileAttachmentAnnotation) { + for (PDPage page : pdfBoxDoc.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationFileAttachment) { + PDAnnotationFileAttachment fileAttachmentAnnotation = (PDAnnotationFileAttachment) annotation; + ObjectNode attachmentNode = objectMapper.createObjectNode(); - attachmentNode.put("Name", ((PdfFileAttachmentAnnotation) annotation).getName().toString()); - attachmentNode.put("Description", annotation.getContents().getValue()); + attachmentNode.put("Name", fileAttachmentAnnotation.getAttachmentName()); + attachmentNode.put("Description", fileAttachmentAnnotation.getContents()); + attachmentsArray.add(attachmentNode); } } @@ -222,65 +224,54 @@ public class GetInfoOnPDF { other.set("Attachments", attachmentsArray); //Javascript - PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names); + PDDocumentNameDictionary namesDict = catalog.getNames(); ArrayNode javascriptArray = objectMapper.createArrayNode(); + if (namesDict != null) { - PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript); + PDJavascriptNameTreeNode javascriptDict = namesDict.getJavaScript(); if (javascriptDict != null) { + try { + Map jsEntries = javascriptDict.getNames(); - PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names); - for (int i = 0; i < namesArray.size(); i += 2) { - ObjectNode jsNode = objectMapper.createObjectNode(); - if(namesArray.getAsString(i) != null) - jsNode.put("JS Name", namesArray.getAsString(i).toString()); + for (Map.Entry entry : jsEntries.entrySet()) { + ObjectNode jsNode = objectMapper.createObjectNode(); + jsNode.put("JS Name", entry.getKey()); - // Here we check for a PdfStream object and retrieve the JS code from it - PdfObject jsCode = namesArray.get(i+1); - if (jsCode instanceof PdfStream) { - byte[] jsCodeBytes = ((PdfStream)jsCode).getBytes(); - String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8); - jsNode.put("JS Script Length", jsCodeStr.length()); - } else if (jsCode instanceof PdfDictionary) { - // If the JS code is in a dictionary, you'll need to know the key to use. - // Assuming the key is PdfName.JS: - PdfStream jsCodeStream = ((PdfDictionary)jsCode).getAsStream(PdfName.JS); - if (jsCodeStream != null) { - byte[] jsCodeBytes = jsCodeStream.getBytes(); - String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8); - jsNode.put("JS Script Character Length", jsCodeStr.length()); + PDActionJavaScript jsAction = entry.getValue(); + if (jsAction != null) { + String jsCodeStr = jsAction.getAction(); + if (jsCodeStr != null) { + jsNode.put("JS Script Length", jsCodeStr.length()); + } } + + javascriptArray.add(jsNode); } - - javascriptArray.add(jsNode); + } catch (IOException e) { + e.printStackTrace(); } - } } other.set("JavaScript", javascriptArray); + //TODO size - PdfOCProperties ocProperties = itextDoc.getCatalog().getOCProperties(false); + PDOptionalContentProperties ocProperties = pdfBoxDoc.getDocumentCatalog().getOCProperties(); ArrayNode layersArray = objectMapper.createArrayNode(); + if (ocProperties != null) { - - for (PdfLayer layer : ocProperties.getLayers()) { + for (PDOptionalContentGroup ocg : ocProperties.getOptionalContentGroups()) { ObjectNode layerNode = objectMapper.createObjectNode(); - layerNode.put("Name", layer.getPdfObject().getAsString(PdfName.Name).toString()); + layerNode.put("Name", ocg.getName()); layersArray.add(layerNode); } - } + other.set("Layers", layersArray); //TODO Security - - - - - // Digital Signatures using iText7 TODO - @@ -297,13 +288,13 @@ public class GetInfoOnPDF { } - boolean isPdfACompliant = checkOutputIntent(itextDoc, "PDF/A"); - boolean isPdfXCompliant = checkOutputIntent(itextDoc, "PDF/X"); - boolean isPdfECompliant = checkForStandard(itextDoc, "PDF/E"); - boolean isPdfVTCompliant = checkForStandard(itextDoc, "PDF/VT"); - boolean isPdfUACompliant = checkForStandard(itextDoc, "PDF/UA"); - boolean isPdfBCompliant = checkForStandard(itextDoc, "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't an official ISO standard. - boolean isPdfSECCompliant = checkForStandard(itextDoc, "PDF/SEC"); // This might not be effective since PDF/SEC was under development in 2021. + boolean isPdfACompliant = checkForStandard(pdfBoxDoc, "PDF/A"); + boolean isPdfXCompliant = checkForStandard(pdfBoxDoc, "PDF/X"); + boolean isPdfECompliant = checkForStandard(pdfBoxDoc, "PDF/E"); + boolean isPdfVTCompliant = checkForStandard(pdfBoxDoc, "PDF/VT"); + boolean isPdfUACompliant = checkForStandard(pdfBoxDoc, "PDF/UA"); + boolean isPdfBCompliant = checkForStandard(pdfBoxDoc, "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't an official ISO standard. + boolean isPdfSECCompliant = checkForStandard(pdfBoxDoc, "PDF/SEC"); // This might not be effective since PDF/SEC was under development in 2021. compliancy.put("IsPDF/ACompliant", isPdfACompliant); compliancy.put("IsPDF/XCompliant", isPdfXCompliant); @@ -317,27 +308,39 @@ public class GetInfoOnPDF { + PDOutlineNode root = pdfBoxDoc.getDocumentCatalog().getDocumentOutline(); ArrayNode bookmarksArray = objectMapper.createArrayNode(); - PdfOutline root = itextDoc.getOutlines(false); + if (root != null) { - for (PdfOutline child : root.getAllChildren()) { + for (PDOutlineItem child : root.children()) { addOutlinesToArray(child, bookmarksArray); } } + other.set("Bookmarks/Outline/TOC", bookmarksArray); - byte[] xmpBytes = itextDoc.getXmpMetadata(); - String xmpString = null; - if (xmpBytes != null) { - try { - XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(xmpBytes); - xmpString = new String(XMPMetaFactory.serializeToBuffer(xmpMeta, new SerializeOptions())); - } catch (XMPException e) { - e.printStackTrace(); - } - } - other.put("XMPMetadata", xmpString); - + + + PDMetadata pdMetadata = pdfBoxDoc.getDocumentCatalog().getMetadata(); + + String xmpString = null; + + if (pdMetadata != null) { + try { + COSInputStream is = pdMetadata.createInputStream(); + DomXmpParser domXmpParser = new DomXmpParser(); + XMPMetadata xmpMeta = domXmpParser.parse(is); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + new XmpSerializer().serialize(xmpMeta, os, true); + xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8); + } catch (XmpParsingException | IOException e) { + e.printStackTrace(); + } + } + + other.put("XMPMetadata", xmpString); + if (pdfBoxDoc.isEncrypted()) { @@ -347,8 +350,21 @@ public class GetInfoOnPDF { PDEncryption pdfEncryption = pdfBoxDoc.getEncryption(); encryption.put("EncryptionAlgorithm", pdfEncryption.getFilter()); encryption.put("KeyLength", pdfEncryption.getLength()); - encryption.put("Permissions", pdfBoxDoc.getCurrentAccessPermission().toString()); - + AccessPermission ap = pdfBoxDoc.getCurrentAccessPermission(); + if (ap != null) { + ObjectNode permissionsNode = objectMapper.createObjectNode(); + + permissionsNode.put("CanAssembleDocument", ap.canAssembleDocument()); + permissionsNode.put("CanExtractContent", ap.canExtractContent()); + permissionsNode.put("CanExtractForAccessibility", ap.canExtractForAccessibility()); + permissionsNode.put("CanFillInForm", ap.canFillInForm()); + permissionsNode.put("CanModify", ap.canModify()); + permissionsNode.put("CanModifyAnnotations", ap.canModifyAnnotations()); + permissionsNode.put("CanPrint", ap.canPrint()); + permissionsNode.put("CanPrintDegraded", ap.canPrintDegraded()); + + encryption.set("Permissions", permissionsNode); // set the node under "Permissions" + } // Add other encryption-related properties as needed } else { encryption.put("IsEncrypted", false); @@ -358,43 +374,65 @@ public class GetInfoOnPDF { ObjectNode pageInfoParent = objectMapper.createObjectNode(); - for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) { + for (int pageNum = 0; pageNum < pdfBoxDoc.getNumberOfPages(); pageNum++) { ObjectNode pageInfo = objectMapper.createObjectNode(); + // Retrieve the page + PDPage page = pdfBoxDoc.getPage(pageNum); + // Page-level Information - Rectangle pageSize = itextDoc.getPage(pageNum).getPageSize(); - pageInfo.put("Width", pageSize.getWidth()); - pageInfo.put("Height", pageSize.getHeight()); - pageInfo.put("Rotation", itextDoc.getPage(pageNum).getRotation()); - pageInfo.put("Page Orientation", getPageOrientation(pageSize.getWidth(),pageSize.getHeight())); - pageInfo.put("Standard Size", getPageSize(pageSize.getWidth(),pageSize.getHeight())); + PDRectangle mediaBox = page.getMediaBox(); + + float width = mediaBox.getWidth(); + float height = mediaBox.getHeight(); + + ObjectNode sizeInfo = objectMapper.createObjectNode(); + getDimensionInfo(sizeInfo, width, height); + + sizeInfo.put("Standard Page", getPageSize(width, height)); + pageInfo.set("Size", sizeInfo); + + pageInfo.put("Rotation", page.getRotation()); + pageInfo.put("Page Orientation", getPageOrientation(width, height)); + + // Boxes - pageInfo.put("MediaBox", itextDoc.getPage(pageNum).getMediaBox().toString()); - pageInfo.put("CropBox", itextDoc.getPage(pageNum).getCropBox().toString()); - pageInfo.put("BleedBox", itextDoc.getPage(pageNum).getBleedBox().toString()); - pageInfo.put("TrimBox", itextDoc.getPage(pageNum).getTrimBox().toString()); - pageInfo.put("ArtBox", itextDoc.getPage(pageNum).getArtBox().toString()); + pageInfo.put("MediaBox", mediaBox.toString()); + + // Assuming the following boxes are defined for your document; if not, you may get null values. + PDRectangle cropBox = page.getCropBox(); + pageInfo.put("CropBox", cropBox == null ? "Undefined" : cropBox.toString()); + + PDRectangle bleedBox = page.getBleedBox(); + pageInfo.put("BleedBox", bleedBox == null ? "Undefined" : bleedBox.toString()); + + PDRectangle trimBox = page.getTrimBox(); + pageInfo.put("TrimBox", trimBox == null ? "Undefined" : trimBox.toString()); + + PDRectangle artBox = page.getArtBox(); + pageInfo.put("ArtBox", artBox == null ? "Undefined" : artBox.toString()); // Content Extraction PDFTextStripper textStripper = new PDFTextStripper(); - textStripper.setStartPage(pageNum -1); - textStripper.setEndPage(pageNum - 1); + textStripper.setStartPage(pageNum + 1); + textStripper.setEndPage(pageNum +1); String pageText = textStripper.getText(pdfBoxDoc); pageInfo.put("Text Characters Count", pageText.length()); // - // Annotations - List annotations = itextDoc.getPage(pageNum).getAnnotations(); + // Annotations + + List annotations = page.getAnnotations(); int subtypeCount = 0; int contentsCount = 0; - for (PdfAnnotation annotation : annotations) { - if(annotation.getSubtype() != null) { + for (PDAnnotation annotation : annotations) { + if (annotation.getSubtype() != null) { subtypeCount++; // Increase subtype count } - if(annotation.getContents() != null) { + if (annotation.getContents() != null) { contentsCount++; // Increase contents count } } @@ -405,25 +443,31 @@ public class GetInfoOnPDF { annotationsObject.put("ContentsCount", contentsCount); pageInfo.set("Annotations", annotationsObject); + + // Images (simplified) // This part is non-trivial as images can be embedded in multiple ways in a PDF. // Here is a basic structure to recognize image XObjects on a page. ArrayNode imagesArray = objectMapper.createArrayNode(); - PdfResources resources = itextDoc.getPage(pageNum).getResources(); - for (PdfName name : resources.getResourceNames()) { - PdfObject obj = resources.getResource(name); - if (obj instanceof PdfStream) { - PdfStream stream = (PdfStream) obj; - if (PdfName.Image.equals(stream.getAsName(PdfName.Subtype))) { - ObjectNode imageNode = objectMapper.createObjectNode(); - imageNode.put("Width", stream.getAsNumber(PdfName.Width).intValue()); - imageNode.put("Height", stream.getAsNumber(PdfName.Height).intValue()); - PdfObject colorSpace = stream.get(PdfName.ColorSpace); - if (colorSpace != null) { - imageNode.put("ColorSpace", colorSpace.toString()); - } - imagesArray.add(imageNode); + PDResources resources = page.getResources(); + + + for (COSName name : resources.getXObjectNames()) { + PDXObject xObject = resources.getXObject(name); + if (xObject instanceof PDImageXObject) { + PDImageXObject image = (PDImageXObject) xObject; + + ObjectNode imageNode = objectMapper.createObjectNode(); + imageNode.put("Width", image.getWidth()); + imageNode.put("Height", image.getHeight()); + if(image.getMetadata() != null && image.getMetadata().getFile() != null && image.getMetadata().getFile().getFile() != null) { + imageNode.put("Name", image.getMetadata().getFile().getFile()); } + if (image.getColorSpace() != null) { + imageNode.put("ColorSpace", image.getColorSpace().getName()); + } + + imagesArray.add(imageNode); } } pageInfo.set("Images", imagesArray); @@ -433,12 +477,13 @@ public class GetInfoOnPDF { ArrayNode linksArray = objectMapper.createArrayNode(); Set uniqueURIs = new HashSet<>(); // To store unique URIs - for (PdfAnnotation annotation : annotations) { - if (annotation instanceof PdfLinkAnnotation) { - PdfLinkAnnotation linkAnnotation = (PdfLinkAnnotation) annotation; - if(linkAnnotation != null && linkAnnotation.getAction() != null) { - String uri = linkAnnotation.getAction().toString(); - uniqueURIs.add(uri); // Add to set to ensure uniqueness + for (PDAnnotation annotation : annotations) { + if (annotation instanceof PDAnnotationLink) { + PDAnnotationLink linkAnnotation = (PDAnnotationLink) annotation; + if (linkAnnotation.getAction() instanceof PDActionURI) { + PDActionURI uriAction = (PDActionURI) linkAnnotation.getAction(); + String uri = uriAction.getURI(); + uniqueURIs.add(uri); // Add to set to ensure uniqueness } } } @@ -451,96 +496,52 @@ public class GetInfoOnPDF { } pageInfo.set("Links", linksArray); - // Fonts + + // Fonts ArrayNode fontsArray = objectMapper.createArrayNode(); - PdfDictionary fontDicts = resources.getResource(PdfName.Font); - Set uniqueSubtypes = new HashSet<>(); // To store unique subtypes - - // Map to store unique fonts and their counts Map uniqueFontsMap = new HashMap<>(); - if (fontDicts != null) { - for (PdfName key : fontDicts.keySet()) { - ObjectNode fontNode = objectMapper.createObjectNode(); // Create a new font node for each font - PdfDictionary font = fontDicts.getAsDictionary(key); + for (COSName fontName : resources.getFontNames()) { + PDFont font = resources.getFont(fontName); + ObjectNode fontNode = objectMapper.createObjectNode(); - boolean isEmbedded = font.containsKey(PdfName.FontFile) || - font.containsKey(PdfName.FontFile2) || - font.containsKey(PdfName.FontFile3); - fontNode.put("IsEmbedded", isEmbedded); + fontNode.put("IsEmbedded", font.isEmbedded()); - if (font.containsKey(PdfName.Encoding)) { - String encoding = font.getAsName(PdfName.Encoding).toString(); - fontNode.put("Encoding", encoding); - } + // PDFBox provides Font's BaseFont (i.e., the font name) directly + fontNode.put("Name", font.getName()); - if (font.getAsString(PdfName.BaseFont) != null) { - fontNode.put("Name", font.getAsString(PdfName.BaseFont).toString()); - } + fontNode.put("Subtype", font.getType()); - String subtype = null; - if (font.containsKey(PdfName.Subtype)) { - subtype = font.getAsName(PdfName.Subtype).toString(); - uniqueSubtypes.add(subtype); // Add to set to ensure uniqueness - } - fontNode.put("Subtype", subtype); + PDFontDescriptor fontDescriptor = font.getFontDescriptor(); - PdfDictionary fontDescriptor = font.getAsDictionary(PdfName.FontDescriptor); - if (fontDescriptor != null) { - if (fontDescriptor.containsKey(PdfName.ItalicAngle)) { - fontNode.put("ItalicAngle", fontDescriptor.getAsNumber(PdfName.ItalicAngle).floatValue()); - } + if (fontDescriptor != null) { + fontNode.put("ItalicAngle", fontDescriptor.getItalicAngle()); + int flags = fontDescriptor.getFlags(); + fontNode.put("IsItalic", (flags & 1) != 0); + fontNode.put("IsBold", (flags & 64) != 0); + fontNode.put("IsFixedPitch", (flags & 2) != 0); + fontNode.put("IsSerif", (flags & 4) != 0); + fontNode.put("IsSymbolic", (flags & 8) != 0); + fontNode.put("IsScript", (flags & 16) != 0); + fontNode.put("IsNonsymbolic", (flags & 32) != 0); - if (fontDescriptor.containsKey(PdfName.Flags)) { - int flags = fontDescriptor.getAsNumber(PdfName.Flags).intValue(); - fontNode.put("IsItalic", (flags & 64) != 0); - fontNode.put("IsBold", (flags & 1 << 16) != 0); - fontNode.put("IsFixedPitch", (flags & 1) != 0); - fontNode.put("IsSerif", (flags & 2) != 0); - fontNode.put("IsSymbolic", (flags & 4) != 0); - fontNode.put("IsScript", (flags & 8) != 0); - fontNode.put("IsNonsymbolic", (flags & 16) != 0); - } + fontNode.put("FontFamily", fontDescriptor.getFontFamily()); + // Font stretch and BBox are not directly available in PDFBox's API, so these are omitted for simplicity + fontNode.put("FontWeight", fontDescriptor.getFontWeight()); + } - if (fontDescriptor.containsKey(PdfName.FontFamily)) { - String fontFamily = fontDescriptor.getAsString(PdfName.FontFamily).toString(); - fontNode.put("FontFamily", fontFamily); - } - if (fontDescriptor.containsKey(PdfName.FontStretch)) { - String fontStretch = fontDescriptor.getAsName(PdfName.FontStretch).toString(); - fontNode.put("FontStretch", fontStretch); - } + // Create a unique key for this font node based on its attributes + String uniqueKey = fontNode.toString(); - if (fontDescriptor.containsKey(PdfName.FontBBox)) { - PdfArray bbox = fontDescriptor.getAsArray(PdfName.FontBBox); - fontNode.put("FontBoundingBox", bbox.toString()); - } - - if (fontDescriptor.containsKey(PdfName.FontWeight)) { - float fontWeight = fontDescriptor.getAsNumber(PdfName.FontWeight).floatValue(); - fontNode.put("FontWeight", fontWeight); - } - } - - if (font.containsKey(PdfName.ToUnicode)) { - fontNode.put("HasToUnicodeMap", true); - } - - if (fontNode.size() > 0) { - // Create a unique key for this font node based on its attributes - String uniqueKey = fontNode.toString(); - - // Increment count if this font exists, or initialize it if new - if (uniqueFontsMap.containsKey(uniqueKey)) { - ObjectNode existingFontNode = uniqueFontsMap.get(uniqueKey); - int count = existingFontNode.get("Count").asInt() + 1; - existingFontNode.put("Count", count); - } else { - fontNode.put("Count", 1); - uniqueFontsMap.put(uniqueKey, fontNode); - } - } + // Increment count if this font exists, or initialize it if new + if (uniqueFontsMap.containsKey(uniqueKey)) { + ObjectNode existingFontNode = uniqueFontsMap.get(uniqueKey); + int count = existingFontNode.get("Count").asInt() + 1; + existingFontNode.put("Count", count); + } else { + fontNode.put("Count", 1); + uniqueFontsMap.put(uniqueKey, fontNode); } } @@ -554,41 +555,49 @@ public class GetInfoOnPDF { - // Access resources dictionary - PdfDictionary resourcesDict = itextDoc.getPage(pageNum).getResources().getPdfObject(); - - // Color Spaces & ICC Profiles + + + + + + + + // Access resources dictionary ArrayNode colorSpacesArray = objectMapper.createArrayNode(); - PdfDictionary colorSpaces = resourcesDict.getAsDictionary(PdfName.ColorSpace); - if (colorSpaces != null) { - for (PdfName name : colorSpaces.keySet()) { - PdfObject colorSpaceObject = colorSpaces.get(name); - if (colorSpaceObject instanceof PdfArray) { - PdfArray colorSpaceArray = (PdfArray) colorSpaceObject; - if (colorSpaceArray.size() > 1 && colorSpaceArray.get(0) instanceof PdfName && PdfName.ICCBased.equals(colorSpaceArray.get(0))) { - ObjectNode iccProfileNode = objectMapper.createObjectNode(); - PdfStream iccStream = (PdfStream) colorSpaceArray.get(1); - byte[] iccData = iccStream.getBytes(); - // TODO: Further decode and analyze the ICC data if needed - iccProfileNode.put("ICC Profile Length", iccData.length); - colorSpacesArray.add(iccProfileNode); - } - } + + Iterable colorSpaceNames = resources.getColorSpaceNames(); + for (COSName name : colorSpaceNames) { + PDColorSpace colorSpace = resources.getColorSpace(name); + if (colorSpace instanceof PDICCBased) { + PDICCBased iccBased = (PDICCBased) colorSpace; + PDStream iccData = iccBased.getPDStream(); + byte[] iccBytes = iccData.toByteArray(); + + // TODO: Further decode and analyze the ICC data if needed + ObjectNode iccProfileNode = objectMapper.createObjectNode(); + iccProfileNode.put("ICC Profile Length", iccBytes.length); + colorSpacesArray.add(iccProfileNode); } } pageInfo.set("Color Spaces & ICC Profiles", colorSpacesArray); + // Other XObjects Map xObjectCountMap = new HashMap<>(); // To store the count for each type - PdfDictionary xObjects = resourcesDict.getAsDictionary(PdfName.XObject); - if (xObjects != null) { - for (PdfName name : xObjects.keySet()) { - PdfStream xObjectStream = xObjects.getAsStream(name); - String xObjectType = xObjectStream.getAsName(PdfName.Subtype).toString(); - - // Increment the count for this type in the map - xObjectCountMap.put(xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1); + for (COSName name : resources.getXObjectNames()) { + PDXObject xObject = resources.getXObject(name); + String xObjectType; + + if (xObject instanceof PDImageXObject) { + xObjectType = "Image"; + } else if (xObject instanceof PDFormXObject) { + xObjectType = "Form"; + } else { + xObjectType = "Other"; } + + // Increment the count for this type in the map + xObjectCountMap.put(xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1); } // Add the count map to pageInfo (or wherever you want to store it) @@ -600,19 +609,22 @@ public class GetInfoOnPDF { + ArrayNode multimediaArray = objectMapper.createArrayNode(); - for (PdfAnnotation annotation : annotations) { - if (PdfName.RichMedia.equals(annotation.getSubtype())) { + + for (PDAnnotation annotation : annotations) { + if ("RichMedia".equals(annotation.getSubtype())) { ObjectNode multimediaNode = objectMapper.createObjectNode(); - // Extract details from the dictionary as needed + // Extract details from the annotation as needed multimediaArray.add(multimediaNode); } } + pageInfo.set("Multimedia", multimediaArray); - pageInfoParent.set("Page " + pageNum, pageInfo); + pageInfoParent.set("Page " + (pageNum+1), pageInfo); } @@ -638,17 +650,21 @@ public class GetInfoOnPDF { return null; } - private static void addOutlinesToArray(PdfOutline outline, ArrayNode arrayNode) { + private static void addOutlinesToArray(PDOutlineItem outline, ArrayNode arrayNode) { if (outline == null) return; + ObjectNode outlineNode = objectMapper.createObjectNode(); outlineNode.put("Title", outline.getTitle()); // You can add other properties if needed arrayNode.add(outlineNode); - - for (PdfOutline child : outline.getAllChildren()) { + + PDOutlineItem child = outline.getFirstChild(); + while (child != null) { addOutlinesToArray(child, arrayNode); + child = child.getNextSibling(); } } + public String getPageOrientation(double width, double height) { if (width > height) { return "Landscape"; @@ -658,66 +674,78 @@ public class GetInfoOnPDF { return "Square"; } } - public String getPageSize(double width, double height) { - // Common aspect ratios used for standard paper sizes - double[] aspectRatios = {4.0 / 3.0, 3.0 / 2.0, Math.sqrt(2.0), 16.0 / 9.0}; + public String getPageSize(float width, float height) { + // Define standard page sizes + Map standardSizes = new HashMap<>(); + standardSizes.put("Letter", PDRectangle.LETTER); + standardSizes.put("LEGAL", PDRectangle.LEGAL); + standardSizes.put("A0", PDRectangle.A0); + standardSizes.put("A1", PDRectangle.A1); + standardSizes.put("A2", PDRectangle.A2); + standardSizes.put("A3", PDRectangle.A3); + standardSizes.put("A4", PDRectangle.A4); + standardSizes.put("A5", PDRectangle.A5); + standardSizes.put("A6", PDRectangle.A6); - // Check if the page matches any common aspect ratio - for (double aspectRatio : aspectRatios) { - if (isCloseToAspectRatio(width, height, aspectRatio)) { - return "Standard"; + for (Map.Entry entry : standardSizes.entrySet()) { + PDRectangle size = entry.getValue(); + if (isCloseToSize(width, height, size.getWidth(), size.getHeight())) { + return entry.getKey(); } } - - // If not a standard aspect ratio, consider it as a custom size return "Custom"; } - private boolean isCloseToAspectRatio(double width, double height, double aspectRatio) { - // Calculate the aspect ratio of the page - double pageAspectRatio = width / height; - // Compare the page aspect ratio with the common aspect ratio within a threshold - return Math.abs(pageAspectRatio - aspectRatio) <= 0.05; + private boolean isCloseToSize(float width, float height, float standardWidth, float standardHeight) { + float tolerance = 1.0f; // You can adjust the tolerance as needed + return Math.abs(width - standardWidth) <= tolerance && Math.abs(height - standardHeight) <= tolerance; + } + + + public ObjectNode getDimensionInfo(ObjectNode dimensionInfo, float width, float height) { + float ppi = 72; // Points Per Inch + + float widthInInches = width / ppi; + float heightInInches = height / ppi; + + float widthInCm = widthInInches * 2.54f; + float heightInCm = heightInInches * 2.54f; + + dimensionInfo.put("Width (px)", String.format("%.2f", width)); + dimensionInfo.put("Height (px)", String.format("%.2f", height)); + dimensionInfo.put("Width (in)", String.format("%.2f", widthInInches)); + dimensionInfo.put("Height (in)", String.format("%.2f", heightInInches)); + dimensionInfo.put("Width (cm)", String.format("%.2f", widthInCm)); + dimensionInfo.put("Height (cm)", String.format("%.2f", heightInCm)); + return dimensionInfo; + } + + + +public static boolean checkForStandard(PDDocument document, String standardKeyword) { + // Check XMP Metadata + try { + PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata(); + if (pdMetadata != null) { + COSInputStream metaStream = pdMetadata.createInputStream(); + DomXmpParser domXmpParser = new DomXmpParser(); + XMPMetadata xmpMeta = domXmpParser.parse(metaStream); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new XmpSerializer().serialize(xmpMeta, baos, true); + String xmpString = new String(baos.toByteArray(), StandardCharsets.UTF_8); + + if (xmpString.contains(standardKeyword)) { + return true; + } + } + } catch (Exception e) { // Catching general exception for brevity, ideally you'd catch specific exceptions. + e.printStackTrace(); } - public boolean checkForStandard(PdfDocument document, String standardKeyword) { - // Check Output Intents - boolean foundInOutputIntents = checkOutputIntent(document, standardKeyword); - if (foundInOutputIntents) return true; + return false; +} - // Check XMP Metadata (rudimentary) - try { - byte[] metadataBytes = document.getXmpMetadata(); - if (metadataBytes != null) { - XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(metadataBytes); - String xmpString = xmpMeta.dumpObject(); - if (xmpString.contains(standardKeyword)) { - return true; - } - } - } catch (XMPException e) { - e.printStackTrace(); - } - - return false; - } - - - public boolean checkOutputIntent(PdfDocument document, String standard) { - PdfArray outputIntents = document.getCatalog().getPdfObject().getAsArray(PdfName.OutputIntents); - if (outputIntents != null && !outputIntents.isEmpty()) { - for (int i = 0; i < outputIntents.size(); i++) { - PdfDictionary outputIntentDict = outputIntents.getAsDictionary(i); - if (outputIntentDict != null) { - PdfString s = outputIntentDict.getAsString(PdfName.S); - if (s != null && s.toString().contains(standard)) { - return true; - } - } - } - } - return false; - } public ArrayNode exploreStructureTree(List nodes) { ArrayNode elementsArray = objectMapper.createArrayNode(); @@ -773,7 +801,7 @@ public class GetInfoOnPDF { } } - private String getPageModeDescription(PdfName pageMode) { + private String getPageModeDescription(String pageMode) { return pageMode != null ? pageMode.toString().replaceFirst("/", "") : "Unknown"; } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java index 909be730..639b6973 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java @@ -8,18 +8,19 @@ import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.security.AddPasswordRequest; +import stirling.software.SPDF.model.api.security.PDFPasswordRequest; import stirling.software.SPDF.utils.WebResponseUtils; @RestController +@RequestMapping("/api/v1/security") @Tag(name = "Security", description = "Security APIs") public class PasswordController { @@ -31,13 +32,12 @@ public class PasswordController { summary = "Remove password from a PDF file", description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO" ) - public ResponseEntity removePassword( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file from which the password should be removed", required = true) - MultipartFile fileInput, - @RequestParam(name = "password") - @Parameter(description = "The password of the PDF file", required = true) - String password) throws IOException { + public ResponseEntity removePassword(@ModelAttribute PDFPasswordRequest request) throws IOException { + MultipartFile fileInput = request.getFileInput(); + String password = request.getPassword(); + + + PDDocument document = PDDocument.load(fileInput.getBytes(), password); document.setAllSecurityToBeRemoved(true); return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_password_removed.pdf"); @@ -48,44 +48,19 @@ public class PasswordController { summary = "Add password to a PDF file", description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF" ) - public ResponseEntity addPassword( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to which the password should be added", required = true) - MultipartFile fileInput, - @RequestParam(value = "", name = "ownerPassword") - @Parameter(description = "The owner password to be added to the PDF file (Restricts what can be done with the document once it is opened)") - String ownerPassword, - @RequestParam( name = "password", required = false) - @Parameter(description = "The password to be added to the PDF file (Restricts the opening of the document itself.)") - String password, - @RequestParam( name = "keyLength", required = false) - @Parameter(description = "The length of the encryption key", schema = @Schema(allowableValues = {"40", "128", "256"})) - int keyLength, - @RequestParam( name = "canAssembleDocument", required = false) - @Parameter(description = "Whether the document assembly is allowed", example = "false") - boolean canAssembleDocument, - @RequestParam( name = "canExtractContent", required = false) - @Parameter(description = "Whether content extraction for accessibility is allowed", example = "false") - boolean canExtractContent, - @RequestParam( name = "canExtractForAccessibility", required = false) - @Parameter(description = "Whether content extraction for accessibility is allowed", example = "false") - boolean canExtractForAccessibility, - @RequestParam( name = "canFillInForm", required = false) - @Parameter(description = "Whether form filling is allowed", example = "false") - boolean canFillInForm, - @RequestParam( name = "canModify", required = false) - @Parameter(description = "Whether the document modification is allowed", example = "false") - boolean canModify, - @RequestParam( name = "canModifyAnnotations", required = false) - @Parameter(description = "Whether modification of annotations is allowed", example = "false") - boolean canModifyAnnotations, - @RequestParam(name = "canPrint", required = false) - @Parameter(description = "Whether printing of the document is allowed", example = "false") - boolean canPrint, - @RequestParam( name = "canPrintFaithful", required = false) - @Parameter(description = "Whether faithful printing is allowed", example = "false") - boolean canPrintFaithful - ) throws IOException { + public ResponseEntity addPassword(@ModelAttribute AddPasswordRequest request) throws IOException { + MultipartFile fileInput = request.getFileInput(); + String ownerPassword = request.getOwnerPassword(); + String password = request.getPassword(); + int keyLength = request.getKeyLength(); + boolean canAssembleDocument = request.isCanAssembleDocument(); + boolean canExtractContent = request.isCanExtractContent(); + boolean canExtractForAccessibility = request.isCanExtractForAccessibility(); + boolean canFillInForm = request.isCanFillInForm(); + boolean canModify = request.isCanModify(); + boolean canModifyAnnotations = request.isCanModifyAnnotations(); + boolean canPrint = request.isCanPrint(); + boolean canPrintFaithful = request.isCanPrintFaithful(); PDDocument document = PDDocument.load(fileInput.getBytes()); AccessPermission ap = new AccessPermission(); @@ -98,15 +73,15 @@ public class PasswordController { ap.setCanPrint(!canPrint); ap.setCanPrintFaithful(!canPrintFaithful); StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPassword, password, ap); - - - - spp.setEncryptionKeyLength(keyLength); + if(!"".equals(ownerPassword) || !"".equals(password)) { + spp.setEncryptionKeyLength(keyLength); + } spp.setPermissions(ap); - document.protect(spp); + if("".equals(ownerPassword) && "".equals(password)) + return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_permissions.pdf"); return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf"); } diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java b/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java new file mode 100644 index 00000000..825544dc --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java @@ -0,0 +1,123 @@ +package stirling.software.SPDF.controller.api.security; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.rendering.ImageType; +import org.apache.pdfbox.rendering.PDFRenderer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.PDFText; +import stirling.software.SPDF.model.api.security.RedactPdfRequest; +import stirling.software.SPDF.pdf.TextFinder; +import stirling.software.SPDF.utils.WebResponseUtils; +@RestController +@RequestMapping("/api/v1/security") +@Tag(name = "Security", description = "Security APIs") +public class RedactController { + + private static final Logger logger = LoggerFactory.getLogger(RedactController.class); + + + @PostMapping(value = "/auto-redact", consumes = "multipart/form-data") + @Operation(summary = "Redacts listOfText in a PDF document", + description = "This operation takes an input PDF file and redacts the provided listOfText. Input:PDF, Output:PDF, Type:SISO") + public ResponseEntity redactPdf(@ModelAttribute RedactPdfRequest request) throws Exception { + MultipartFile file = request.getFileInput(); + String listOfTextString = request.getListOfText(); + boolean useRegex = request.isUseRegex(); + boolean wholeWordSearchBool = request.isWholeWordSearch(); + String colorString = request.getRedactColor(); + float customPadding = request.getCustomPadding(); + boolean convertPDFToImage = request.isConvertPDFToImage(); + + System.out.println(listOfTextString); + String[] listOfText = listOfTextString.split("\n"); + byte[] bytes = file.getBytes(); + PDDocument document = PDDocument.load(new ByteArrayInputStream(bytes)); + + Color redactColor; + try { + if (!colorString.startsWith("#")) { + colorString = "#" + colorString; + } + redactColor = Color.decode(colorString); + } catch (NumberFormatException e) { + logger.warn("Invalid color string provided. Using default color BLACK for redaction."); + redactColor = Color.BLACK; + } + + + + for (String text : listOfText) { + text = text.trim(); + System.out.println(text); + TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool); + List foundTexts = textFinder.getTextLocations(document); + redactFoundText(document, foundTexts, customPadding,redactColor); + } + + + + if (convertPDFToImage) { + PDDocument imageDocument = new PDDocument(); + PDFRenderer pdfRenderer = new PDFRenderer(document); + for (int page = 0; page < document.getNumberOfPages(); ++page) { + BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB); + PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight())); + imageDocument.addPage(newPage); + PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim); + PDPageContentStream contentStream = new PDPageContentStream(imageDocument, newPage); + contentStream.drawImage(pdImage, 0, 0); + contentStream.close(); + } + document.close(); + document = imageDocument; + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + document.close(); + + byte[] pdfContent = baos.toByteArray(); + return WebResponseUtils.bytesToWebResponse(pdfContent, + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_redacted.pdf"); + } + + + private void redactFoundText(PDDocument document, List blocks, float customPadding, Color redactColor) throws IOException { + var allPages = document.getDocumentCatalog().getPages(); + + for (PDFText block : blocks) { + var page = allPages.get(block.getPageIndex()); + PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true); + contentStream.setNonStrokingColor(redactColor); + float padding = (block.getY2() - block.getY1()) * 0.3f + customPadding; + PDRectangle pageBox = page.getBBox(); + contentStream.addRect(block.getX1(), pageBox.getHeight() - block.getY1() - padding, block.getX2() - block.getX1(), block.getY2() - block.getY1() + 2 * padding); + contentStream.fill(); + contentStream.close(); + } + } + + +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java b/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java index f03dcec8..dab9d1d7 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java @@ -1,58 +1,51 @@ package stirling.software.SPDF.controller.api.security; +import java.io.IOException; + +import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.PDPageTree; +import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.PDMetadata; -import org.apache.pdfbox.pdmodel.common.PDStream; -import org.apache.pdfbox.pdmodel.interactive.action.*; +import org.apache.pdfbox.pdmodel.interactive.action.PDAction; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; -import org.apache.pdfbox.pdmodel.interactive.form.PDNonTerminalField; -import org.apache.pdfbox.pdmodel.interactive.form.PDTerminalField; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; + import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.security.SanitizePdfRequest; import stirling.software.SPDF.utils.WebResponseUtils; -import org.apache.pdfbox.cos.COSDictionary; -import org.apache.pdfbox.cos.COSName; -import org.apache.pdfbox.cos.COSString; -import java.io.IOException; -import java.io.InputStream; @RestController +@RequestMapping("/api/v1/security") +@Tag(name = "Security", description = "Security APIs") public class SanitizeController { @PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf") @Operation(summary = "Sanitize a PDF file", description = "This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO") - public ResponseEntity sanitizePDF( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to be sanitized") - MultipartFile inputFile, - @RequestParam(name = "removeJavaScript", required = false, defaultValue = "true") - @Parameter(description = "Remove JavaScript actions from the PDF if set to true") - Boolean removeJavaScript, - @RequestParam(name = "removeEmbeddedFiles", required = false, defaultValue = "true") - @Parameter(description = "Remove embedded files from the PDF if set to true") - Boolean removeEmbeddedFiles, - @RequestParam(name = "removeMetadata", required = false, defaultValue = "true") - @Parameter(description = "Remove metadata from the PDF if set to true") - Boolean removeMetadata, - @RequestParam(name = "removeLinks", required = false, defaultValue = "true") - @Parameter(description = "Remove links from the PDF if set to true") - Boolean removeLinks, - @RequestParam(name = "removeFonts", required = false, defaultValue = "true") - @Parameter(description = "Remove fonts from the PDF if set to true") - Boolean removeFonts) throws IOException { + public ResponseEntity sanitizePDF(@ModelAttribute SanitizePdfRequest request) throws IOException { + MultipartFile inputFile = request.getFileInput(); + boolean removeJavaScript = request.isRemoveJavaScript(); + boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles(); + boolean removeMetadata = request.isRemoveMetadata(); + boolean removeLinks = request.isRemoveLinks(); + boolean removeFonts = request.isRemoveFonts(); try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { if (removeJavaScript) { diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java index f5655391..b19636cd 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java @@ -1,8 +1,6 @@ package stirling.software.SPDF.controller.api.security; import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; @@ -24,40 +22,35 @@ import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.util.Matrix; import org.springframework.core.io.ClassPathResource; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.model.api.security.AddWatermarkRequest; import stirling.software.SPDF.utils.WebResponseUtils; -import io.swagger.v3.oas.annotations.media.Schema; @RestController +@RequestMapping("/api/v1/security") @Tag(name = "Security", description = "Security APIs") public class WatermarkController { @PostMapping(consumes = "multipart/form-data", value = "/add-watermark") @Operation(summary = "Add watermark to a PDF file", description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO") - public ResponseEntity addWatermark( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to add a watermark") MultipartFile pdfFile, - @RequestParam(required = true) @Parameter(description = "The watermark type (text or image)") String watermarkType, - @RequestParam(required = false) @Parameter(description = "The watermark text") String watermarkText, - @RequestPart(required = false) @Parameter(description = "The watermark image") MultipartFile watermarkImage, - - @RequestParam(defaultValue = "roman", name = "alphabet") @Parameter(description = "The selected alphabet", - schema = @Schema(type = "string", - allowableValues = {"roman","arabic","japanese","korean","chinese"}, - defaultValue = "roman")) String alphabet, - @RequestParam(defaultValue = "30", name = "fontSize") @Parameter(description = "The font size of the watermark text", example = "30") float fontSize, - @RequestParam(defaultValue = "0", name = "rotation") @Parameter(description = "The rotation of the watermark in degrees", example = "0") float rotation, - @RequestParam(defaultValue = "0.5", name = "opacity") @Parameter(description = "The opacity of the watermark (0.0 - 1.0)", example = "0.5") float opacity, - @RequestParam(defaultValue = "50", name = "widthSpacer") @Parameter(description = "The width spacer between watermark elements", example = "50") int widthSpacer, - @RequestParam(defaultValue = "50", name = "heightSpacer") @Parameter(description = "The height spacer between watermark elements", example = "50") int heightSpacer) - throws IOException, Exception { + public ResponseEntity addWatermark(@ModelAttribute AddWatermarkRequest request) throws IOException, Exception { + MultipartFile pdfFile = request.getFileInput(); + String watermarkType = request.getWatermarkType(); + String watermarkText = request.getWatermarkText(); + MultipartFile watermarkImage = request.getWatermarkImage(); + String alphabet = request.getAlphabet(); + float fontSize = request.getFontSize(); + float rotation = request.getRotation(); + float opacity = request.getOpacity(); + int widthSpacer = request.getWidthSpacer(); + int heightSpacer = request.getHeightSpacer(); // Load the input PDF PDDocument document = PDDocument.load(pdfFile.getInputStream()); diff --git a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java new file mode 100644 index 00000000..cba7ebb5 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java @@ -0,0 +1,135 @@ +package stirling.software.SPDF.controller.web; +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import stirling.software.SPDF.model.User; +import stirling.software.SPDF.repository.UserRepository; +@Controller +@Tag(name = "Account Security", description = "Account Security APIs") +public class AccountWebController { + + + @GetMapping("/login") + public String login(HttpServletRequest request, Model model, Authentication authentication) { + if (authentication != null && authentication.isAuthenticated()) { + return "redirect:/"; + } + + if (request.getParameter("error") != null) { + + model.addAttribute("error", request.getParameter("error")); + } + if (request.getParameter("logout") != null) { + + model.addAttribute("logoutMessage", "You have been logged out."); + } + + return "login"; + } + @Autowired + private UserRepository userRepository; // Assuming you have a repository for user operations + + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @GetMapping("/addUsers") + public String showAddUserForm(Model model, Authentication authentication) { + List allUsers = userRepository.findAll(); + model.addAttribute("users", allUsers); + model.addAttribute("currentUsername", authentication.getName()); + return "addUsers"; + } + + + + @GetMapping("/account") + public String account(HttpServletRequest request, Model model, Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) { + return "redirect:/"; + } + if (authentication != null && authentication.isAuthenticated()) { + Object principal = authentication.getPrincipal(); + + if (principal instanceof UserDetails) { + // Cast the principal object to UserDetails + UserDetails userDetails = (UserDetails) principal; + + // Retrieve username and other attributes + String username = userDetails.getUsername(); + + // Fetch user details from the database + Optional user = userRepository.findByUsername(username); // Assuming findByUsername method exists + if (!user.isPresent()) { + // Handle error appropriately + return "redirect:/error"; // Example redirection in case of error + } + + // Convert settings map to JSON string + ObjectMapper objectMapper = new ObjectMapper(); + String settingsJson; + try { + settingsJson = objectMapper.writeValueAsString(user.get().getSettings()); + } catch (JsonProcessingException e) { + // Handle JSON conversion error + e.printStackTrace(); + return "redirect:/error"; // Example redirection in case of error + } + + // Add attributes to the model + model.addAttribute("username", username); + model.addAttribute("role", user.get().getRolesAsString()); + model.addAttribute("settings", settingsJson); + model.addAttribute("changeCredsFlag", user.get().isFirstLogin()); + } + } else { + return "redirect:/"; + } + return "account"; + } + + + + @GetMapping("/change-creds") + public String changeCreds(HttpServletRequest request, Model model, Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) { + return "redirect:/"; + } + if (authentication != null && authentication.isAuthenticated()) { + Object principal = authentication.getPrincipal(); + + if (principal instanceof UserDetails) { + // Cast the principal object to UserDetails + UserDetails userDetails = (UserDetails) principal; + + // Retrieve username and other attributes + String username = userDetails.getUsername(); + + // Fetch user details from the database + Optional user = userRepository.findByUsername(username); // Assuming findByUsername method exists + if (!user.isPresent()) { + // Handle error appropriately + return "redirect:/error"; // Example redirection in case of error + } + // Add attributes to the model + model.addAttribute("username", username); + } + } else { + return "redirect:/"; + } + return "change-creds"; + } + + +} diff --git a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java index 4d6e991a..6c934290 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -20,14 +21,19 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.tags.Tag; + @Controller @Tag(name = "General", description = "General APIs") public class GeneralWebController { + + + @GetMapping("/pipeline") @Hidden public String pipelineForm(Model model) { @@ -46,7 +52,8 @@ public class GeneralWebController { } List> pipelineConfigsWithNames = new ArrayList<>(); for (String config : pipelineConfigs) { - Map jsonContent = new ObjectMapper().readValue(config, Map.class); + Map jsonContent = new ObjectMapper().readValue(config, new TypeReference>(){}); + String name = (String) jsonContent.get("name"); Map configWithName = new HashMap<>(); configWithName.put("json", config); @@ -133,30 +140,116 @@ public class GeneralWebController { return "sign"; } + @GetMapping("/multi-page-layout") + @Hidden + public String multiPageLayoutForm(Model model) { + model.addAttribute("currentPage", "multi-page-layout"); + return "multi-page-layout"; + } + + + @GetMapping("/scale-pages") + @Hidden + public String scalePagesFrom(Model model) { + model.addAttribute("currentPage", "scale-pages"); + return "scale-pages"; + } + + + @Autowired private ResourceLoader resourceLoader; - private List getFontNames() { + private List getFontNames() { + List fontNames = new ArrayList<>(); + + // Extract font names from classpath + fontNames.addAll(getFontNamesFromLocation("classpath:static/fonts/*.woff2")); + + // Extract font names from external directory + fontNames.addAll(getFontNamesFromLocation("file:customFiles/static/fonts/*")); + + return fontNames; + } + + private List getFontNamesFromLocation(String locationPattern) { try { Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader) - .getResources("classpath:static/fonts/*.woff2"); - + .getResources(locationPattern); return Arrays.stream(resources) .map(resource -> { try { String filename = resource.getFilename(); - return filename.substring(0, filename.length() - 6); // Remove .woff2 extension + if (filename != null) { + int lastDotIndex = filename.lastIndexOf('.'); + if (lastDotIndex != -1) { + String name = filename.substring(0, lastDotIndex); + String extension = filename.substring(lastDotIndex + 1); + return new FontResource(name, extension); + } + } + return null; } catch (Exception e) { throw new RuntimeException("Error processing filename", e); } }) + .filter(Objects::nonNull) .collect(Collectors.toList()); } catch (Exception e) { - throw new RuntimeException("Failed to read font directory", e); + throw new RuntimeException("Failed to read font directory from " + locationPattern, e); + } + } + + + public String getFormatFromExtension(String extension) { + switch (extension) { + case "ttf": return "truetype"; + case "woff": return "woff"; + case "woff2": return "woff2"; + case "eot": return "embedded-opentype"; + case "svg": return "svg"; + default: return ""; // or throw an exception if an unexpected extension is encountered } } + public class FontResource { + private String name; + private String extension; + private String type; + public FontResource(String name, String extension) { + this.name = name; + this.extension = extension; + this.type = getFormatFromExtension(extension); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getExtension() { + return extension; + } + + public void setExtension(String extension) { + this.extension = extension; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + + } + @GetMapping("/crop") @Hidden diff --git a/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java b/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java index ba857afd..b6d08fbf 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java @@ -1,5 +1,6 @@ package stirling.software.SPDF.controller.web; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -7,6 +8,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import io.swagger.v3.oas.annotations.Hidden; +import stirling.software.SPDF.model.ApplicationProperties; @Controller public class HomeWebController { @@ -31,18 +33,16 @@ public class HomeWebController { return "redirect:/"; } - + @Autowired + ApplicationProperties applicationProperties; + @GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody @Hidden public String getRobotsTxt() { - String allowGoogleVisibility = System.getProperty("ALLOW_GOOGLE_VISIBILITY"); - if (allowGoogleVisibility == null) - allowGoogleVisibility = System.getenv("ALLOW_GOOGLE_VISIBILITY"); - if (allowGoogleVisibility == null) - allowGoogleVisibility = "false"; - if (Boolean.parseBoolean(allowGoogleVisibility)) { + Boolean allowGoogle = applicationProperties.getSystem().getGooglevisibility(); + if(Boolean.TRUE.equals(allowGoogle)) { return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /"; } else { return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /"; diff --git a/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java b/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java index ab18b1b5..9dc4f2b7 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java @@ -8,7 +8,7 @@ import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -24,26 +24,28 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.PostConstruct; import stirling.software.SPDF.config.StartupApplicationListener; +import stirling.software.SPDF.model.ApplicationProperties; @RestController @RequestMapping("/api/v1") @Tag(name = "API", description = "Info APIs") public class MetricsController { + + @Autowired + ApplicationProperties applicationProperties; + + private final MeterRegistry meterRegistry; - private boolean isEndpointEnabled; + private boolean metricsEnabled; @PostConstruct public void init() { - String isEndpointEnabled = System.getProperty("ENABLE_API_METRICS"); - if (isEndpointEnabled == null) { - isEndpointEnabled = System.getenv("ENABLE_API_METRICS"); - if (isEndpointEnabled == null) { - isEndpointEnabled = "true"; - } - } - this.isEndpointEnabled = "true".equalsIgnoreCase(isEndpointEnabled); + Boolean metricsEnabled = applicationProperties.getMetrics().getEnabled(); + if(metricsEnabled == null) + metricsEnabled = true; + this.metricsEnabled = metricsEnabled; } public MetricsController(MeterRegistry meterRegistry) { @@ -54,7 +56,7 @@ public class MetricsController { @Operation(summary = "Application status and version", description = "This endpoint returns the status of the application and its version number.") public ResponseEntity getStatus() { - if (!isEndpointEnabled) { + if (!metricsEnabled) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } @@ -68,7 +70,7 @@ public class MetricsController { @Operation(summary = "GET request count", description = "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.") public ResponseEntity getPageLoads(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional endpoint) { - if (!isEndpointEnabled) { + if (!metricsEnabled) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } try { @@ -109,7 +111,7 @@ public class MetricsController { @Operation(summary = "GET requests count for all endpoints", description = "This endpoint returns the count of GET requests for each endpoint.") public ResponseEntity getAllEndpointLoads() { - if (!isEndpointEnabled) { + if (!metricsEnabled) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } try { @@ -170,7 +172,7 @@ public class MetricsController { @Operation(summary = "POST request count", description = "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.") public ResponseEntity getTotalRequests(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional endpoint) { - if (!isEndpointEnabled) { + if (!metricsEnabled) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } try { @@ -208,7 +210,7 @@ public class MetricsController { @Operation(summary = "POST requests count for all endpoints", description = "This endpoint returns the count of POST requests for each endpoint.") public ResponseEntity getAllPostRequests() { - if (!isEndpointEnabled) { + if (!metricsEnabled) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } try { @@ -244,7 +246,7 @@ public class MetricsController { @GetMapping("/uptime") public ResponseEntity getUptime() { - if (!isEndpointEnabled) { + if (!metricsEnabled) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } diff --git a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java index 157f76e4..827523cb 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java @@ -15,19 +15,19 @@ import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.tags.Tag; @Controller -@Tag(name = "Other", description = "Other APIs") +@Tag(name = "Misc", description = "Miscellaneous APIs") public class OtherWebController { @GetMapping("/compress-pdf") @Hidden public String compressPdfForm(Model model) { model.addAttribute("currentPage", "compress-pdf"); - return "other/compress-pdf"; + return "misc/compress-pdf"; } @GetMapping("/extract-image-scans") @Hidden public ModelAndView extractImageScansForm() { - ModelAndView modelAndView = new ModelAndView("other/extract-image-scans"); + ModelAndView modelAndView = new ModelAndView("misc/extract-image-scans"); modelAndView.addObject("currentPage", "extract-image-scans"); return modelAndView; } @@ -36,7 +36,7 @@ public class OtherWebController { @Hidden public String extractJavascriptForm(Model model) { model.addAttribute("currentPage", "show-javascript"); - return "other/show-javascript"; + return "misc/show-javascript"; } @@ -44,21 +44,21 @@ public class OtherWebController { @Hidden public String addPageNumbersForm(Model model) { model.addAttribute("currentPage", "add-page-numbers"); - return "other/add-page-numbers"; + return "misc/add-page-numbers"; } @GetMapping("/extract-images") @Hidden public String extractImagesForm(Model model) { model.addAttribute("currentPage", "extract-images"); - return "other/extract-images"; + return "misc/extract-images"; } @GetMapping("/flatten") @Hidden public String flattenForm(Model model) { model.addAttribute("currentPage", "flatten"); - return "other/flatten"; + return "misc/flatten"; } @@ -67,14 +67,14 @@ public class OtherWebController { @Hidden public String addWatermarkForm(Model model) { model.addAttribute("currentPage", "change-metadata"); - return "other/change-metadata"; + return "misc/change-metadata"; } @GetMapping("/compare") @Hidden public String compareForm(Model model) { model.addAttribute("currentPage", "compare"); - return "other/compare"; + return "misc/compare"; } public List getAvailableTesseractLanguages() { @@ -90,7 +90,7 @@ public class OtherWebController { @GetMapping("/ocr-pdf") @Hidden public ModelAndView ocrPdfPage() { - ModelAndView modelAndView = new ModelAndView("other/ocr-pdf"); + ModelAndView modelAndView = new ModelAndView("misc/ocr-pdf"); List languages = getAvailableTesseractLanguages(); Collections.sort(languages); modelAndView.addObject("languages", languages); @@ -103,56 +103,43 @@ public class OtherWebController { @Hidden public String overlayImage(Model model) { model.addAttribute("currentPage", "add-image"); - return "other/add-image"; + return "misc/add-image"; } @GetMapping("/adjust-contrast") @Hidden public String contrast(Model model) { model.addAttribute("currentPage", "adjust-contrast"); - return "other/adjust-contrast"; + return "misc/adjust-contrast"; } @GetMapping("/repair") @Hidden public String repairForm(Model model) { model.addAttribute("currentPage", "repair"); - return "other/repair"; + return "misc/repair"; } @GetMapping("/remove-blanks") @Hidden public String removeBlanksForm(Model model) { model.addAttribute("currentPage", "remove-blanks"); - return "other/remove-blanks"; - } - - @GetMapping("/multi-page-layout") - @Hidden - public String multiPageLayoutForm(Model model) { - model.addAttribute("currentPage", "multi-page-layout"); - return "other/multi-page-layout"; - } - - @GetMapping("/scale-pages") - @Hidden - public String scalePagesFrom(Model model) { - model.addAttribute("currentPage", "scale-pages"); - return "other/scale-pages"; + return "misc/remove-blanks"; } + @GetMapping("/auto-crop") @Hidden public String autoCropForm(Model model) { model.addAttribute("currentPage", "auto-crop"); - return "other/auto-crop"; + return "misc/auto-crop"; } @GetMapping("/auto-rename") @Hidden public String autoRenameForm(Model model) { model.addAttribute("currentPage", "auto-rename"); - return "other/auto-rename"; + return "misc/auto-rename"; } diff --git a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java index 3857cc9e..2cbf245f 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java @@ -11,6 +11,12 @@ import io.swagger.v3.oas.annotations.tags.Tag; @Tag(name = "Security", description = "Security APIs") public class SecurityWebController { + @GetMapping("/auto-redact") + @Hidden + public String autoRedactForm(Model model) { + model.addAttribute("currentPage", "auto-redact"); + return "security/auto-redact"; + } @GetMapping("/add-password") @Hidden diff --git a/src/main/java/stirling/software/SPDF/model/ApiKeyAuthenticationToken.java b/src/main/java/stirling/software/SPDF/model/ApiKeyAuthenticationToken.java new file mode 100644 index 00000000..54b61c1b --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/ApiKeyAuthenticationToken.java @@ -0,0 +1,49 @@ +package stirling.software.SPDF.model; +import java.util.Collection; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken { + + private final Object principal; + private Object credentials; + + public ApiKeyAuthenticationToken(String apiKey) { + super(null); + this.principal = null; + this.credentials = apiKey; + setAuthenticated(false); + } + + public ApiKeyAuthenticationToken(Object principal, String apiKey, Collection authorities) { + super(authorities); + this.principal = principal; // principal can be a UserDetails object + this.credentials = apiKey; + super.setAuthenticated(true); // this authentication is trusted + } + + @Override + public Object getCredentials() { + return credentials; + } + + @Override + public Object getPrincipal() { + return principal; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + if (isAuthenticated) { + throw new IllegalArgumentException("Cannot set this token to trusted. Use constructor which takes a GrantedAuthority list instead."); + } + super.setAuthenticated(false); + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + credentials = null; + } +} diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java new file mode 100644 index 00000000..dc068779 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -0,0 +1,335 @@ +package stirling.software.SPDF.model; + +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import stirling.software.SPDF.config.YamlPropertySourceFactory; + +@Configuration +@ConfigurationProperties(prefix = "") +@PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class) +public class ApplicationProperties { + private Security security; + private System system; + private Ui ui; + private Endpoints endpoints; + private Metrics metrics; + private AutomaticallyGenerated automaticallyGenerated; + private AutoPipeline autoPipeline; + + public AutoPipeline getAutoPipeline() { + return autoPipeline != null ? autoPipeline : new AutoPipeline(); + } + + public void setAutoPipeline(AutoPipeline autoPipeline) { + this.autoPipeline = autoPipeline; + } + + public Security getSecurity() { + return security != null ? security : new Security(); + } + + public void setSecurity(Security security) { + this.security = security; + } + + public System getSystem() { + return system != null ? system : new System(); + } + + public void setSystem(System system) { + this.system = system; + } + + public Ui getUi() { + return ui != null ? ui : new Ui(); + } + + public void setUi(Ui ui) { + this.ui = ui; + } + + public Endpoints getEndpoints() { + return endpoints != null ? endpoints : new Endpoints(); + } + + public void setEndpoints(Endpoints endpoints) { + this.endpoints = endpoints; + } + + public Metrics getMetrics() { + return metrics != null ? metrics : new Metrics(); + } + + public void setMetrics(Metrics metrics) { + this.metrics = metrics; + } + + public AutomaticallyGenerated getAutomaticallyGenerated() { + return automaticallyGenerated != null ? automaticallyGenerated : new AutomaticallyGenerated(); + } + + public void setAutomaticallyGenerated(AutomaticallyGenerated automaticallyGenerated) { + this.automaticallyGenerated = automaticallyGenerated; + } + + @Override + public String toString() { + return "ApplicationProperties [security=" + security + ", system=" + system + ", ui=" + ui + ", endpoints=" + + endpoints + ", metrics=" + metrics + ", automaticallyGenerated=" + automaticallyGenerated + + ", autoPipeline=" + autoPipeline + "]"; + } + + public static class AutoPipeline { + private String outputFolder; + + public String getOutputFolder() { + return outputFolder; + } + + public void setOutputFolder(String outputFolder) { + this.outputFolder = outputFolder; + } + + @Override + public String toString() { + return "AutoPipeline [outputFolder=" + outputFolder + "]"; + } + + + + } + public static class Security { + private Boolean enableLogin; + private Boolean csrfDisabled; + private InitialLogin initialLogin; + + public InitialLogin getInitialLogin() { + return initialLogin != null ? initialLogin : new InitialLogin(); + } + + public void setInitialLogin(InitialLogin initialLogin) { + this.initialLogin = initialLogin; + } + + public Boolean getEnableLogin() { + return enableLogin; + } + + public void setEnableLogin(Boolean enableLogin) { + this.enableLogin = enableLogin; + } + + public Boolean getCsrfDisabled() { + return csrfDisabled; + } + + public void setCsrfDisabled(Boolean csrfDisabled) { + this.csrfDisabled = csrfDisabled; + } + + + @Override + public String toString() { + return "Security [enableLogin=" + enableLogin + ", initialLogin=" + initialLogin + ", csrfDisabled=" + + csrfDisabled + "]"; + } + + public static class InitialLogin { + + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public String toString() { + return "InitialLogin [username=" + username + ", password=" + (password != null && !password.isEmpty() ? "MASKED" : "NULL") + "]"; + } + + + + } + } + + public static class System { + private String defaultLocale; + private Boolean googlevisibility; + private String rootURIPath; + private String customStaticFilePath; + private Integer maxFileSize; + + public String getDefaultLocale() { + return defaultLocale; + } + + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + public Boolean getGooglevisibility() { + return googlevisibility; + } + + public void setGooglevisibility(Boolean googlevisibility) { + this.googlevisibility = googlevisibility; + } + + public String getRootURIPath() { + return rootURIPath; + } + + public void setRootURIPath(String rootURIPath) { + this.rootURIPath = rootURIPath; + } + + public String getCustomStaticFilePath() { + return customStaticFilePath; + } + + public void setCustomStaticFilePath(String customStaticFilePath) { + this.customStaticFilePath = customStaticFilePath; + } + + public Integer getMaxFileSize() { + return maxFileSize; + } + + public void setMaxFileSize(Integer maxFileSize) { + this.maxFileSize = maxFileSize; + } + + @Override + public String toString() { + return "System [defaultLocale=" + defaultLocale + ", googlevisibility=" + googlevisibility + ", rootURIPath=" + + rootURIPath + ", customStaticFilePath=" + customStaticFilePath + ", maxFileSize=" + maxFileSize + + "]"; + } + + + } + + public static class Ui { + private String appName; + private String homeDescription; + private String appNameNavbar; + + public String getAppName() { + if(appName != null && appName.trim().length() == 0) + return null; + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getHomeDescription() { + if(homeDescription != null && homeDescription.trim().length() == 0) + return null; + return homeDescription; + } + + public void setHomeDescription(String homeDescription) { + this.homeDescription = homeDescription; + } + + public String getAppNameNavbar() { + if(appNameNavbar != null && appNameNavbar.trim().length() == 0) + return null; + return appNameNavbar; + } + + public void setAppNameNavbar(String appNameNavbar) { + this.appNameNavbar = appNameNavbar; + } + + @Override + public String toString() { + return "UserInterface [appName=" + appName + ", homeDescription=" + homeDescription + ", appNameNavbar=" + appNameNavbar + "]"; + } + } + + + public static class Endpoints { + private List toRemove; + private List groupsToRemove; + + public List getToRemove() { + return toRemove; + } + + public void setToRemove(List toRemove) { + this.toRemove = toRemove; + } + + public List getGroupsToRemove() { + return groupsToRemove; + } + + public void setGroupsToRemove(List groupsToRemove) { + this.groupsToRemove = groupsToRemove; + } + + @Override + public String toString() { + return "Endpoints [toRemove=" + toRemove + ", groupsToRemove=" + groupsToRemove + "]"; + } + + + } + + public static class Metrics { + private Boolean enabled; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + @Override + public String toString() { + return "Metrics [enabled=" + enabled + "]"; + } + + + } + + public static class AutomaticallyGenerated { + private String key; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + @Override + public String toString() { + return "AutomaticallyGenerated [key=" + (key != null && !key.isEmpty() ? "MASKED" : "NULL") + "]"; + } + + } +} diff --git a/src/main/java/stirling/software/SPDF/model/Authority.java b/src/main/java/stirling/software/SPDF/model/Authority.java new file mode 100644 index 00000000..8be853ea --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/Authority.java @@ -0,0 +1,64 @@ +package stirling.software.SPDF.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; + +@Entity +@Table(name = "authorities") +public class Authority { + + public Authority() { + + } + + + public Authority(String authority, User user) { + this.authority = authority; + this.user = user; + user.getAuthorities().add(this); + } + + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "authority") + private String authority; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getAuthority() { + return authority; + } + + public void setAuthority(String authority) { + this.authority = authority; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + +} diff --git a/src/main/java/stirling/software/SPDF/model/PDFText.java b/src/main/java/stirling/software/SPDF/model/PDFText.java new file mode 100644 index 00000000..9a4909d0 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/PDFText.java @@ -0,0 +1,42 @@ +package stirling.software.SPDF.model; +public class PDFText { + private final int pageIndex; + private final float x1; + private final float y1; + private final float x2; + private final float y2; + private final String text; + + public PDFText(int pageIndex, float x1, float y1, float x2, float y2, String text) { + this.pageIndex = pageIndex; + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + this.text = text; + } + + public int getPageIndex() { + return pageIndex; + } + + public float getX1() { + return x1; + } + + public float getY1() { + return y1; + } + + public float getX2() { + return x2; + } + + public float getY2() { + return y2; + } + + public String getText() { + return text; + } +} \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/model/PersistentLogin.java b/src/main/java/stirling/software/SPDF/model/PersistentLogin.java new file mode 100644 index 00000000..0747c1eb --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/PersistentLogin.java @@ -0,0 +1,61 @@ +package stirling.software.SPDF.model; + +import java.util.Date; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "persistent_logins") +public class PersistentLogin { + + @Id + @Column(name = "series") + private String series; + + @Column(name = "username", length = 64, nullable = false) + private String username; + + @Column(name = "token", length = 64, nullable = false) + private String token; + + @Column(name = "last_used", nullable = false) + private Date lastUsed; + + public String getSeries() { + return series; + } + + public void setSeries(String series) { + this.series = series; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public Date getLastUsed() { + return lastUsed; + } + + public void setLastUsed(Date lastUsed) { + this.lastUsed = lastUsed; + } + + + // Getters, setters, etc. +} diff --git a/src/main/java/stirling/software/SPDF/model/Role.java b/src/main/java/stirling/software/SPDF/model/Role.java new file mode 100644 index 00000000..1b775de0 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/Role.java @@ -0,0 +1,50 @@ +package stirling.software.SPDF.model; +public enum Role { + + // Unlimited access + ADMIN("ROLE_ADMIN", Integer.MAX_VALUE, Integer.MAX_VALUE), + + // Unlimited access + USER("ROLE_USER", Integer.MAX_VALUE, Integer.MAX_VALUE), + + // 40 API calls Per Day, 40 web calls + LIMITED_API_USER("ROLE_LIMITED_API_USER", 40, 40), + + // 20 API calls Per Day, 20 web calls + EXTRA_LIMITED_API_USER("ROLE_EXTRA_LIMITED_API_USER", 20, 20), + + // 0 API calls per day and 20 web calls + WEB_ONLY_USER("ROLE_WEB_ONLY_USER", 0, 20); + + private final String roleId; + private final int apiCallsPerDay; + private final int webCallsPerDay; + + Role(String roleId, int apiCallsPerDay, int webCallsPerDay) { + this.roleId = roleId; + this.apiCallsPerDay = apiCallsPerDay; + this.webCallsPerDay = webCallsPerDay; + } + + public String getRoleId() { + return roleId; + } + + public int getApiCallsPerDay() { + return apiCallsPerDay; + } + + public int getWebCallsPerDay() { + return webCallsPerDay; + } + + public static Role fromString(String roleId) { + for (Role role : Role.values()) { + if (role.getRoleId().equalsIgnoreCase(roleId)) { + return role; + } + } + throw new IllegalArgumentException("No Role defined for id: " + roleId); + } + +} diff --git a/src/main/java/stirling/software/SPDF/model/SortTypes.java b/src/main/java/stirling/software/SPDF/model/SortTypes.java new file mode 100644 index 00000000..21181cfa --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/SortTypes.java @@ -0,0 +1,4 @@ +package stirling.software.SPDF.model; +public enum SortTypes { + REVERSE_ORDER, DUPLEX_SORT, BOOKLET_SORT, SIDE_STITCH_BOOKLET_SORT, ODD_EVEN_SPLIT, REMOVE_FIRST, REMOVE_LAST, REMOVE_FIRST_AND_LAST, +} \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/model/User.java b/src/main/java/stirling/software/SPDF/model/User.java new file mode 100644 index 00000000..f771a821 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/User.java @@ -0,0 +1,134 @@ +package stirling.software.SPDF.model; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.MapKeyColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +@Entity +@Table(name = "users") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + @Column(name = "username", unique = true) + private String username; + + @Column(name = "password") + private String password; + + @Column(name = "apiKey") + private String apiKey; + + @Column(name = "enabled") + private boolean enabled; + + @Column(name = "isFirstLogin") + private Boolean isFirstLogin = false; + + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user") + private Set authorities = new HashSet<>(); + + @ElementCollection + @MapKeyColumn(name = "setting_key") + @Column(name = "setting_value") + @CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "user_id")) + private Map settings = new HashMap<>(); // Key-value pairs of settings. + + + public boolean isFirstLogin() { + return isFirstLogin != null && isFirstLogin; + } + + public void setFirstLogin(boolean isFirstLogin) { + this.isFirstLogin = isFirstLogin; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public Map getSettings() { + return settings; + } + + public void setSettings(Map settings) { + this.settings = settings; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + public void addAuthorities(Set authorities) { + this.authorities.addAll(authorities); + } + public void addAuthority(Authority authorities) { + this.authorities.add(authorities); + } + + public String getRolesAsString() { + return this.authorities.stream() + .map(Authority::getAuthority) + .collect(Collectors.joining(", ")); + } + + +} diff --git a/src/main/java/stirling/software/SPDF/model/api/GeneralFile.java b/src/main/java/stirling/software/SPDF/model/api/GeneralFile.java new file mode 100644 index 00000000..441d904a --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/GeneralFile.java @@ -0,0 +1,17 @@ +package stirling.software.SPDF.model.api; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@EqualsAndHashCode +@NoArgsConstructor +public class GeneralFile { + + @Schema(description = "The input file") + private MultipartFile fileInput; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/HandleDataRequest.java b/src/main/java/stirling/software/SPDF/model/api/HandleDataRequest.java new file mode 100644 index 00000000..1d7a8afe --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/HandleDataRequest.java @@ -0,0 +1,20 @@ +package stirling.software.SPDF.model.api; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@EqualsAndHashCode +public class HandleDataRequest { + + @Schema(description = "The input files") + private MultipartFile[] fileInputs; + + @Schema(description = "JSON String") + private String jsonString; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/ImageFile.java b/src/main/java/stirling/software/SPDF/model/api/ImageFile.java new file mode 100644 index 00000000..02079843 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/ImageFile.java @@ -0,0 +1,16 @@ +package stirling.software.SPDF.model.api; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@EqualsAndHashCode +public class ImageFile { + @Schema(description = "The input image file") + private MultipartFile fileInput; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/MultiplePDFFiles.java b/src/main/java/stirling/software/SPDF/model/api/MultiplePDFFiles.java new file mode 100644 index 00000000..937a4265 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/MultiplePDFFiles.java @@ -0,0 +1,15 @@ +package stirling.software.SPDF.model.api; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +@Data +@NoArgsConstructor +@EqualsAndHashCode +public class MultiplePDFFiles { + @Schema(description = "The input PDF files", type = "array", format = "binary") + private MultipartFile[] fileInput; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFComparison.java b/src/main/java/stirling/software/SPDF/model/api/PDFComparison.java new file mode 100644 index 00000000..1f902d88 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/PDFComparison.java @@ -0,0 +1,16 @@ +package stirling.software.SPDF.model.api; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper=true) +public class PDFComparison extends PDFFile { + + @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = { + "Greater", "Equal", "Less" }) + private String comparator; + +} diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFComparisonAndCount.java b/src/main/java/stirling/software/SPDF/model/api/PDFComparisonAndCount.java new file mode 100644 index 00000000..14462f0a --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/PDFComparisonAndCount.java @@ -0,0 +1,15 @@ +package stirling.software.SPDF.model.api; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper=true) +public class PDFComparisonAndCount extends PDFComparison { + @Schema(description = "Count") + private String pageCount; + + +} diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFFile.java b/src/main/java/stirling/software/SPDF/model/api/PDFFile.java new file mode 100644 index 00000000..378b3c03 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/PDFFile.java @@ -0,0 +1,13 @@ +package stirling.software.SPDF.model.api; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +@Data +@EqualsAndHashCode +public class PDFFile { + @Schema(description = "The input PDF file") + private MultipartFile fileInput; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFWithImageFormatRequest.java b/src/main/java/stirling/software/SPDF/model/api/PDFWithImageFormatRequest.java new file mode 100644 index 00000000..aa8fe08b --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/PDFWithImageFormatRequest.java @@ -0,0 +1,14 @@ +package stirling.software.SPDF.model.api; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper=true) +public class PDFWithImageFormatRequest extends PDFFile { + + @Schema(description = "The output image format e.g., 'png', 'jpeg', or 'gif'", + allowableValues = { "png", "jpeg", "gif" }) + private String format; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFWithPageNums.java b/src/main/java/stirling/software/SPDF/model/api/PDFWithPageNums.java new file mode 100644 index 00000000..d53d8d12 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/PDFWithPageNums.java @@ -0,0 +1,39 @@ +package stirling.software.SPDF.model.api; + +import java.io.IOException; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import stirling.software.SPDF.utils.GeneralUtils; +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper=true) +public class PDFWithPageNums extends PDFFile { + + @Schema(description = "The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')\"") + private String pageNumbers; + + + public List getPageNumbersList(){ + int pageCount = 0; + try { + pageCount = PDDocument.load(getFileInput().getInputStream()).getNumberOfPages(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return GeneralUtils.parsePageString(pageNumbers, pageCount); + + } + public List getPageNumbersList(PDDocument doc){ + int pageCount = 0; + pageCount = doc.getNumberOfPages(); + return GeneralUtils.parsePageString(pageNumbers, pageCount); + + } +} diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFWithPageSize.java b/src/main/java/stirling/software/SPDF/model/api/PDFWithPageSize.java new file mode 100644 index 00000000..661a4ffe --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/PDFWithPageSize.java @@ -0,0 +1,16 @@ +package stirling.software.SPDF.model.api; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper=true) +public class PDFWithPageSize extends PDFFile { + + @Schema(description = "The scale of pages in the output PDF. Acceptable values are A0-A6, LETTER, LEGAL.", + allowableValues = { + "A0", "A1", "A2", "A3", "A4", "A5", "A6", "LETTER", "LEGAL" + }) + private String pageSize; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToImageRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToImageRequest.java new file mode 100644 index 00000000..18026618 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToImageRequest.java @@ -0,0 +1,23 @@ +package stirling.software.SPDF.model.api.converters; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class ConvertToImageRequest extends PDFFile { + + @Schema(description = "The output image format", allowableValues = {"png", "jpeg", "jpg", "gif"}) + private String imageFormat; + + @Schema(description = "Choose between a single image containing all pages or separate images for each page", allowableValues = {"single", "multiple"}) + private String singleOrMultiple; + + @Schema(description = "The color type of the output image(s)", allowableValues = {"color", "greyscale", "blackwhite"}) + private String colorType; + + @Schema(description = "The DPI (dots per inch) for the output image(s)") + private String dpi; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToPdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToPdfRequest.java new file mode 100644 index 00000000..37df6f9e --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/converters/ConvertToPdfRequest.java @@ -0,0 +1,27 @@ +package stirling.software.SPDF.model.api.converters; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode +public class ConvertToPdfRequest { + + @Schema(description = "The input images to be converted to a PDF file") + private MultipartFile[] fileInput; + + @Schema(description = "Option to determine how the image will fit onto the page", + allowableValues = { "fillPage", "fitDocumentToImage", "maintainAspectRatio" }) + private String fitOption; + + + + @Schema(description = "The color type of the output image(s)", allowableValues = {"color", "greyscale", "blackwhite"}) + private String colorType; + + @Schema(description = "Whether to automatically rotate the images to better fit the PDF page", example = "true") + private boolean autoRotate; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/PdfToPresentationRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToPresentationRequest.java new file mode 100644 index 00000000..0e8b79ad --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToPresentationRequest.java @@ -0,0 +1,14 @@ +package stirling.software.SPDF.model.api.converters; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class PdfToPresentationRequest extends PDFFile { + + @Schema(description = "The output Presentation format", allowableValues = {"ppt", "pptx", "odp"}) + private String outputFormat; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/PdfToTextOrRTFRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToTextOrRTFRequest.java new file mode 100644 index 00000000..687ed621 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToTextOrRTFRequest.java @@ -0,0 +1,14 @@ +package stirling.software.SPDF.model.api.converters; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class PdfToTextOrRTFRequest extends PDFFile { + + @Schema(description = "The output Text or RTF format", allowableValues = {"rtf", "txt:Text"}) + private String outputFormat; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/PdfToWordRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToWordRequest.java new file mode 100644 index 00000000..87150c73 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToWordRequest.java @@ -0,0 +1,14 @@ +package stirling.software.SPDF.model.api.converters; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class PdfToWordRequest extends PDFFile { + + @Schema(description = "The output Word document format", allowableValues = {"doc", "docx", "odt"}) + private String outputFormat; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/UrlToPdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/UrlToPdfRequest.java new file mode 100644 index 00000000..4607c153 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/converters/UrlToPdfRequest.java @@ -0,0 +1,13 @@ +package stirling.software.SPDF.model.api.converters; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode +public class UrlToPdfRequest { + + @Schema(description = "The input URL to be converted to a PDF file", required = true) + private String urlInput; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/filter/ContainsTextRequest.java b/src/main/java/stirling/software/SPDF/model/api/filter/ContainsTextRequest.java new file mode 100644 index 00000000..0b6cb1cb --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/filter/ContainsTextRequest.java @@ -0,0 +1,14 @@ +package stirling.software.SPDF.model.api.filter; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFWithPageNums; + +@Data +@EqualsAndHashCode(callSuper=true) +public class ContainsTextRequest extends PDFWithPageNums { + + @Schema(description = "The text to check for", required = true) + private String text; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/filter/FileSizeRequest.java b/src/main/java/stirling/software/SPDF/model/api/filter/FileSizeRequest.java new file mode 100644 index 00000000..ce9a9236 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/filter/FileSizeRequest.java @@ -0,0 +1,16 @@ +package stirling.software.SPDF.model.api.filter; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFComparison; + +@Data +@EqualsAndHashCode(callSuper=true) +public class FileSizeRequest extends PDFComparison { + + @Schema(description = "File Size", required = true) + private String fileSize; + + +} diff --git a/src/main/java/stirling/software/SPDF/model/api/filter/PageRotationRequest.java b/src/main/java/stirling/software/SPDF/model/api/filter/PageRotationRequest.java new file mode 100644 index 00000000..d5fb9739 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/filter/PageRotationRequest.java @@ -0,0 +1,15 @@ +package stirling.software.SPDF.model.api.filter; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFComparison; + +@Data +@EqualsAndHashCode(callSuper=true) +public class PageRotationRequest extends PDFComparison { + + @Schema(description = "Rotation in degrees", required = true) + private int rotation; + +} diff --git a/src/main/java/stirling/software/SPDF/model/api/filter/PageSizeRequest.java b/src/main/java/stirling/software/SPDF/model/api/filter/PageSizeRequest.java new file mode 100644 index 00000000..12083636 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/filter/PageSizeRequest.java @@ -0,0 +1,16 @@ +package stirling.software.SPDF.model.api.filter; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFComparison; + +@Data +@EqualsAndHashCode(callSuper=true) +public class PageSizeRequest extends PDFComparison { + + @Schema(description = "Standard Page Size", required = true) + private String standardPageSize; + + +} diff --git a/src/main/java/stirling/software/SPDF/model/api/general/CropPdfForm.java b/src/main/java/stirling/software/SPDF/model/api/general/CropPdfForm.java new file mode 100644 index 00000000..52821515 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/general/CropPdfForm.java @@ -0,0 +1,24 @@ +package stirling.software.SPDF.model.api.general; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class CropPdfForm extends PDFFile { + + + @Schema(description = "The x-coordinate of the top-left corner of the crop area", type = "number") + private float x; + + @Schema(description = "The y-coordinate of the top-left corner of the crop area", type = "number") + private float y; + + @Schema(description = "The width of the crop area", type = "number") + private float width; + + @Schema(description = "The height of the crop area", type = "number") + private float height; +} + diff --git a/src/main/java/stirling/software/SPDF/model/api/general/MergeMultiplePagesRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/MergeMultiplePagesRequest.java new file mode 100644 index 00000000..4642cb75 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/general/MergeMultiplePagesRequest.java @@ -0,0 +1,18 @@ +package stirling.software.SPDF.model.api.general; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class MergeMultiplePagesRequest extends PDFFile { + + @Schema(description = "The number of pages to fit onto a single sheet in the output PDF.", + type = "integer", allowableValues = {"2", "3", "4", "9", "16"}) + private int pagesPerSheet; + + @Schema(description = "Boolean for if you wish to add border around the pages") + private boolean addBorder; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/general/MergePdfsRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/MergePdfsRequest.java new file mode 100644 index 00000000..b7b3bda7 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/general/MergePdfsRequest.java @@ -0,0 +1,22 @@ +package stirling.software.SPDF.model.api.general; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.MultiplePDFFiles; + +@Data +@EqualsAndHashCode(callSuper=true) +public class MergePdfsRequest extends MultiplePDFFiles { + + @Schema(description = "The type of sorting to be applied on the input files before merging.", + allowableValues = { + "orderProvided", + "byFileName", + "byDateModified", + "byDateCreated", + "byPDFTitle" + }, + defaultValue = "orderProvided") + private String sortType = "orderProvided"; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java new file mode 100644 index 00000000..3e5b4f23 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java @@ -0,0 +1,23 @@ +package stirling.software.SPDF.model.api.general; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.SortTypes; +import stirling.software.SPDF.model.api.PDFWithPageNums; +@Data +@EqualsAndHashCode(callSuper=true) +public class RearrangePagesRequest extends PDFWithPageNums { + + @Schema(implementation = SortTypes.class, description = "The custom mode for page rearrangement. Valid values are:\n" + + "REVERSE_ORDER: Reverses the order of all pages.\n" + + "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). " + + "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n" + + "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n" + + "REMOVE_FIRST: Removes the first page.\n" + "REMOVE_LAST: Removes the last page.\n" + + "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n") + private String customMode; + + + +} diff --git a/src/main/java/stirling/software/SPDF/model/api/general/RotatePDFRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/RotatePDFRequest.java new file mode 100644 index 00000000..8f48c605 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/general/RotatePDFRequest.java @@ -0,0 +1,14 @@ +package stirling.software.SPDF.model.api.general; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class RotatePDFRequest extends PDFFile { + + @Schema(description = "The angle by which to rotate the PDF file. This should be a multiple of 90.", example = "90") + private Integer angle; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/general/ScalePagesRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/ScalePagesRequest.java new file mode 100644 index 00000000..28be3990 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/general/ScalePagesRequest.java @@ -0,0 +1,15 @@ +package stirling.software.SPDF.model.api.general; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; +import stirling.software.SPDF.model.api.PDFWithPageSize; + +@Data +@EqualsAndHashCode(callSuper=true) +public class ScalePagesRequest extends PDFWithPageSize { + + @Schema(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.") + private float scaleFactor; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/AddPageNumbersRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/AddPageNumbersRequest.java new file mode 100644 index 00000000..313d42ab --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/misc/AddPageNumbersRequest.java @@ -0,0 +1,26 @@ +package stirling.software.SPDF.model.api.misc; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFWithPageNums; + +@Data +@EqualsAndHashCode(callSuper=true) +public class AddPageNumbersRequest extends PDFWithPageNums { + + @Schema(description = "Custom margin: small/medium/large", allowableValues = {"small", "medium", "large"}) + private String customMargin; + + @Schema(description = "Position: 1 of 9 positions", minimum = "1", maximum = "9") + private int position; + + @Schema(description = "Starting number", minimum = "1") + private int startingNumber; + + @Schema(description = "Which pages to number, default all") + private String pagesToNumber; + + @Schema(description = "Custom text: defaults to just number but can have things like \"Page {n} of {p}\"") + private String customText; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/AutoSplitPdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/AutoSplitPdfRequest.java new file mode 100644 index 00000000..c4923746 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/misc/AutoSplitPdfRequest.java @@ -0,0 +1,14 @@ +package stirling.software.SPDF.model.api.misc; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class AutoSplitPdfRequest extends PDFFile { + + @Schema(description = "Flag indicating if the duplex mode is active, where the page after the divider also gets removed.", required = false, defaultValue = "false") + private boolean duplexMode; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/ExtractHeaderRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/ExtractHeaderRequest.java new file mode 100644 index 00000000..f3028445 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/misc/ExtractHeaderRequest.java @@ -0,0 +1,14 @@ +package stirling.software.SPDF.model.api.misc; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class ExtractHeaderRequest extends PDFFile { + + @Schema(description = "Flag indicating whether to use the first text as a fallback if no suitable title is found. Defaults to false.", required = false, defaultValue = "false") + private boolean useFirstTextAsFallback; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/ExtractImageScansRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/ExtractImageScansRequest.java new file mode 100644 index 00000000..1a575fe6 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/misc/ExtractImageScansRequest.java @@ -0,0 +1,29 @@ +package stirling.software.SPDF.model.api.misc; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode +public class ExtractImageScansRequest { + @Schema(description = "The input file containing image scans", required = true) + private MultipartFile fileInput; + + @Schema(description = "The angle threshold for the image scan extraction", defaultValue = "5", example = "5") + private int angleThreshold = 5; + + @Schema(description = "The tolerance for the image scan extraction", defaultValue = "20", example = "20") + private int tolerance = 20; + + @Schema(description = "The minimum area for the image scan extraction", defaultValue = "8000", example = "8000") + private int minArea = 8000; + + @Schema(description = "The minimum contour area for the image scan extraction", defaultValue = "500", example = "500") + private int minContourArea = 500; + + @Schema(description = "The border size for the image scan extraction", defaultValue = "1", example = "1") + private int borderSize =1; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/MetadataRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/MetadataRequest.java new file mode 100644 index 00000000..d62890aa --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/misc/MetadataRequest.java @@ -0,0 +1,46 @@ +package stirling.software.SPDF.model.api.misc; + +import java.util.Map; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class MetadataRequest extends PDFFile { + + @Schema(description = "Delete all metadata if set to true") + private boolean deleteAll; + + @Schema(description = "The author of the document") + private String author; + + @Schema(description = "The creation date of the document (format: yyyy/MM/dd HH:mm:ss)") + private String creationDate; + + @Schema(description = "The creator of the document") + private String creator; + + @Schema(description = "The keywords for the document") + private String keywords; + + @Schema(description = "The modification date of the document (format: yyyy/MM/dd HH:mm:ss)") + private String modificationDate; + + @Schema(description = "The producer of the document") + private String producer; + + @Schema(description = "The subject of the document") + private String subject; + + @Schema(description = "The title of the document") + private String title; + + @Schema(description = "The trapped status of the document") + private String trapped; + + @Schema(description = "Map list of key and value of custom parameters. Note these must start with customKey and customValue if they are non-standard") + private Map allRequestParams; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/OptimizePdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/OptimizePdfRequest.java new file mode 100644 index 00000000..bc00cf20 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/misc/OptimizePdfRequest.java @@ -0,0 +1,18 @@ +package stirling.software.SPDF.model.api.misc; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class OptimizePdfRequest extends PDFFile { + + @Schema(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", + allowableValues = { "1", "2", "3", "4", "5" }) + private Integer optimizeLevel; + + @Schema(description = "The expected output size, e.g. '100MB', '25KB', etc.") + private String expectedOutputSize; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/OverlayImageRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/OverlayImageRequest.java new file mode 100644 index 00000000..50ec4abb --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/misc/OverlayImageRequest.java @@ -0,0 +1,25 @@ +package stirling.software.SPDF.model.api.misc; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class OverlayImageRequest extends PDFFile { + + @Schema(description = "The image file to be overlaid onto the PDF.") + private MultipartFile imageFile; + + @Schema(description = "The x-coordinate at which to place the top-left corner of the image.", example = "0") + private float x; + + @Schema(description = "The y-coordinate at which to place the top-left corner of the image.", example = "0") + private float y; + + @Schema(description = "Whether to overlay the image onto every page of the PDF.", example = "false") + private boolean everyPage; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/ProcessPdfWithOcrRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/ProcessPdfWithOcrRequest.java new file mode 100644 index 00000000..392f8d54 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/misc/ProcessPdfWithOcrRequest.java @@ -0,0 +1,37 @@ +package stirling.software.SPDF.model.api.misc; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class ProcessPdfWithOcrRequest extends PDFFile { + + @Schema(description = "List of languages to use in OCR processing") + private List 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(description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'", allowableValues = {"skip-text", "force-ocr", "Normal"}) + private String ocrType; + + @Schema(description = "Specify the OCR render type, either 'hocr' or 'sandwich'", allowableValues = {"hocr", "sandwich"}, defaultValue = "hocr") + private String ocrRenderType = "hocr"; + + @Schema(description = "Remove images from the output PDF if set to true") + private boolean removeImagesAfter; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/RemoveBlankPagesRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/RemoveBlankPagesRequest.java new file mode 100644 index 00000000..0d2e11c7 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/misc/RemoveBlankPagesRequest.java @@ -0,0 +1,17 @@ +package stirling.software.SPDF.model.api.misc; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class RemoveBlankPagesRequest extends PDFFile { + + @Schema(description = "The threshold value to determine blank pages", example = "10", defaultValue = "10") + private int threshold = 10; + + @Schema(description = "The percentage of white color on a page to consider it as blank", example = "99.9", defaultValue = "99.9") + private float whitePercent = 99.9f; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/security/AddPasswordRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/AddPasswordRequest.java new file mode 100644 index 00000000..ea83e470 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/security/AddPasswordRequest.java @@ -0,0 +1,44 @@ +package stirling.software.SPDF.model.api.security; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class AddPasswordRequest extends PDFFile { + + @Schema(description = "The owner password to be added to the PDF file (Restricts what can be done with the document once it is opened)", defaultValue = "") + private String ownerPassword; + + @Schema(description = "The password to be added to the PDF file (Restricts the opening of the document itself.)", defaultValue = "") + private String password; + + @Schema(description = "The length of the encryption key", allowableValues = {"40", "128", "256"}, defaultValue = "256") + private int keyLength = 256; + + @Schema(description = "Whether the document assembly is allowed", example = "false") + private boolean canAssembleDocument; + + @Schema(description = "Whether content extraction for accessibility is allowed", example = "false") + private boolean canExtractContent; + + @Schema(description = "Whether content extraction for accessibility is allowed", example = "false") + private boolean canExtractForAccessibility; + + @Schema(description = "Whether form filling is allowed", example = "false") + private boolean canFillInForm; + + @Schema(description = "Whether the document modification is allowed", example = "false") + private boolean canModify; + + @Schema(description = "Whether modification of annotations is allowed", example = "false") + private boolean canModifyAnnotations; + + @Schema(description = "Whether printing of the document is allowed", example = "false") + private boolean canPrint; + + @Schema(description = "Whether faithful printing is allowed", example = "false") + private boolean canPrintFaithful; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/security/AddWatermarkRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/AddWatermarkRequest.java new file mode 100644 index 00000000..cd800948 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/security/AddWatermarkRequest.java @@ -0,0 +1,44 @@ +package stirling.software.SPDF.model.api.security; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class AddWatermarkRequest extends PDFFile { + + @Schema(description = "The watermark type (text or image)", + allowableValues = {"text", "image"}, + required = true) + private String watermarkType; + + @Schema(description = "The watermark text") + private String watermarkText; + + @Schema(description = "The watermark image") + private MultipartFile watermarkImage; + + @Schema(description = "The selected alphabet", + allowableValues = {"roman", "arabic", "japanese", "korean", "chinese"}, + defaultValue = "roman") + private String alphabet = "roman"; + + @Schema(description = "The font size of the watermark text", example = "30") + private float fontSize = 30; + + @Schema(description = "The rotation of the watermark in degrees", example = "0") + private float rotation = 0; + + @Schema(description = "The opacity of the watermark (0.0 - 1.0)", example = "0.5") + private float opacity; + + @Schema(description = "The width spacer between watermark elements", example = "50") + private int widthSpacer; + + @Schema(description = "The height spacer between watermark elements", example = "50") + private int heightSpacer; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/security/PDFPasswordRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/PDFPasswordRequest.java new file mode 100644 index 00000000..94d04d1e --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/security/PDFPasswordRequest.java @@ -0,0 +1,14 @@ +package stirling.software.SPDF.model.api.security; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class PDFPasswordRequest extends PDFFile { + + @Schema(description = "The password of the PDF file", required = true) + private String password; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/security/RedactPdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/RedactPdfRequest.java new file mode 100644 index 00000000..1966c53a --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/security/RedactPdfRequest.java @@ -0,0 +1,29 @@ +package stirling.software.SPDF.model.api.security; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class RedactPdfRequest extends PDFFile { + + @Schema(description = "List of text to redact from the PDF", type = "string", required = true) + private String listOfText; + + @Schema(description = "Whether to use regex for the listOfText", defaultValue = "false") + private boolean useRegex; + + @Schema(description = "Whether to use whole word search", defaultValue = "false") + private boolean wholeWordSearch; + + @Schema(description = "The color for redaction", defaultValue = "#000000") + private String redactColor = "#000000"; + + @Schema(description = "Custom padding for redaction", type = "number") + private float customPadding; + + @Schema(description = "Convert the redacted PDF to an image", defaultValue = "false") + private boolean convertPDFToImage; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/security/SanitizePdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/SanitizePdfRequest.java new file mode 100644 index 00000000..98c7743f --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/security/SanitizePdfRequest.java @@ -0,0 +1,26 @@ +package stirling.software.SPDF.model.api.security; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class SanitizePdfRequest extends PDFFile { + + @Schema(description = "Remove JavaScript actions from the PDF", defaultValue = "false") + private boolean removeJavaScript; + + @Schema(description = "Remove embedded files from the PDF", defaultValue = "false") + private boolean removeEmbeddedFiles; + + @Schema(description = "Remove metadata from the PDF", defaultValue = "false") + private boolean removeMetadata; + + @Schema(description = "Remove links from the PDF", defaultValue = "false") + private boolean removeLinks; + + @Schema(description = "Remove fonts from the PDF", defaultValue = "false") + private boolean removeFonts; +} diff --git a/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java new file mode 100644 index 00000000..8e537c6a --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java @@ -0,0 +1,43 @@ +package stirling.software.SPDF.model.api.security; + +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class SignPDFWithCertRequest extends PDFFile { + + @Schema(description = "The type of the digital certificate", allowableValues = { "PKCS12", "PEM" }) + private String certType; + + @Schema(description = "The private key for the digital certificate (required for PEM type certificates)") + private MultipartFile privateKeyFile; + + @Schema(description = "The digital certificate (required for PEM type certificates)") + private MultipartFile certFile; + + @Schema(description = "The PKCS12 keystore file (required for PKCS12 type certificates)") + private MultipartFile p12File; + + @Schema(description = "The password for the keystore or the private key") + private String password; + + @Schema(description = "Whether to visually show the signature in the PDF file") + private boolean showSignature; + + @Schema(description = "The reason for signing the PDF") + private String reason; + + @Schema(description = "The location where the PDF is signed") + private String location; + + @Schema(description = "The name of the signer") + private String name; + + @Schema(description = "The page number where the signature should be visible. This is required if showSignature is set to true") + private Integer pageNumber; +} diff --git a/src/main/java/stirling/software/SPDF/pdf/TextFinder.java b/src/main/java/stirling/software/SPDF/pdf/TextFinder.java new file mode 100644 index 00000000..f7eb9e3f --- /dev/null +++ b/src/main/java/stirling/software/SPDF/pdf/TextFinder.java @@ -0,0 +1,90 @@ +package stirling.software.SPDF.pdf; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.text.PDFTextStripper; +import org.apache.pdfbox.text.TextPosition; + +import stirling.software.SPDF.model.PDFText; + +public class TextFinder extends PDFTextStripper { + + private final String searchText; + private final boolean useRegex; + private final boolean wholeWordSearch; + private final List textOccurrences = new ArrayList<>(); + + public TextFinder(String searchText, boolean useRegex, boolean wholeWordSearch) throws IOException { + this.searchText = searchText.toLowerCase(); + this.useRegex = useRegex; + this.wholeWordSearch = wholeWordSearch; + setSortByPosition(true); + } + + private List findOccurrencesInText(String searchText, String content) { + List indexes = new ArrayList<>(); + Pattern pattern; + + if (useRegex) { + // Use regex-based search + pattern = wholeWordSearch + ? Pattern.compile("(\\b|_|\\.)" + searchText + "(\\b|_|\\.)") + : Pattern.compile(searchText); + } else { + // Use normal text search + pattern = wholeWordSearch + ? Pattern.compile("(\\b|_|\\.)" + Pattern.quote(searchText) + "(\\b|_|\\.)") + : Pattern.compile(Pattern.quote(searchText)); + } + + Matcher matcher = pattern.matcher(content); + while (matcher.find()) { + indexes.add(matcher.start()); + } + return indexes; + } + + @Override + protected void writeString(String text, List textPositions) { + for (Integer index : findOccurrencesInText(searchText, text.toLowerCase())) { + if (index + searchText.length() <= textPositions.size()) { + // Initial values based on the first character + TextPosition first = textPositions.get(index); + float minX = first.getX(); + float minY = first.getY(); + float maxX = first.getX() + first.getWidth(); + float maxY = first.getY() + first.getHeight(); + + // Loop over the rest of the characters and adjust bounding box values + for (int i = index; i < index + searchText.length(); i++) { + TextPosition position = textPositions.get(i); + minX = Math.min(minX, position.getX()); + minY = Math.min(minY, position.getY()); + maxX = Math.max(maxX, position.getX() + position.getWidth()); + maxY = Math.max(maxY, position.getY() + position.getHeight()); + } + + textOccurrences.add(new PDFText( + getCurrentPageNo() - 1, + minX, + minY, + maxX, + maxY, + text + )); + } + } + } + + public List getTextLocations(PDDocument document) throws Exception { + this.getText(document); + System.out.println("Found " + textOccurrences.size() + " occurrences of '" + searchText + "' in the document."); + + return textOccurrences; + } + +} \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/repository/AuthorityRepository.java b/src/main/java/stirling/software/SPDF/repository/AuthorityRepository.java new file mode 100644 index 00000000..62f546b8 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/repository/AuthorityRepository.java @@ -0,0 +1,12 @@ +package stirling.software.SPDF.repository; + +import java.util.Set; + +import org.springframework.data.jpa.repository.JpaRepository; + +import stirling.software.SPDF.model.Authority; + +public interface AuthorityRepository extends JpaRepository { + //Set findByUsername(String username); + Set findByUser_Username(String username); +} diff --git a/src/main/java/stirling/software/SPDF/repository/JPATokenRepositoryImpl.java b/src/main/java/stirling/software/SPDF/repository/JPATokenRepositoryImpl.java new file mode 100644 index 00000000..e7753903 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/repository/JPATokenRepositoryImpl.java @@ -0,0 +1,53 @@ +package stirling.software.SPDF.repository; + +import java.util.Date; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; + +import stirling.software.SPDF.model.PersistentLogin; + +public class JPATokenRepositoryImpl implements PersistentTokenRepository { + + @Autowired + private PersistentLoginRepository persistentLoginRepository; + + @Override + public void createNewToken(PersistentRememberMeToken token) { + PersistentLogin newToken = new PersistentLogin(); + newToken.setSeries(token.getSeries()); + newToken.setUsername(token.getUsername()); + newToken.setToken(token.getTokenValue()); + newToken.setLastUsed(token.getDate()); + persistentLoginRepository.save(newToken); + } + + @Override + public void updateToken(String series, String tokenValue, Date lastUsed) { + PersistentLogin existingToken = persistentLoginRepository.findById(series).orElse(null); + if (existingToken != null) { + existingToken.setToken(tokenValue); + existingToken.setLastUsed(lastUsed); + persistentLoginRepository.save(existingToken); + } + } + + @Override + public PersistentRememberMeToken getTokenForSeries(String seriesId) { + PersistentLogin token = persistentLoginRepository.findById(seriesId).orElse(null); + if (token != null) { + return new PersistentRememberMeToken(token.getUsername(), token.getSeries(), token.getToken(), token.getLastUsed()); + } + return null; + } + + @Override + public void removeUserTokens(String username) { + for (PersistentLogin token : persistentLoginRepository.findAll()) { + if (token.getUsername().equals(username)) { + persistentLoginRepository.delete(token); + } + } + } +} diff --git a/src/main/java/stirling/software/SPDF/repository/PersistentLoginRepository.java b/src/main/java/stirling/software/SPDF/repository/PersistentLoginRepository.java new file mode 100644 index 00000000..10c1acae --- /dev/null +++ b/src/main/java/stirling/software/SPDF/repository/PersistentLoginRepository.java @@ -0,0 +1,8 @@ +package stirling.software.SPDF.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import stirling.software.SPDF.model.PersistentLogin; + +public interface PersistentLoginRepository extends JpaRepository { +} diff --git a/src/main/java/stirling/software/SPDF/repository/UserRepository.java b/src/main/java/stirling/software/SPDF/repository/UserRepository.java new file mode 100644 index 00000000..744953d7 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/repository/UserRepository.java @@ -0,0 +1,13 @@ +package stirling.software.SPDF.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import stirling.software.SPDF.model.User; + +public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); + User findByApiKey(String apiKey); +} + diff --git a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java index 28a1e73e..3097fc1b 100644 --- a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java @@ -65,7 +65,8 @@ public class GeneralUtils { } else if (sizeStr.endsWith("B")) { return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); } else { - // Input string does not have a valid format, handle this case + // Assume MB if no unit is specified + return (long) (Double.parseDouble(sizeStr) * 1024 * 1024); } } catch (NumberFormatException e) { // The numeric part of the input string cannot be parsed, handle this case @@ -74,6 +75,9 @@ public class GeneralUtils { return null; } + public static List parsePageString(String pageOrder, int totalPages) { + return parsePageList(pageOrder.split(","), totalPages); + } public static List parsePageList(String[] pageOrderArr, int totalPages) { List newPageOrder = new ArrayList<>(); diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 5b116a93..2f57fe10 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -32,10 +32,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.multipart.MultipartFile; -import com.itextpdf.kernel.pdf.PdfPage; -import com.itextpdf.kernel.pdf.canvas.parser.PdfTextExtractor; -import com.itextpdf.kernel.pdf.canvas.parser.listener.SimpleTextExtractionStrategy; - import stirling.software.SPDF.pdf.ImageFinder; public class PdfUtils { @@ -252,7 +248,7 @@ public class PdfUtils { throw e; } } - public static byte[] imageToPdf(MultipartFile[] files, boolean stretchToFit, boolean autoRotate, String colorType) throws IOException { + public static byte[] imageToPdf(MultipartFile[] files, String fitOption, boolean autoRotate, String colorType) throws IOException { try (PDDocument doc = new PDDocument()) { for (MultipartFile file : files) { String contentType = file.getContentType(); @@ -265,7 +261,7 @@ public class PdfUtils { BufferedImage pageImage = reader.read(i); BufferedImage convertedImage = ImageProcessingUtils.convertColorType(pageImage, colorType); PDImageXObject pdImage = LosslessFactory.createFromImage(doc, convertedImage); - addImageToDocument(doc, pdImage, stretchToFit, autoRotate); + addImageToDocument(doc, pdImage, fitOption, autoRotate); } } else { File imageFile = Files.createTempFile("image", ".png").toFile(); @@ -283,7 +279,7 @@ public class PdfUtils { } else { pdImage = LosslessFactory.createFromImage(doc, convertedImage); } - addImageToDocument(doc, pdImage, stretchToFit, autoRotate); + addImageToDocument(doc, pdImage, fitOption, autoRotate); } catch (IOException e) { logger.error("Error writing image to file: {}", imageFile.getAbsolutePath(), e); throw e; @@ -299,12 +295,20 @@ public class PdfUtils { } } - private static void addImageToDocument(PDDocument doc, PDImageXObject image, boolean stretchToFit, boolean autoRotate) throws IOException { + private static void addImageToDocument(PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate) throws IOException { boolean imageIsLandscape = image.getWidth() > image.getHeight(); PDRectangle pageSize = PDRectangle.A4; + + System.out.println(fitOption); + if (autoRotate && imageIsLandscape) { pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth()); } + + if ("fitDocumentToImage".equals(fitOption)) { + pageSize = new PDRectangle(image.getWidth(), image.getHeight()); + } + PDPage page = new PDPage(pageSize); doc.addPage(page); @@ -312,9 +316,9 @@ public class PdfUtils { float pageHeight = page.getMediaBox().getHeight(); try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) { - if (stretchToFit) { + if ("fillPage".equals(fitOption) || "fitDocumentToImage".equals(fitOption)) { contentStream.drawImage(image, 0, 0, pageWidth, pageHeight); - } else { + } else if ("maintainAspectRatio".equals(fitOption)) { float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight(); float pageAspectRatio = pageWidth / pageHeight; @@ -335,6 +339,7 @@ public class PdfUtils { } } + public static byte[] overlayImage(byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) throws IOException { PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes)); diff --git a/src/main/java/stirling/software/SPDF/utils/PropertyConfigs.java b/src/main/java/stirling/software/SPDF/utils/PropertyConfigs.java new file mode 100644 index 00000000..8d12267c --- /dev/null +++ b/src/main/java/stirling/software/SPDF/utils/PropertyConfigs.java @@ -0,0 +1,49 @@ +package stirling.software.SPDF.utils; + +import java.util.List; + +public class PropertyConfigs { + + + public static boolean getBooleanValue(List keys, boolean defaultValue) { + for (String key : keys) { + String value = System.getProperty(key); + if (value == null) + value = System.getenv(key); + + if (value != null) + return Boolean.valueOf(value); + } + return defaultValue; + } + + public static String getStringValue(List keys, String defaultValue) { + for (String key : keys) { + String value = System.getProperty(key); + if (value == null) + value = System.getenv(key); + + if (value != null) + return value; + } + return defaultValue; + } + + + + + public static boolean getBooleanValue(String key, boolean defaultValue) { + String value = System.getProperty(key); + if (value == null) + value = System.getenv(key); + return (value != null) ? Boolean.valueOf(value) : defaultValue; + } + + public static String getStringValue(String key, String defaultValue) { + String value = System.getProperty(key); + if (value == null) + value = System.getenv(key); + return (value != null) ? value : defaultValue; + } + +} diff --git a/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java b/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java index 09a395ba..131aaf03 100644 --- a/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java @@ -12,8 +12,6 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfWriter; public class WebResponseUtils { @@ -61,18 +59,6 @@ public class WebResponseUtils { return boasToWebResponse(baos, docName); } - public static ResponseEntity pdfDocToWebResponse(PdfDocument document, String docName) throws IOException { - - // Open Byte Array and save document to it - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PdfWriter writer = new PdfWriter(baos); - PdfDocument newDocument = new PdfDocument(writer); - - document.copyPagesTo(1, document.getNumberOfPages(), newDocument); - newDocument.close(); - - return boasToWebResponse(baos, docName); - } - + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c4118342..f8cfc519 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,12 +1,5 @@ -spring.http.multipart.max-file-size=${MAX_FILE_SIZE:2000MB} -spring.http.multipart.max-request-size=${MAX_FILE_SIZE:2000MB} - multipart.enabled=true -multipart.max-file-size=${MAX_FILE_SIZE:2000MB} -multipart.max-request-size=${MAX_FILE_SIZE:2000MB} -spring.servlet.multipart.max-file-size=${MAX_FILE_SIZE:2000MB} -spring.servlet.multipart.max-request-size=${MAX_FILE_SIZE:2000MB} server.forward-headers-strategy=NATIVE @@ -17,19 +10,38 @@ server.error.include-exception=true server.error.include-message=always #logging.level.org.springframework.web=DEBUG +#logging.level.org.springframework=DEBUG +#logging.level.org.springframework.security=DEBUG +spring.servlet.multipart.max-file-size=2000MB +spring.servlet.multipart.max-request-size=2000MB server.servlet.session.tracking-modes=cookie -server.servlet.context-path=${APP_ROOT_PATH:/} +server.servlet.context-path=${SYSTEM_ROOTURIPATH:/} spring.devtools.restart.enabled=true spring.devtools.livereload.enabled=true spring.thymeleaf.encoding=UTF-8 -server.connection-timeout=${CONNECTION_TIMEOUT:5m} -spring.mvc.async.request-timeout=${ASYNC_CONNECTION_TIMEOUT:300000} +server.connection-timeout=${SYSTEM_CONNECTIONTIMEOUTMINUTES:5m} +spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:300000} spring.resources.static-locations=file:customFiles/static/ #spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/ -#spring.thymeleaf.cache=false \ No newline at end of file +#spring.thymeleaf.cache=false + +spring.datasource.url=jdbc:h2:file:./configs/stirling-pdf-DB;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.h2.console.enabled=true +spring.jpa.hibernate.ddl-auto=update + +# Change the default URL path for OpenAPI JSON +springdoc.api-docs.path=/v1/api-docs + +# Set the URL of the OpenAPI JSON for the Swagger UI +springdoc.swagger-ui.url=/v1/api-docs + + diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index 53a0fa44..d8722e95 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -31,6 +31,24 @@ sizes.medium=Medium sizes.large=Large sizes.x-large=X-Large error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +delete=Delete +username=Username +password=Password +welcome=Welcome +property=Property +black=Black +white=White +red=Red +green=Green +blue=Blue +custom=Custom... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -54,6 +72,55 @@ settings.downloadOption.1=\u0641\u062A\u062D \u0641\u064A \u0646\u0641\u0633 \u0 settings.downloadOption.2=\u0641\u062A\u062D \u0641\u064A \u0646\u0627\u0641\u0630\u0629 \u062C\u062F\u064A\u062F\u0629 settings.downloadOption.3=\u062A\u0646\u0632\u064A\u0644 \u0627\u0644\u0645\u0644\u0641 settings.zipThreshold=\u0645\u0644\u0641\u0627\u062A \u0645\u0636\u063A\u0648\u0637\u0629 \u0639\u0646\u062F \u062A\u062C\u0627\u0648\u0632 \u0639\u062F\u062F \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0627\u0644\u062A\u064A \u062A\u0645 \u062A\u0646\u0632\u064A\u0644\u0647\u0627 +settings.signOut=Sign Out +settings.accountSettings=Account Settings + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Account Settings +account.accountSettings=Account Settings +account.adminSettings=Admin Settings - View and Add Users +account.userControlSettings=User Control Settings +account.changeUsername=Change Username +account.changeUsername=Change Username +account.password=Confirmation Password +account.oldPassword=Old password +account.newPassword=New Password +account.changePassword=Change Password +account.confirmNewPassword=Confirm New Password +account.signOut=Sign Out +account.yourApiKey=Your API Key +account.syncTitle=Sync browser settings with Account +account.settingsCompare=Settings Comparison: +account.property=Property +account.webBrowserSettings=Web Browser Setting +account.syncToBrowser=Sync Account -> Browser +account.syncToAccount=Sync Account <- Browser + + +adminUserSettings.title=User Control Settings +adminUserSettings.header=Admin User Control Settings +adminUserSettings.admin=Admin +adminUserSettings.user=User +adminUserSettings.addUser=Add New User +adminUserSettings.roles=Roles +adminUserSettings.role=Role +adminUserSettings.actions=Actions +adminUserSettings.apiUser=Limited API User +adminUserSettings.webOnlyUser=Web Only User +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Save User ############# # HOME-PAGE # @@ -256,22 +323,42 @@ home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page -########################## -### TODO: Translate ### -########################## home.showJS.title=Show Javascript home.showJS.desc=Searches and displays any JS injected into a PDF showJS.tags=JS +home.autoRedact.title=Auto Redact +home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### +#login +login.title=Sign in +login.signin=Sign in +login.rememberme=Remember me +login.invalid=Invalid username or password. +login.locked=Your account has been locked. +login.signinTitle=Please sign in + + +#auto-redact +autoRedact.title=Auto Redact +autoRedact.header=Auto Redact +autoRedact.colorLabel=Colour +autoRedact.textsToRedactLabel=Text to Redact (line-separated) +autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret +autoRedact.useRegexLabel=Use Regex +autoRedact.wholeWordSearchLabel=Whole Word Search +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) +autoRedact.submitButton=Submit + + #showJS -########################## -### TODO: Translate ### -########################## showJS.title=Show Javascript showJS.header=Show Javascript showJS.downloadJS=Download Javascript @@ -341,6 +428,9 @@ addPageNumbers.selectText.3=Position addPageNumbers.selectText.4=Starting Number addPageNumbers.selectText.5=Pages to Number addPageNumbers.selectText.6=Custom Text +addPageNumbers.customTextDesc=Custom Text +addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} addPageNumbers.submit=Add Page Numbers @@ -388,6 +478,10 @@ pipeline.title=Pipeline pageLayout.title=Multi Page Layout pageLayout.header=Multi Page Layout pageLayout.pagesPerSheet=Pages per sheet: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders pageLayout.submit=Submit @@ -526,6 +620,8 @@ addImage.submit=إضافة صورة #merge merge.title=دمج merge.header=دمج ملفات PDF متعددة (2+) +merge.sortByName=Sort by name +merge.sortByDate=Sort by date merge.submit=دمج @@ -573,7 +669,10 @@ split.submit=Split imageToPDF.title=صورة إلى PDF imageToPDF.header=صورة إلى PDF imageToPDF.submit=تحول -imageToPDF.selectText.1=\u062A\u0645\u062F\u062F \u0644\u0644\u0645\u0644\u0627\u0621\u0645\u0629 +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=\u062F\u0648\u0631\u0627\u0646 PDF \u062A\u0644\u0642\u0627\u0626\u064A\u064B\u0627 imageToPDF.selectText.3=\u0627\u0644\u0645\u0646\u0637\u0642 \u0627\u0644\u0645\u062A\u0639\u062F\u062F \u0644\u0644\u0645\u0644\u0641\u0627\u062A (\u0645\u0641\u0639\u0651\u0644 \u0641\u0642\u0637 \u0625\u0630\u0627 \u0643\u0646\u062A \u062A\u0639\u0645\u0644 \u0645\u0639 \u0635\u0648\u0631 \u0645\u062A\u0639\u062F\u062F\u0629) imageToPDF.selectText.4=\u062F\u0645\u062C \u0641\u064A \u0645\u0644\u0641 PDF \u0648\u0627\u062D\u062F @@ -626,17 +725,11 @@ watermark.selectText.4=دوران (0-360): watermark.selectText.5=widthSpacer (مسافة بين كل علامة مائية أفقيًا): watermark.selectText.6=heightSpacer (مسافة بين كل علامة مائية عموديًا): watermark.selectText.7=\u0627\u0644\u062A\u0639\u062A\u064A\u0645 (0\u066A - 100\u066A): +watermark.selectText.8=Watermark Type: +watermark.selectText.9=Watermark Image: watermark.submit=إضافة علامة مائية -#remove-watermark -remove-watermark.title=\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629 -remove-watermark.header=\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629 -remove-watermark.selectText.1=\u062D\u062F\u062F PDF \u0644\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629 \u0645\u0646: -remove-watermark.selectText.2=\u0646\u0635 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629: -remove-watermark.submit=\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0639\u0644\u0627\u0645\u0629 \u0627\u0644\u0645\u0627\u0626\u064A\u0629 - - #Change permissions permissions.title=تغيير الأذونات permissions.header=تغيير الأذونات @@ -682,13 +775,6 @@ changeMetadata.selectText.5=\u0625\u0636\u0627\u0641\u0629 \u0625\u062F\u062E\u0 changeMetadata.submit=\u062A\u063A\u064A\u064A\u0631 -#xlsToPdf -xlsToPdf.title=\u062A\u062D\u0648\u064A\u0644 Excel \u0625\u0644\u0649 PDF -xlsToPdf.header=\u062A\u062D\u0648\u064A\u0644 Excel \u0625\u0644\u0649 PDF -xlsToPdf.selectText.1=\u062D\u062F\u062F \u0648\u0631\u0642\u0629 \u0625\u0643\u0633\u0644 XLS \u0623\u0648 XLSX \u0644\u0644\u062A\u062D\u0648\u064A\u0644 -xlsToPdf.convert=\u062A\u062D\u0648\u064A\u0644 - - #pdfToPDFA pdfToPDFA.title=PDF \u0625\u0644\u0649 PDF / A pdfToPDFA.header=PDF \u0625\u0644\u0649 PDF / A diff --git a/src/main/resources/messages_bg_BG.properties b/src/main/resources/messages_bg_BG.properties new file mode 100644 index 00000000..90db3fca --- /dev/null +++ b/src/main/resources/messages_bg_BG.properties @@ -0,0 +1,817 @@ +########### +# Generic # +########### +# the direction that the language is written (ltr = left to right, rtl = right to left) +language.direction=ltr + +pdfPrompt=Изберете PDF(и) +multiPdfPrompt=Изберете PDF (2+) +multiPdfDropPrompt=Изберете (или плъзнете и пуснете) всички PDF файлове, от които се нуждаете +imgPrompt=Изберете изображение(я) +genericSubmit=Подайте +processTimeWarning=Предупреждение: Този процес може да отнеме до минута в зависимост от размера на файла +pageOrderPrompt=Персонализиран ред на страниците (Въведете разделен със запетаи списък с номера на страници или функции като 2n+1): +goToPage=Давай +true=Вярно +false=Невярно +unknown=Непознат +save=Съхранете +close=Затворете +filesSelected=избрани файлове +noFavourites=Няма добавени любими +bored=Отекчени сте да чакате? +alphabet=Азбука +downloadPdf=Изтеглете PDF +text=Текст +font=Шрифт +selectFillter=-- Изберете -- +pageNum=Брой страница +sizes.small=Малък +sizes.medium=Среден +sizes.large=Голям +sizes.x-large=X-Голям +error.pdfPassword=PDF документът е с парола и или паролата не е предоставена, или е неправилна +delete=Изтрий +username=Потребителско име +password=Парола +welcome=Добре дошли +property=Свойство +black=Черно +white=Бяло +red=Червено +green=Зелено +blue=Синьо +custom=Персонализиране... + +changedCredsMessage=Идентификационните данни са променени! +notAuthenticatedMessage=Потребителят не е автентикиран. +userNotFoundMessage=Потребителят не е намерен +incorrectPasswordMessage=Текущата парола е неправилна. +usernameExistsMessage=Новият потребител вече съществува. + + + +############# +# NAVBAR # +############# +navbar.convert=Преобразуване +navbar.security=Сигурност +navbar.other=Разни +navbar.darkmode=Тъмна тема +navbar.pageOps=Операции със страници +navbar.settings=Настройки + +############# +# SETTINGS # +############# +settings.title=Настройки +settings.update=Налична актуализация +settings.appVersion=Версия на приложението: +settings.downloadOption.title=Изберете опция за изтегляне (за изтегляния на един файл без да е архивиран): +settings.downloadOption.1=Отваряне в същия прозорец +settings.downloadOption.2=Отваряне в нов прозорец +settings.downloadOption.3=Изтегли файл +settings.zipThreshold=Архивирайте файловете, когато броят на изтеглените файлове надвишава +settings.signOut=Изход +settings.accountSettings=Настройки на акаунта + + + +changeCreds.title=Промяна на идентификационните данни +changeCreds.header=Актуализирайте данните за акаунта си +changeCreds.changeUserAndPassword=Използвате идентификационни данни за вход по подразбиране. Моля, въведете нова парола (и потребителско име, ако искате) +changeCreds.newUsername=Ново потребителско име +changeCreds.oldPassword=Текуща парола +changeCreds.newPassword=Нова парола +changeCreds.confirmNewPassword=Подтвърдете новата парола +changeCreds.submit=Изпращане на промените + + + +account.title=Настройки на акаунта +account.accountSettings=Настройки на акаунта +account.adminSettings=Настройки на администратора - Преглед и добавяне на потребители +account.userControlSettings=Настройки за потребителски контрол +account.changeUsername=Нов потребител +account.changeUsername=Промени потребител +account.password=Парола за потвърждение +account.oldPassword=Стара парола +account.newPassword=Нова парола +account.changePassword=Промени паролата +account.confirmNewPassword=Потвърдете новата парола +account.signOut=Изход +account.yourApiKey=Вашият API ключ +account.syncTitle=Синхронизиране на настройките на браузъра с акаунта +account.settingsCompare=Сравняване на настройките: +account.property=Свойство +account.webBrowserSettings=Уеб-браузър настройки +account.syncToBrowser=Синхронизиране на акаунт -> Бразър +account.syncToAccount=Синхронизиране на акаунт <- Бразър + + +adminUserSettings.title=Настройки за потребителски контрол +adminUserSettings.header=Настройки за администраторски потребителски контрол +adminUserSettings.admin=Администратор +adminUserSettings.user=Потребител +adminUserSettings.addUser=Добавяне на нов потребител +adminUserSettings.roles=Роли +adminUserSettings.role=Роля +adminUserSettings.actions=Действия +adminUserSettings.apiUser=Ограничен API потребител +adminUserSettings.webOnlyUser=Само за уеб-потребител +adminUserSettings.forceChange = Принудете потребителя да промени потребителското име/парола при влизане +adminUserSettings.submit=Съхранете потребителя + +############# +# HOME-PAGE # +############# +home.desc=Вашето локално хоствано обслужване на едно място за всички ваши PDF нужди. + + +home.multiTool.title=PDF Мулти инструмент +home.multiTool.desc=Обединяване, завъртане, пренареждане и премахване на страници +multiTool.tags=Мултиинструмент,Мулти операции,UI,плъзгане с щракване,потребителска част,страна на клиента,интерактивен,неразрешим,преместване + +home.merge.title=Обединяване +home.merge.desc=Лесно обединете множество PDF файлове в един. +merge.tags=сливане,операции на страници,администраторска зона,от страна на сървъра + +home.split.title=Разделяне +home.split.desc=Разделяне на PDF файлове на множество документи +split.tags=Операции на страницата,разделяне,Множество страници,изрязване,сървърна страна + +home.rotate.title=Завъртане +home.rotate.desc=Лесно завъртете вашите PDF файлове. +rotate.tags=от страната на сървъра + + +home.imageToPdf.title=Изображение към PDF +home.imageToPdf.desc=Преобразуване на изображение (PNG, JPEG, GIF) към PDF. +imageToPdf.tags=преобразуване,img,jpg,изображение,снимка + +home.pdfToImage.title=PDF към изображение +home.pdfToImage.desc=Преобразуване на PDF към изображение. (PNG, JPEG, GIF) +pdfToImage.tags=преобразуване,img,jpg,изображение,снимка + +home.pdfOrganiser.title=Организиране +home.pdfOrganiser.desc=Премахване/пренареждане на страници към произволен ред +pdfOrganiser.tags=дуплекс,четно,нечетно,сортиране,преместване + + +home.addImage.title=Добавяне на изображение +home.addImage.desc=Добавя изображение към зададено място към PDF файла +addImage.tags=img,jpg,изображение,снимка + +home.watermark.title=Добавяне на воден знак +home.watermark.desc=Добавете персонализиран воден знак към вашия PDF документ. +watermark.tags=Текст,повтарящ се,етикет,собствено,авторско право,търговска марка,img,jpg,изображение,снимка + +home.permissions.title=Промяна на правата +home.permissions.desc=Променете правата на вашия PDF документ +permissions.tags=четене,писане,редактиране,печат + + +home.removePages.title=Премахване +home.removePages.desc=Изтрийте нежеланите страници от вашия PDF документ. +removePages.tags=Премахване на страници,изтриване на страници + +home.addPassword.title=Добавете парола +home.addPassword.desc=Шифровайте вашия PDF документ с парола. +addPassword.tags=сигурен,сигурност + +home.removePassword.title=Премахване на парола +home.removePassword.desc=Премахнете защитата с парола от вашия PDF документ. +removePassword.tags=сигурно,декриптиране,сигурност,отмяна на парола,изтриване на парола + +home.compressPdfs.title=Компресиране +home.compressPdfs.desc=Компресирайте PDF файлове, за да намалите размера на файла. +compressPdfs.tags=мачкам,малък,мъничък + + +home.changeMetadata.title=Промяна на метаданни +home.changeMetadata.desc=Промяна/Премахване/Добавяне на метаданни от PDF документ +changeMetadata.tags=Заглавие,автор,дата,създаване,час,издател,продуцент,статистика + +home.fileToPDF.title=Преобразуване на файл към PDF +home.fileToPDF.desc=Преобразуване почти всеки файл към PDF (DOCX, PNG, XLS, PPT, TXT и други) +fileToPDF.tags=трансформация,формат,документ,изображение,слайд,текст,преобразуване,офис,документи,word,excel,powerpoint + +home.ocr.title=OCR / Почистващи сканирания +home.ocr.desc=Cleanup сканира и открива текст от изображения към PDF и го добавя отново като текст. +ocr.tags=разпознаване,текст,изображение,сканиране,четене,идентифициране,откриване,редактиране + + +home.extractImages.title=Извличане на изображения +home.extractImages.desc=Извлича всички изображения от PDF и ги записва към архив +extractImages.tags=изображение,снимка,запазване,архивиране,архив,заснемане,грабване + +home.pdfToPDFA.title=PDF към PDF/A +home.pdfToPDFA.desc=Конвертирайте PDF към PDF/A за дългосрочно съхранение +pdfToPDFA.tags=архив,дълготраен,стандартен,преобразуване,съхранение,консервиране + +home.PDFToWord.title=PDF към Word +home.PDFToWord.desc=Преобразуване на PDF към Word формати (DOC, DOCX и ODT) +PDFToWord.tags=doc,docx,odt,word,трансформация,формат,преобразуване,офис,microsoft,docfile + +home.PDFToPresentation.title=PDF към презентация +home.PDFToPresentation.desc=Преобразуване на PDF във формати за презентация (PPT, PPTX и ODP) +PDFToPresentation.tags=слайдове,покажи,офис,microsoft + +home.PDFToText.title=PDF към RTF (Текст) +home.PDFToText.desc=Преобразуване PDF към Text или RTF формат +PDFToText.tags=richformat,richtextformat,богат текстов формат + +home.PDFToHTML.title=PDF към HTML +home.PDFToHTML.desc=Преобразуване PDF към HTML формат +PDFToHTML.tags=уеб-съдържание,удобен за браузър + + +home.PDFToXML.title=PDF към XML +home.PDFToXML.desc=Преобразуване на PDF към XML формат +PDFToXML.tags=извличане на данни,структурирано съдържание,взаимодействие,трансформация,преобразуване + +home.ScannerImageSplit.title=Откриване/Разделяне на сканирани снимки +home.ScannerImageSplit.desc=Разделя множество снимки от една снимка/PDF +ScannerImageSplit.tags=разделяне,автоматично откриване,сканиране,много снимки,организиране + +home.sign.title=Подпишете +home.sign.desc=Добавя подпис към PDF чрез рисунка, текст или изображение +sign.tags=упълномощаване,инициали,нарисуван-подпис,текстов-знак,изображение-подпис + +home.flatten.title=Изравняване +home.flatten.desc=Премахнете всички интерактивни елементи и формуляри от PDF +flatten.tags=статичен,деактивиран,неинтерактивен,рационализиран + +home.repair.title=Поправи +home.repair.desc=Опитва се да поправи повреден/счупен PDF +repair.tags=поправка,възстановяване,корекция,възстановяване + +home.removeBlanks.title=Премахване на празни страници +home.removeBlanks.desc=Открива и премахва празни страници от документ +removeBlanks.tags=почистване,рационализиране,без съдържание,организиране + +home.compare.title=Сравнете +home.compare.desc=Сравнява и показва разликите между 2 PDF документа +compare.tags=разграничаване,контраст,промени,анализ + +home.certSign.title=Подпишете със сертификат +home.certSign.desc=Подписва PDF със сертификат/ключ (PEM/P12) +certSign.tags=удостоверяване,PEM,P12,официален,шифроване + +home.pageLayout.title=Оформление с няколко страници +home.pageLayout.desc=Слейте няколко страници от PDF документ в една страница +pageLayout.tags=сливане,комбиниран,единичен изглед,организиране + +home.scalePages.title=Коригирайте размера/мащаба на страницата +home.scalePages.desc=Промяна на размера/мащаба на страница и/или нейното съдържание. +scalePages.tags=преоразмеряване,промяна,размер,адаптиране + +home.pipeline.title=Pipeline (Разширено) +home.pipeline.desc=Изпълнявайте множество действия върху PDF файлове чрез дефиниране на конвейерни скриптове +pipeline.tags=автоматизиране,последователност,чрез скриптове,пакетен процес + +home.add-page-numbers.title=Добавяне на номера на страници +home.add-page-numbers.desc=Добавете номера на страници в документ на определено място +add-page-numbers.tags=страничен, етикетиране, организиране, индексиране + +home.auto-rename.title=Автоматично преименуване на PDF файл +home.auto-rename.desc=Автоматично преименува PDF файл въз основа на откритата му заглавка +auto-rename.tags=автоматично откриване,базирано на заглавка,организиране,преетикетиране + +home.adjust-contrast.title=Коригиране на цветове/контраст +home.adjust-contrast.desc=Коригиране на контраста, наситеността и яркостта на PDF +adjust-contrast.tags=корекция на цвета,настройте,модифицирайте,подобрете + +home.crop.title=Изрязване на PDF +home.crop.desc=Изрежете PDF, за да намалите размера му (поддържа текст!) +crop.tags=изрязване,свиване,редактиране,оформяне + +home.autoSplitPDF.title=Автоматично разделяне на страници +home.autoSplitPDF.desc=Автоматично разделяне на сканиран PDF файл с QR код за разделяне на физически сканирани страници +autoSplitPDF.tags=QR-базиран,отделен,сканиране-сегмент,организиране + +home.sanitizePdf.title=Дезинфекцирай +home.sanitizePdf.desc=Премахване на скриптове и други елементи от PDF файлове +sanitizePdf.tags=чисти,сигурни,безопасни,премахване-заплахи + +home.URLToPDF.title=URL/уеб-сайт към PDF +home.URLToPDF.desc=Преобразува всеки http(s) URL към PDF +URLToPDF.tags=уеб-заснемане,запазване на страница,уеб към документ,архив + +home.HTMLToPDF.title=HTML към PDF +home.HTMLToPDF.desc=Преобразува всеки HTML файл или архив към PDF +HTMLToPDF.tags=маркиране,уеб-съдържание,трансформация,преобразуване + + +home.MarkdownToPDF.title=Markdown към PDF +home.MarkdownToPDF.desc=Преобразува всеки Markdown файл към PDF +MarkdownToPDF.tags=маркиране,уеб-съдържание,трансформация,преобразуване + + +home.getPdfInfo.title=Вземете ЦЯЛАТА информация към PDF +home.getPdfInfo.desc=Взема всяка възможна информация от PDF файлове +getPdfInfo.tags=информация,данни,статистики,статистика + + +home.extractPage.title=Извличане на страница(и) +home.extractPage.desc=Извлича избрани страници от PDF +extractPage.tags=извличане + + +home.PdfToSinglePage.title=PDF към една голяма страница +home.PdfToSinglePage.desc=Обединява всички PDF страници в една голяма страница +PdfToSinglePage.tags=единична страница + + +home.showJS.title=Показване на Javascript +home.showJS.desc=Търси и показва всеки JS, инжектиран в PDF +showJS.tags=JS + +home.autoRedact.title=Автоматично редактиране +home.autoRedact.desc=Автоматично редактира (зачернява) текст в PDF въз основа на въведен текст +showJS.tags=Редактиране,Скриване,затъмняване,черен,маркер,скрит + +########################### +# # +# WEB PAGES # +# # +########################### +#login +login.title=Вход +login.signin=Впишете се +login.rememberme=Запомни ме +login.invalid=Невалидно потребителско име или парола. +login.locked=Вашият акаунт е заключен. +login.signinTitle=Моля впишете се + + +#auto-redact +autoRedact.title=Автоматично редактиране +autoRedact.header=Автоматично редактиране +autoRedact.colorLabel=Цвят +autoRedact.textsToRedactLabel=Текст за редактиране (разделен с редове) +autoRedact.textsToRedactPlaceholder=например: \nПоверително \nСтрого секретно +autoRedact.useRegexLabel=Използване на Regex +autoRedact.wholeWordSearchLabel=Търсене на цялата дума +autoRedact.customPaddingLabel=Персонализирана допълнителна подложка +autoRedact.convertPDFToImageLabel=Преобразуване на PDF към PDF-изображение (използва се за премахване на текст зад полето) +autoRedact.submitButton=Изпращане + + +#showJS +showJS.title=Покажи Javascript +showJS.header=Покажи Javascript +showJS.downloadJS=Изтегли Javascript +showJS.submit=Покажи + + +#pdfToSinglePage +pdfToSinglePage.title=PDF към единична страница +pdfToSinglePage.header=PDF към единична страница +pdfToSinglePage.submit=Преобразуване към единична страница + + +#pageExtracter +pageExtrater.title=Извличане на страници +pageExtrater.header=Извличане на страници +pageExtrater.submit=Извличане + + +#getPdfInfo +getPdfInfo.title=Вземете информация за PDF +getPdfInfo.header=Вземете информация за PDF +getPdfInfo.submit=Вземете информация +getPdfInfo.downloadJson=Изтеглете JSON + + +#markdown-to-pdf +MarkdownToPDF.title=Markdown към PDF +MarkdownToPDF.header=Markdown към PDF +MarkdownToPDF.submit=Преобразуване +MarkdownToPDF.help=Работата е в ход +MarkdownToPDF.credit=Използва WeasyPrint + + + +#url-to-pdf +URLToPDF.title=URL към PDF +URLToPDF.header=URL към PDF +URLToPDF.submit=Преобразуване +URLToPDF.credit=Използва WeasyPrint + + +#html-to-pdf +HTMLToPDF.title=HTML към PDF +HTMLToPDF.header=HTML към PDF +HTMLToPDF.help=Приема HTML файлове и ZIP файлове, съдържащи html/css/изображения и т.н +HTMLToPDF.submit=Преобразуване +HTMLToPDF.credit=Използва WeasyPrint + + +#sanitizePDF +sanitizePDF.title=Дезинфектирай PDF +sanitizePDF.header=Дезинфектира PDF файл +sanitizePDF.selectText.1=Премахва JavaScript действия +sanitizePDF.selectText.2=Премахва вградени файлове +sanitizePDF.selectText.3=Премахва метаданни +sanitizePDF.selectText.4=Премахва линкове +sanitizePDF.selectText.5=Премахва шрифтове +sanitizePDF.submit=Дезинфектирай PDF + + +#addPageNumbers +addPageNumbers.title=Добавяне на номера на страници +addPageNumbers.header=Добавяне на номера на страници +addPageNumbers.selectText.1=Изберете PDF файл: +addPageNumbers.selectText.2=Размер на полето +addPageNumbers.selectText.3=Позиция +addPageNumbers.selectText.4=Начален номер +addPageNumbers.selectText.5=Страници към номер +addPageNumbers.selectText.6=Персонализиран текст +addPageNumbers.customTextDesc=Персонализиран текст +addPageNumbers.numberPagesDesc=Кои страници да номерирате, по подразбиране 'всички', също приема 1-5 или 2,5,9 и т.н. +addPageNumbers.customNumberDesc=По подразбиране е {n}, също приема 'Страница {n} от {total}', 'Текст-{n}', '{filename}-{n} +addPageNumbers.submit=Добавяне на номера на страници + + +#auto-rename +auto-rename.title=Автоматично преименуване +auto-rename.header=Автоматично преименуване на PDF +auto-rename.submit=Автоматично преименуване + + +#adjustContrast +adjustContrast.title=Настройка на контраста +adjustContrast.header=Коригиране на контраста +adjustContrast.contrast=Контраст: +adjustContrast.brightness=Яркост: +adjustContrast.saturation=Наситеност: +adjustContrast.download=Изтегли + + +#crop +crop.title=Изрязване +crop.header=Изрязване на изображение +crop.submit=Подайте + + +#autoSplitPDF +autoSplitPDF.title=Автоматично разделяне на PDF +autoSplitPDF.header=Автоматично разделяне на PDF +autoSplitPDF.description=Печатайте, вмъквайте, сканирайте, качвайте и ни позволете да разделим автоматично вашите документи. Не е необходимо ръчно сортиране. +autoSplitPDF.selectText.1=Отпечатайте някои разделителни листове отдолу (Черно-бялото е добре). +autoSplitPDF.selectText.2=Сканирайте всичките си документи наведнъж, като поставите разделителния лист между тях. +autoSplitPDF.selectText.3=Качете единствения голям сканиран PDF файл и оставете Stirling PDF да се справи с останалото. +autoSplitPDF.selectText.4=Разделителните страници се откриват и премахват автоматично, което гарантира чист краен документ. +autoSplitPDF.formPrompt=Изпратете PDF, съдържащ разделители на страници на Stirling-PDF: +autoSplitPDF.duplexMode=Дуплексен режим (сканиране отпред и отзад) +autoSplitPDF.dividerDownload1=Изтеглете 'Автоматичен сплитер разделител (минимален).pdf' +autoSplitPDF.dividerDownload2=Изтеглете 'Автоматичен сплитер разделител (с инструкции).pdf' +autoSplitPDF.submit=Подайте + + +#pipeline +pipeline.title=Pipeline + + +#pageLayout +pageLayout.title=Многостранично оформление +pageLayout.header=Оформление на няколко страници +pageLayout.pagesPerSheet=Страници на лист: +pageLayout.addBorder=Добавяне на граници +pageLayout.submit=Подайте + + +#scalePages +scalePages.title=Коригиране на мащаба на страницата +scalePages.header=Коригиране на мащаба на страницата +scalePages.pageSize=Размер на страница от документа. +scalePages.scaleFactor=Ниво на мащабиране (изрязване) на страница. +scalePages.submit=Подайте + + +#certSign +certSign.title=Подписване на сертификат +certSign.header=Подпишете PDF с вашия сертификат (В процес на работа) +certSign.selectPDF=Изберете PDF файл за подписване: +certSign.selectKey=Изберете вашия файл с личен ключ (формат PKCS#8, може да бъде .pem или .der): +certSign.selectCert=Изберете вашия файл със сертификат (формат X.509, може да бъде .pem или .der): +certSign.selectP12=Изберете вашия PKCS#12 Keystore файл (.p12 или .pfx) (По избор, ако е предоставен, трябва да съдържа вашия личен ключ и сертификат): +certSign.certType=Тип сертификат +certSign.password=Въведете вашата парола за Keystore за ключове или частен ключ (ако има): +certSign.showSig=Показване на подпис +certSign.reason=Причина +certSign.location=Местоположение +certSign.name=Име +certSign.submit=Подпишете PDF + + +#removeBlanks +removeBlanks.title=Премахване на празни места +removeBlanks.header=Премахване на празни страници +removeBlanks.threshold=Праг на белота на пикселите: +removeBlanks.thresholdDesc=Праг за определяне колко бял трябва да бъде един бял пиксел, за да бъде класифициран като 'бял'. 0 = черно, 255 чисто бяло. +removeBlanks.whitePercent=Процент бяло (%): +removeBlanks.whitePercentDesc=Процент от страницата, която трябва да бъде в 'бели' пиксели, които да бъдат премахнати +removeBlanks.submit=Премахване на празни места + + +#compare +compare.title=Сравнявай +compare.header=Сравнявай PDF-и +compare.document.1=Документ 1 +compare.document.2=Документ 2 +compare.submit=Сравнявай + + +#sign +sign.title=Подпишете +sign.header=Подпишете PDF-и +sign.upload=Качи изображение +sign.draw=Начертайте подпис +sign.text=Въвеждане на текст +sign.clear=Изчисти +sign.add=Добави + + +#repair +repair.title=Поправи +repair.header=Поправи PDF-и +repair.submit=Поправи + + +#flatten +flatten.title=Изравнете +flatten.header=Изравнете PDF-и +flatten.submit=Изравнете + + +#ScannerImageSplit +ScannerImageSplit.selectText.1=Праг на ъгъла: +ScannerImageSplit.selectText.2=Задава минималния абсолютен ъгъл, необходим за завъртане на изображението (по подразбиране: 10). +ScannerImageSplit.selectText.3=Толеранс: +ScannerImageSplit.selectText.4=Определя обхвата на цветовата вариация около предполагаемия фонов цвят (по подразбиране: 30). +ScannerImageSplit.selectText.5=Минимална площ: +ScannerImageSplit.selectText.6=Задава минималния праг на площ за изображение (по подразбиране: 10000). +ScannerImageSplit.selectText.7=Минимална контурна площ: +ScannerImageSplit.selectText.8=Задава минималния праг на контурната площ за изображение +ScannerImageSplit.selectText.9=Размер на рамката: +ScannerImageSplit.selectText.10=Задава размера на добавената и премахната граница, за да предотврати бели граници към изхода (по подразбиране: 1). + + +#OCR +ocr.title=OCR / Почистване на сканиране +ocr.header=Почистващи сканирания / OCR (оптично разпознаване на знаци) +ocr.selectText.1=Изберете езици, които да бъдат открити в рамките на PDF (изброените са откритите към момента): +ocr.selectText.2=Създаване на текстов файл, съдържащ OCR текст заедно с OCR PDF +ocr.selectText.3=Правилните страници бяха сканирани под изкривен ъгъл чрез завъртането им обратно на мястото им +ocr.selectText.4=Чиста страница, така че е по-малко вероятно OCR да намери текст във фонов шум. (Без промяна на изхода) +ocr.selectText.5=Чиста страница, така че е по-малко вероятно OCR да намери текст във фонов шум, поддържа почистване към изхода. +ocr.selectText.6=Игнорира страници, които имат интерактивен текст, само OCR страници, които са изображения +ocr.selectText.7=Принудително OCR, ще премахва чрез OCR на всяка страница всички оригинални текстови елементи +ocr.selectText.8=Нормално (Ще има грешка, ако PDF съдържа текст) +ocr.selectText.9=Допълнителни настройки +ocr.selectText.10=OCR режим +ocr.selectText.11=Премахване на изображения след OCR (Премахва ВСИЧКИ изображения, полезно само ако е част от стъпката на преобразуване) +ocr.selectText.12=Тип изобразяване (Разширен) +ocr.help=Моля, прочетете тази документация за това как да използвате това за други езици и/или да не използвате в docker +ocr.credit=Тази услуга използва OCRmyPDF и Tesseract за OCR. +ocr.submit=Обработка на PDF чрез OCR + + +#extractImages +extractImages.title=Извличане на изображения +extractImages.header=Извличане на изображения +extractImages.selectText=Изберете формат на изображението, в който да преобразувате извлечените изображения +extractImages.submit=Извличане + + +#File to PDF +fileToPDF.title=Файл към PDF +fileToPDF.header=Конвертирайте всеки файл към PDF +fileToPDF.credit=Тази услуга използва LibreOffice и Unoconv за преобразуване на файлове. +fileToPDF.supportedFileTypes=Поддържаните типове файлове трябва да включват по-долу, но за пълен актуализиран списък на поддържаните формати, моля, вижте документацията на LibreOffice +fileToPDF.submit=Преобразуване към PDF + + +#compress +compress.title=Компресиране +compress.header=Компресиране на PDF +compress.credit=Тази услуга използва Ghostscript за PDF компресиране/оптимизиране. +compress.selectText.1=Ръчен режим - От 1 до 4 +compress.selectText.2=Ниво на оптимизация: +compress.selectText.3=4 (Ужасно за текстови изображения) +compress.selectText.4=Автоматичен режим - Автоматично настройва качеството, за да получи PDF точен размер +compress.selectText.5=Очакван PDF размер (напр. 25MB, 10.8MB, 25KB) +compress.submit=Компресиране + + +#Add image +addImage.title=Добавяне на изображение +addImage.header=Добавяне на изображение към PDF +addImage.everyPage=Всяка страница? +addImage.upload=Добавяне на изображение +addImage.submit=Добавяне на изображение + + +#merge +merge.title=Обединяване +merge.header=Обединяване на множество PDF файлове (2+) +merge.sortByName=Сортиране по име +merge.sortByDate=Сортиране по дата +merge.submit=Обединяване + + +#pdfOrganiser +pdfOrganiser.title=Организатор на страници +pdfOrganiser.header=Организатор на PDF страници +pdfOrganiser.submit=Пренареждане на страниците + + +#multiTool +multiTool.title=PDF Мулти инструмент +multiTool.header=PDF Мулти инструмент + + +#pageRemover +pageRemover.title=Премахване на страници +pageRemover.header=Премахване на PDF страници +pageRemover.pagesToDelete=Страници за изтриване (Въведете списък с номера на страници, разделени със запетая) : +pageRemover.submit=Изтриване на страници + + +#rotate +rotate.title=Завъртане на PDF +rotate.header=Завъртане на PDF +rotate.selectAngle=Изберете ъгъл на въртене (кратно на 90 градуса): +rotate.submit=Завъртане + + +#merge +split.title=Разделяне на PDF +split.header=Разделяне на PDF +split.desc.1=Числата, които избирате, са номера на страницата, на която искате да направите разделяне +split.desc.2=Така че избирането на 1,3,7-8 ще раздели документ от 10 страници на 6 отделни PDF файла с: +split.desc.3=Документ #1: Страница 1 +split.desc.4=Документ #2: Страница 2 и 3 +split.desc.5=Документ #3: Страница 4, 5 и 6 +split.desc.6=Документ #4: Страница 7 +split.desc.7=Документ #5: Страница 8 +split.desc.8=Документ #6: Страница 9 и 10 +split.splitPages=Въведете страници за разделяне: +split.submit=Разделяне + + +#merge +imageToPDF.title=Изображение към PDF +imageToPDF.header=Изображение към PDF +imageToPDF.submit=Преобразуване +imageToPDF.selectLabel=Опции за прилягане на изображението +imageToPDF.fillPage=Попълване на страница +imageToPDF.fitDocumentToImage=Побиране на страницата в изображението +imageToPDF.maintainAspectRatio=Поддържане на пропорции +imageToPDF.selectText.2=Автоматично завъртане на PDF +imageToPDF.selectText.3=Файлова логика с много (Активирано само ако работите с множество изображения) +imageToPDF.selectText.4=Сливане към един PDF +imageToPDF.selectText.5=Преобразуване към отделни PDF файлове + + +#pdfToImage +pdfToImage.title=PDF към Изображение +pdfToImage.header=PDF към Изображение +pdfToImage.selectText=Формат на изображението +pdfToImage.singleOrMultiple=Тип резултат от страница към изображение +pdfToImage.single=Единично голямо изображение комбиниране на всички страници +pdfToImage.multi=Множество изображения, по едно изображение на страница +pdfToImage.colorType=Тип цвят +pdfToImage.color=Цвят +pdfToImage.grey=Скала на сивото +pdfToImage.blackwhite=Черно и бяло (може да загубите данни!) +pdfToImage.submit=Преобразуване + + +#addPassword +addPassword.title=Добавяне на парола +addPassword.header=Добавяне на парола (Шифроване) +addPassword.selectText.1=Изберете PDF, който да шифровате +addPassword.selectText.2=Потребителска парола +addPassword.selectText.3=Предотвратяване на сглобяването на документ +addPassword.selectText.4=По-високите стойности са по-силни, но по-ниските стойности имат по-добра съвместимост. +addPassword.selectText.5=Разрешения за задаване (препоръчва се да се използва заедно с паролата на собственика) +addPassword.selectText.6=Предотвратяване на сглобяването на документ +addPassword.selectText.7=Предотвратете извличането на съдържание +addPassword.selectText.8=Предотвратете извличането за достъпност +addPassword.selectText.9=Предотвратяване на попълване на формуляр +addPassword.selectText.10=Предотвратяване на промени +addPassword.selectText.11=Предотвратяване на промени на анотация +addPassword.selectText.12=Предотвратяване на печат +addPassword.selectText.13=Предотвратете отпечатването в различни формати +addPassword.selectText.14=Парола на собственика +addPassword.selectText.15=Ограничава какво може да се прави с документа, след като бъде отворен (не се поддържа от всички четци) +addPassword.selectText.16=Ограничава отварянето на самия документ +addPassword.submit=Шифроване + + +#watermark +watermark.title=Добавяне на воден знак +watermark.header=Добавяне на воден знак +watermark.selectText.1=Изберете PDF, към който да добавите воден знак: +watermark.selectText.2=Текст на воден знак: +watermark.selectText.3=Размер на шрифта: +watermark.selectText.4=Завъртане (0-360): +watermark.selectText.5=ширинаSpacer (Разстояние между всеки воден знак хоризонтално): +watermark.selectText.6=дължинаSpacer (Разстояние между всеки воден знак вертикално): +watermark.selectText.7=Непрозрачност (0% - 100%): +watermark.selectText.8=Тип воден знак: +watermark.selectText.9=Изображение за воден знак: +watermark.submit=Добавяне на воден знак + + +#Change permissions +permissions.title=Промяна на правата +permissions.header=Промени правата +permissions.warning=Предупреждение, че тези разрешения са непроменими, препоръчва се да ги зададете с парола чрез страницата за добавяне на парола +permissions.selectText.1=Изберете PDF, за да промените правата +permissions.selectText.2=Разрешения за задаване +permissions.selectText.3=Предотвратяване на сглобяването на документ +permissions.selectText.4=Предотвратете извличането на съдържание +permissions.selectText.5=Предотвратете извличането за достъпност +permissions.selectText.6=Предотвратяване на попълване на формуляр +permissions.selectText.7=Предотвратяване на модификация +permissions.selectText.8=Предотвратяване на модификация на анотация +permissions.selectText.9=Предотвратявам на отпечатването +permissions.selectText.10=Предотвратете отпечатването на различни формати +permissions.submit=Промени + + +#remove password +removePassword.title=Премахване на паролата +removePassword.header=Премахване на паролата (Декриптиране) +removePassword.selectText.1=Изберете PDF за Декриптиране +removePassword.selectText.2=Парола +removePassword.submit=Премахване + + +#changeMetadata +changeMetadata.title=Промени метаданните +changeMetadata.header=Промени метаданните +changeMetadata.selectText.1=Моля, редактирайте променливите, които искате да промените +changeMetadata.selectText.2=Изтрий всички метаданни +changeMetadata.selectText.3=Покажи персонализирани метаданни: +changeMetadata.author=Автор: +changeMetadata.creationDate=Дата на създаване (гггг/ММ/дд ЧЧ:мм:сс): +changeMetadata.creator=Създател: +changeMetadata.keywords=Ключови думи: +changeMetadata.modDate=Дата на промяна (гггг/ММ/дд ЧЧ:мм:сс): +changeMetadata.producer=Продуцент: +changeMetadata.subject=Тема: +changeMetadata.title=Заглавие: +changeMetadata.trapped=В капан: +changeMetadata.selectText.4=Други метаданни: +changeMetadata.selectText.5=Добавяне на персонализиране метаданни +changeMetadata.submit=Промени + + +#pdfToPDFA +pdfToPDFA.title=PDF към PDF/A +pdfToPDFA.header=PDF към PDF/A +pdfToPDFA.credit=Тази услуга използва OCRmyPDF за PDF/A преобразуване. +pdfToPDFA.submit=Преобразуване + + +#PDFToWord +PDFToWord.title=PDF към Word +PDFToWord.header=PDF към Word +PDFToWord.selectText.1=Изходен файлов формат +PDFToWord.credit=Тази услуга използва LibreOffice за преобразуване на файлове. +PDFToWord.submit=Преобразуване + + +#PDFToPresentation +PDFToPresentation.title=PDF към Презентация +PDFToPresentation.header=PDF към Презентация +PDFToPresentation.selectText.1=Изходен файлов формат +PDFToPresentation.credit=Тази услуга използва LibreOffice за преобразуване на файлове. +PDFToPresentation.submit=Преобразуване + + +#PDFToText +PDFToText.title=PDF към RTF (Текст) +PDFToText.header=PDF към RTF (Текст) +PDFToText.selectText.1=Изходен файлов формат +PDFToText.credit=Тази услуга използва LibreOffice за преобразуване на файлове. +PDFToText.submit=Преобразуване + + +#PDFToHTML +PDFToHTML.title=PDF към HTML +PDFToHTML.header=PDF към HTML +PDFToHTML.credit=Тази услуга използва LibreOffice за преобразуване на файлове. +PDFToHTML.submit=Преобразуване + + +#PDFToXML +PDFToXML.title=PDF към XML +PDFToXML.header=PDF към XML +PDFToXML.credit=Тази услуга използва LibreOffice за преобразуване на файлове. +PDFToXML.submit=Преобразуване \ No newline at end of file diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index 6d653a9b..b6341fa0 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -26,11 +26,29 @@ text=Text font=Tipus de lletra selectFillter=-- Selecciona -- pageNum=Número de pàgina -sizes.small=Small -sizes.medium=Medium -sizes.large=Large +sizes.small=Petit +sizes.medium=Mitjà +sizes.large=Llarg sizes.x-large=X-Large -error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +error.pdfPassword=El PDF està protegit o bé el password és incorrecte +delete=Esborra +username=Usuari +password=Contrasenya +welcome=Benvingut +property=Propietat +black=Negre +white=Blanc +red=Vermell +green=Verd +blue=Blau +custom=Personalitzat... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -39,7 +57,7 @@ error.pdfPassword=The PDF Document is passworded and either the password was not navbar.convert=Converteix navbar.security=Seguretat navbar.other=Altres -navbar.darkmode=Mode Fost +navbar.darkmode=Mode Fosc navbar.pageOps=Operacions de Pàgina navbar.settings=Opcions @@ -54,6 +72,55 @@ settings.downloadOption.1=Obre mateixa finestra settings.downloadOption.2=Obre mateixa finestra settings.downloadOption.3=Descarrega Arxiu settings.zipThreshold=Comprimiu els fitxers quan el nombre de fitxers baixats superi +settings.signOut=Sortir +settings.accountSettings=Account Settings + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Opcions del compte +account.accountSettings=Opcions del compte +account.adminSettings=Opcions d'Admin - Veure i afegir usuaris +account.userControlSettings=Opcions de Control d'Usuari +account.changeUsername=Canvia nom usuari +account.changeUsername=Canvia nom usuari +account.password=Confirma contrasenya +account.oldPassword=Password Antic +account.newPassword=Password Nou +account.changePassword=Canvia contrasenya +account.confirmNewPassword=Confirma Nova contrasenya +account.signOut=Sortir +account.yourApiKey=Clau API +account.syncTitle=Sincronitza opcions navegador amb compte +account.settingsCompare=Comparador Opcions: +account.property=Propietat: +account.webBrowserSettings=Opcins Navegador +account.syncToBrowser=Sincronitza Compte -> Navegador +account.syncToAccount=Sincronitza Compte <- Navegador + + +adminUserSettings.title=Opcions Control Usuari +adminUserSettings.header=Usuari Admin Opcions Control +adminUserSettings.admin=Admin +adminUserSettings.user=Usuari +adminUserSettings.addUser=Afegir Usuari +adminUserSettings.roles=Rols +adminUserSettings.role=Rol +adminUserSettings.actions=Accions +adminUserSettings.apiUser=Usuari amb API limitada +adminUserSettings.webOnlyUser=Usuari només WEB +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Desar Usuari ############# # HOME-PAGE # @@ -108,12 +175,12 @@ home.removePages.title=Elimina home.removePages.desc=Elimina pàgines del document PDF. removePages.tags=Remove pages,delete pages -home.addPassword.title=Afegir Password -home.addPassword.desc=Xifra document PDF amb password. +home.addPassword.title=Afegir Contrasenya +home.addPassword.desc=Xifra document PDF amb contrasenya. addPassword.tags=secure,security -home.removePassword.title=Elimina Password -home.removePassword.desc=Elimia Password de document PDF. +home.removePassword.title=Elimina Contrasenya +home.removePassword.desc=Elimia contrasenya de document PDF. removePassword.tags=secure,Decrypt,security,unpassword,delete password home.compressPdfs.title=Comprimeix @@ -167,7 +234,7 @@ home.ScannerImageSplit.title=Detecta/Divideix fotos escanejades home.ScannerImageSplit.desc=Divideix múltiples fotos dins del PDF/foto ScannerImageSplit.tags=separate,auto-detect,scans,multi-photo,organize -home.sign.title=Sign +home.sign.title=Signa home.sign.desc=Afegeix signatura al PDF mitjançant dibuix, text o imatge sign.tags=authorize,initials,drawn-signature,text-sign,image-signature @@ -187,9 +254,9 @@ home.compare.title=Compara home.compare.desc=Compara i mostra les diferències entre 2 documents PDF compare.tags=differentiate,contrast,changes,analysis -home.certSign.title=Sign with Certificate -home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) -certSign.tags=authenticate,PEM,P12,official,encrypt +home.certSign.title=Signa amb Certificat +home.certSign.desc=Sign PDF amb Certificate/Clau (PEM/P12) +certSign.tags=authentica,PEM,P12,official,encripta home.pageLayout.title=Multi-Page Layout home.pageLayout.desc=Merge multiple pages of a PDF document into a single page @@ -203,20 +270,20 @@ home.pipeline.title=Pipeline (Advanced) home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts pipeline.tags=automate,sequence,scripted,batch-process -home.add-page-numbers.title=Add Page Numbers -home.add-page-numbers.desc=Add Page numbers throughout a document in a set location -add-page-numbers.tags=paginate,label,organize,index +home.add-page-numbers.title=Afegir Números de Pàgina +home.add-page-numbers.desc=Afegir Números de Pàgina en una localització +add-page-numbers.tags=pagina,etiqueta,organitza,indexa home.auto-rename.title=Auto Rename PDF File home.auto-rename.desc=Auto renames a PDF file based on its detected header auto-rename.tags=auto-detect,header-based,organize,relabel -home.adjust-contrast.title=Adjust Colors/Contrast -home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF +home.adjust-contrast.title=Ajusta Colors/Contrast +home.adjust-contrast.desc=Ajusta Colors/Contrast, Saturació i Brillantor adjust-contrast.tags=color-correction,tune,modify,enhance -home.crop.title=Crop PDF -home.crop.desc=Crop a PDF to reduce its size (maintains text!) +home.crop.title=Talla PDF +home.crop.desc=Talla PDF per reduïr la mida (manté text!) crop.tags=trim,shrink,edit,shape home.autoSplitPDF.title=Auto Split Pages @@ -256,22 +323,42 @@ home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page -########################## -### TODO: Translate ### -########################## home.showJS.title=Show Javascript home.showJS.desc=Searches and displays any JS injected into a PDF showJS.tags=JS +home.autoRedact.title=Auto Redact +home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### +#login +login.title=Accedir +login.signin=Accedir +login.rememberme=Recordar +login.invalid=Nom usuari / password no vàlid +login.locked=Compte bloquejat +login.signinTitle=Autenticat + + +#auto-redact +autoRedact.title=Auto Redact +autoRedact.header=Auto Redact +autoRedact.colorLabel=Colour +autoRedact.textsToRedactLabel=Text to Redact (line-separated) +autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret +autoRedact.useRegexLabel=Use Regex +autoRedact.wholeWordSearchLabel=Whole Word Search +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) +autoRedact.submitButton=Submit + + #showJS -########################## -### TODO: Translate ### -########################## showJS.title=Show Javascript showJS.header=Show Javascript showJS.downloadJS=Download Javascript @@ -333,15 +420,18 @@ sanitizePDF.submit=Sanitize PDF #addPageNumbers -addPageNumbers.title=Add Page Numbers -addPageNumbers.header=Add Page Numbers -addPageNumbers.selectText.1=Select PDF file: -addPageNumbers.selectText.2=Margin Size -addPageNumbers.selectText.3=Position -addPageNumbers.selectText.4=Starting Number -addPageNumbers.selectText.5=Pages to Number -addPageNumbers.selectText.6=Custom Text -addPageNumbers.submit=Add Page Numbers +addPageNumbers.title=Afegir Números de Pàgina +addPageNumbers.header=Afegir Números de Pàgina +addPageNumbers.selectText.1=Selecciona PDF: +addPageNumbers.selectText.2=Mida Marge +addPageNumbers.selectText.3=Posició +addPageNumbers.selectText.4=Número Inicial +addPageNumbers.selectText.5=Pàgines a enumerar +addPageNumbers.selectText.6=Text Personalitzat +addPageNumbers.customTextDesc=Text Personalitzat +addPageNumbers.numberPagesDesc=Pàgines a enumerar, defecte 'totes', accepta 1-5 o 2,5,9 etc +addPageNumbers.customNumberDesc=Defecte a {n}, accepta 'Pàgina {n} de {total}', 'Text-{n}', '{filename}-{n} +addPageNumbers.submit=Afegir Números de Pàgina #auto-rename @@ -360,8 +450,8 @@ adjustContrast.download=Download #crop -crop.title=Crop -crop.header=Crop Image +crop.title=Talla +crop.header=Talla Imatge crop.submit=Submit @@ -388,6 +478,10 @@ pipeline.title=Pipeline pageLayout.title=Multi Page Layout pageLayout.header=Multi Page Layout pageLayout.pagesPerSheet=Pages per sheet: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders pageLayout.submit=Submit @@ -526,6 +620,8 @@ addImage.submit=Afegir Imatge #merge merge.title=Fusiona merge.header=Fusiona múltiples PDFs (2+) +merge.sortByName=Sort by name +merge.sortByDate=Sort by date merge.submit=Fusiona @@ -573,7 +669,10 @@ split.submit=Divideix imageToPDF.title=Imatge a PDF imageToPDF.header=Imatge a PDF imageToPDF.submit=Converteix -imageToPDF.selectText.1=Estirar per adaptar +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=Auto rota PDF imageToPDF.selectText.3=Lògica de diversos fitxers (només està activada si es treballa amb diverses imatges) imageToPDF.selectText.4=Combina en un únic PDF @@ -626,17 +725,11 @@ watermark.selectText.4=Rotació (0-360): watermark.selectText.5=separació d'amplada (Espai horitzontal entre cada Marca d'Aigua): watermark.selectText.6=separació d'alçada (Espai vertical entre cada Marca d'Aigua): watermark.selectText.7=Opacitat (0% - 100%): +watermark.selectText.8=Watermark Type: +watermark.selectText.9=Watermark Image: watermark.submit=Afegir Marca d'Aigua -#remove-watermark -remove-watermark.title=Elimina Marca d'Aigua -remove-watermark.header=Elimina Marca d'Aigua -remove-watermark.selectText.1=Seleciona PDF per eliminar Marca d'Aigua: -remove-watermark.selectText.2=Text de la Marca d'Aigua: -remove-watermark.submit=Elimina Marca d'Aigua - - #Change permissions permissions.title=Canviar Permissos permissions.header=Canviar Permissos @@ -682,13 +775,6 @@ changeMetadata.selectText.5=Afegir entrada personalizada changeMetadata.submit=Canvia -#xlsToPdf -xlsToPdf.title=Excel a PDF -xlsToPdf.header=Excel a PDF -xlsToPdf.selectText.1=Selecciona arxiu XLS o XLSX a convertir -xlsToPdf.convert=Converteix - - #pdfToPDFA pdfToPDFA.title=PDF a PDF/A pdfToPDFA.header=PDF a PDF/A diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index 1b5691bf..02f87df6 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -19,18 +19,36 @@ save=Speichern close=Schließen filesSelected=Dateien ausgewählt noFavourites=Keine Favoriten hinzugefügt -bored=Gelangweiltes Warten? +bored=Langeweile beim Warten? alphabet=Alphabet downloadPdf=PDF herunterladen text=Text font=Schriftart selectFillter=-- Auswählen -- pageNum=Seitenzahl -sizes.small=Small -sizes.medium=Medium -sizes.large=Large -sizes.x-large=X-Large -error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +sizes.small=Klein +sizes.medium=Mittel +sizes.large=Groß +sizes.x-large=Extra Groß +error.pdfPassword=Das PDF-Dokument ist passwortgeschützt und das Passwort wurde entweder nicht angegeben oder war falsch +delete=Löschen +username=Benutzername +password=Passwort +welcome=Willkommen +property=Eigenschaft +black=Schwarz +white=Weiß +red=Rot +green=Grün +blue=Blau +custom=benutzerdefiniert... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -54,6 +72,55 @@ settings.downloadOption.1=Im selben Fenster öffnen settings.downloadOption.2=In neuem Fenster öffnen settings.downloadOption.3=Datei herunterladen settings.zipThreshold=Dateien komprimieren, wenn die Anzahl der heruntergeladenen Dateien überschritten wird +settings.signOut=Abmelden +settings.accountSettings=Kontoeinstellungen + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Kontoeinstellungen +account.accountSettings=Kontoeinstellungen +account.adminSettings=Admin Einstellungen - Benutzer anzeigen und hinzufügen +account.userControlSettings=Benutzerkontrolle +account.changeUsername=Passwort ändern +account.changeUsername=Passwort ändern +account.password=Bestätigungspasswort +account.oldPassword=Altes Passwort +account.newPassword=Neues Passwort +account.changePassword=Password ändern +account.confirmNewPassword=Neues Passwort bestätigen +account.signOut=Abmelden +account.yourApiKey=Dein API Schlüssel +account.syncTitle=Browsereinstellungen mit Konto synchronisieren +account.settingsCompare=Einstellungen vergleichen: +account.property=Eigenschaft +account.webBrowserSettings=Webbrowser-Einstellung +account.syncToBrowser=Synchronisiere Konto -> Browser +account.syncToAccount=Synchronisiere Konto <- Browser + + +adminUserSettings.title=Benutzerkontrolle +adminUserSettings.header=Administrator-Benutzerkontrolle +adminUserSettings.admin=Admin +adminUserSettings.user=Benutzer +adminUserSettings.addUser=Neuen Benutzer hinzufügen +adminUserSettings.roles=Rollen +adminUserSettings.role=Rolle +adminUserSettings.actions=Aktion +adminUserSettings.apiUser=Eingeschränkter API-Benutzer +adminUserSettings.webOnlyUser=Nur Web-Benutzer +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Benutzer speichern ############# # HOME-PAGE # @@ -92,7 +159,7 @@ pdfOrganiser.tags=duplex,even,odd,sort,move home.addImage.title=Bild einfügen -home.addImage.desc=Fügt ein Bild an eine bestimmte Stelle im PDF ein (Work in progress). +home.addImage.desc=Fügt ein Bild an eine bestimmte Stelle im PDF ein (in Arbeit). addImage.tags=img,jpg,picture,photo home.watermark.title=Wasserzeichen hinzufügen @@ -129,13 +196,13 @@ home.fileToPDF.title=Datei in PDF konvertieren home.fileToPDF.desc=Konvertieren Sie nahezu jede Datei in PDF (DOCX, PNG, XLS, PPT, TXT und mehr) fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint -home.ocr.title=Führe OCR auf PDF- und/oder Cleanup-Scans aus +home.ocr.title=Führe OCR/Cleanup-Scans aus home.ocr.desc=Cleanup scannt und erkennt Text aus Bildern in einer PDF-Datei und fügt ihn erneut als Text hinzu. ocr.tags=recognition,text,image,scan,read,identify,detection,editable home.extractImages.title=Bilder extrahieren -home.extractImages.desc=Extrahiert alle Bilder aus einer PDF-Datei und speichert sie als Zip-Datei +home.extractImages.desc=Extrahiert alle Bilder aus einer PDF-Datei und speichert sie als Zip-Archiv extractImages.tags=picture,photo,save,archive,zip,capture,grab home.pdfToPDFA.title=PDF zu PDF/A konvertieren @@ -187,80 +254,81 @@ home.compare.title=Vergleichen home.compare.desc=Vergleicht und zeigt die Unterschiede zwischen zwei PDF-Dokumenten an compare.tags=differentiate,contrast,changes,analysis -home.certSign.title=Sign with Certificate -home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) +home.certSign.title=Mit Zertifikat signieren +home.certSign.desc=Ein PDF mit einem Zertifikat/Schlüssel (PEM/P12) signieren certSign.tags=authenticate,PEM,P12,official,encrypt -home.pageLayout.title=Multi-Page Layout -home.pageLayout.desc=Merge multiple pages of a PDF document into a single page +home.pageLayout.title=Mehrseitiges Layout +home.pageLayout.desc=Mehrere Seiten eines PDF zu einer Seite zusammenführen pageLayout.tags=merge,composite,single-view,organize -home.scalePages.title=Adjust page size/scale -home.scalePages.desc=Change the size/scale of page and/or its contents. +home.scalePages.title=Seitengröße/Skalierung anpassen +home.scalePages.desc=Größe/Skalierung der Seite und/oder des Inhalts ändern scalePages.tags=resize,modify,dimension,adapt -home.pipeline.title=Pipeline (Advanced) -home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts +home.pipeline.title=Pipeline (Fortgeschritten) +home.pipeline.desc=Mehrere Aktionen auf ein PDF anwenden, definiert durch einen Pipeline Skript pipeline.tags=automate,sequence,scripted,batch-process -home.add-page-numbers.title=Add Page Numbers -home.add-page-numbers.desc=Add Page numbers throughout a document in a set location +home.add-page-numbers.title=Seitenzahlen hinzufügen +home.add-page-numbers.desc=Hinzufügen von Seitenzahlen an einer bestimmten Stelle add-page-numbers.tags=paginate,label,organize,index -home.auto-rename.title=Auto Rename PDF File -home.auto-rename.desc=Auto renames a PDF file based on its detected header +home.auto-rename.title=PDF automatisch umbenennen +home.auto-rename.desc=PDF-Datei anhand von erkannten Kopfzeilen umbenennen auto-rename.tags=auto-detect,header-based,organize,relabel -home.adjust-contrast.title=Adjust Colors/Contrast -home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF +home.adjust-contrast.title=Farben/Kontrast anpassen +home.adjust-contrast.desc=Kontrast, Sättigung und Helligkeit einer PDF anpassen adjust-contrast.tags=color-correction,tune,modify,enhance -home.crop.title=Crop PDF -home.crop.desc=Crop a PDF to reduce its size (maintains text!) +home.crop.title=PDF zuschneiden +home.crop.desc=PDF zuschneiden um die Größe zu verändern (Text bleibt erhalten!) crop.tags=trim,shrink,edit,shape -home.autoSplitPDF.title=Auto Split Pages -home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code +home.autoSplitPDF.title=PDF automatisch teilen +home.autoSplitPDF.desc=Physisch gescannte PDF anhand von Splitter-Seiten und QR-Codes aufteilen autoSplitPDF.tags=QR-based,separate,scan-segment,organize -home.sanitizePdf.title=Sanitize -home.sanitizePdf.desc=Remove scripts and other elements from PDF files +home.sanitizePdf.title=PDF Bereinigen +home.sanitizePdf.desc=Entfernen von Skripten und anderen Elementen aus PDF-Dateien sanitizePdf.tags=clean,secure,safe,remove-threats -home.URLToPDF.title=URL/Website To PDF -home.URLToPDF.desc=Converts any http(s)URL to PDF +home.URLToPDF.title=URL/Website zu PDF +home.URLToPDF.desc=Konvertiert jede http(s)URL zu PDF URLToPDF.tags=web-capture,save-page,web-to-doc,archive -home.HTMLToPDF.title=HTML to PDF -home.HTMLToPDF.desc=Converts any HTML file or zip to PDF +home.HTMLToPDF.title=HTML zu PDF +home.HTMLToPDF.desc=Konvertiert jede HTML-Datei oder Zip-Archiv zu PDF HTMLToPDF.tags=markup,web-content,transformation,convert -home.MarkdownToPDF.title=Markdown to PDF -home.MarkdownToPDF.desc=Converts any Markdown file to PDF +home.MarkdownToPDF.title=Markdown zu PDF +home.MarkdownToPDF.desc=Konvertiert jede Markdown-Datei zu PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -home.getPdfInfo.title=Get ALL Info on PDF -home.getPdfInfo.desc=Grabs any and all information possible on PDFs +home.getPdfInfo.title=Alle Informationen anzeigen +home.getPdfInfo.desc=Erfasst alle möglichen Informationen in einer PDF getPdfInfo.tags=infomation,data,stats,statistics -home.extractPage.title=Extract page(s) -home.extractPage.desc=Extracts select pages from PDF +home.extractPage.title=Seite(n) extrahieren +home.extractPage.desc=Extrahiert ausgewählte Seiten aus einer PDF extractPage.tags=extract -home.PdfToSinglePage.title=PDF to Single Large Page -home.PdfToSinglePage.desc=Merges all PDF pages into one large single page +home.PdfToSinglePage.title=PDF zu einer Seite zusammenfassen +home.PdfToSinglePage.desc=Fügt alle PDF-Seiten zu einer einzigen großen Seite zusammen PdfToSinglePage.tags=single page -########################## -### TODO: Translate ### -########################## -home.showJS.title=Show Javascript -home.showJS.desc=Searches and displays any JS injected into a PDF +home.showJS.title=Javascript anzeigen +home.showJS.desc=Alle Javascript Funktionen in einer PDF anzeigen +showJS.tags=JS + +home.autoRedact.title=Automatisch zensieren/schwärzen +home.autoRedact.desc=Automatisches zensiertes (Schwärzen) von Text in einer PDF-Datei basierend auf dem eingegebenen Text showJS.tags=JS ########################### @@ -268,116 +336,138 @@ showJS.tags=JS # WEB PAGES # # # ########################### +#login +login.title=Anmelden +login.signin=Anmelden +login.rememberme=Angemeldet bleiben +login.invalid=Ungültiger Benutzername oder Passwort. +login.locked=Ihr Konto wurde gesperrt. +login.signinTitle=Bitte melden Sie sich an + + +#auto-redact +autoRedact.title=Automatisch zensieren/schwärzen +autoRedact.header=Automatisch zensieren/schwärzen +autoRedact.colorLabel=Farbe +autoRedact.textsToRedactLabel=Zu zensierender Text (einer pro Zeile) +autoRedact.textsToRedactPlaceholder=z.B. \nVertraulich \nStreng geheim +autoRedact.useRegexLabel=Regex verwenden +autoRedact.wholeWordSearchLabel=Ganzes Wort suchen +autoRedact.customPaddingLabel=Benutzerdefinierte Extra-Padding +autoRedact.convertPDFToImageLabel=PDF in PDF-Bild konvertieren (zum Entfernen von Text hinter dem Kasten) +autoRedact.submitButton=zensieren + + #showJS -########################## -### TODO: Translate ### -########################## -showJS.title=Show Javascript -showJS.header=Show Javascript -showJS.downloadJS=Download Javascript -showJS.submit=Show +showJS.title=Javascript anzeigen +showJS.header=Javascript anzeigen +showJS.downloadJS=Javascript herunterladen +showJS.submit=Anzeigen #pdfToSinglePage -pdfToSinglePage.title=PDF To Single Page -pdfToSinglePage.header=PDF To Single Page -pdfToSinglePage.submit=Convert To Single Page +pdfToSinglePage.title=PDF zu einer Seite zusammenfassen +pdfToSinglePage.header=PDF zu einer Seite zusammenfassen +pdfToSinglePage.submit=Zusammenfassen #pageExtracter -pageExtracter.title=Extract Pages -pageExtracter.header=Extract Pages -pageExtracter.submit=Extract +pageExtracter.title=Seiten extrahieren +pageExtracter.header=Seiten extrahieren +pageExtracter.submit=Extrahieren #getPdfInfo -getPdfInfo.title=Get Info on PDF -getPdfInfo.header=Get Info on PDF -getPdfInfo.submit=Get Info -getPdfInfo.downloadJson=Download JSON +getPdfInfo.title=Alle Informationen anzeigen +getPdfInfo.header=Alle Informationen anzeigen +getPdfInfo.submit=Informationen anzeigen +getPdfInfo.downloadJson=Als JSON herunterladen #markdown-to-pdf -MarkdownToPDF.title=Markdown To PDF -MarkdownToPDF.header=Markdown To PDF -MarkdownToPDF.submit=Convert -MarkdownToPDF.help=Work in progress -MarkdownToPDF.credit=Uses WeasyPrint +MarkdownToPDF.title=Markdown zu PDF +MarkdownToPDF.header=Markdown zu PDF +MarkdownToPDF.submit=Konvertieren +MarkdownToPDF.help=In Arbeit +MarkdownToPDF.credit=Verwendet WeasyPrint #url-to-pdf -URLToPDF.title=URL To PDF -URLToPDF.header=URL To PDF -URLToPDF.submit=Convert -URLToPDF.credit=Uses WeasyPrint +URLToPDF.title=URL zu PDF +URLToPDF.header=URL zu PDF +URLToPDF.submit=Konvertieren +URLToPDF.credit=Verwendet WeasyPrint #html-to-pdf -HTMLToPDF.title=HTML To PDF -HTMLToPDF.header=HTML To PDF -HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required -HTMLToPDF.submit=Convert -HTMLToPDF.credit=Uses WeasyPrint +HTMLToPDF.title=HTML zu PDF +HTMLToPDF.header=HTML zu PDF +HTMLToPDF.help=Akzeptiert HTML-Dateien und ZIPs mit html/css/images etc. +HTMLToPDF.submit=Konvertieren +HTMLToPDF.credit=Verwendet WeasyPrint #sanitizePDF -sanitizePDF.title=Sanitize PDF -sanitizePDF.header=Sanitize a PDF file -sanitizePDF.selectText.1=Remove JavaScript actions -sanitizePDF.selectText.2=Remove embedded files -sanitizePDF.selectText.3=Remove metadata -sanitizePDF.selectText.4=Remove links -sanitizePDF.selectText.5=Remove fonts -sanitizePDF.submit=Sanitize PDF +sanitizePDF.title=PDF Bereinigen +sanitizePDF.header=PDF Bereinigen +sanitizePDF.selectText.1=Javascript-Aktionen entfernen +sanitizePDF.selectText.2=Eingebettete Dateien entfernen +sanitizePDF.selectText.3=Metadaten entfernen +sanitizePDF.selectText.4=Links entfernen +sanitizePDF.selectText.5=Schriftarten entfernen +sanitizePDF.submit=Bereinigen #addPageNumbers -addPageNumbers.title=Add Page Numbers -addPageNumbers.header=Add Page Numbers -addPageNumbers.selectText.1=Select PDF file: -addPageNumbers.selectText.2=Margin Size +addPageNumbers.title=Seitenzahlen hinzufügen +addPageNumbers.header=Seitenzahlen hinzufügen +addPageNumbers.selectText.1=PDF-Datei auswählen: +addPageNumbers.selectText.2=Margin Größe addPageNumbers.selectText.3=Position -addPageNumbers.selectText.4=Starting Number -addPageNumbers.selectText.5=Pages to Number -addPageNumbers.selectText.6=Custom Text -addPageNumbers.submit=Add Page Numbers +addPageNumbers.selectText.4=Startnummer +addPageNumbers.selectText.5=Seiten zu nummerieren +addPageNumbers.selectText.6=Benutzerdefinierter Text +addPageNumbers.customTextDesc=Benutzerdefinierter Text +addPageNumbers.numberPagesDesc=Welche Seiten nummeriert werden sollen, Standardeinstellung 'alle' ('all'), akzeptiert auch 1-5 oder 2,5,9 usw. +addPageNumbers.customNumberDesc=Standardmäßig {n}, akzeptiert auch 'Seite {n} von {insgesamt}', 'Text-{n}', '{Dateiname}-{n} ('{filename}-{n}) +addPageNumbers.submit=Seitenzahlen hinzufügen #auto-rename -auto-rename.title=Auto Rename -auto-rename.header=Auto Rename PDF -auto-rename.submit=Auto Rename +auto-rename.title=PDF automatisch umbenennen +auto-rename.header=PDF automatisch umbenennen +auto-rename.submit=Automatisch umbenennen #adjustContrast -adjustContrast.title=Adjust Contrast -adjustContrast.header=Adjust Contrast -adjustContrast.contrast=Contrast: -adjustContrast.brightness=Brightness: -adjustContrast.saturation=Saturation: -adjustContrast.download=Download +adjustContrast.title=Kontrast anpassen +adjustContrast.header=Farben/Kontrast anpassen +adjustContrast.contrast=Kontrast: +adjustContrast.brightness=Helligkeit: +adjustContrast.saturation=Sättigung: +adjustContrast.download=Herunterladen #crop -crop.title=Crop -crop.header=Crop Image -crop.submit=Submit +crop.title=Zuschneiden +crop.header=Bild zuschneiden +crop.submit=Abschicken #autoSplitPDF -autoSplitPDF.title=Auto Split PDF -autoSplitPDF.header=Auto Split PDF -autoSplitPDF.description=Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed. -autoSplitPDF.selectText.1=Print out some divider sheets from below (Black and white is fine). -autoSplitPDF.selectText.2=Scan all your documents at once by inserting the divider sheet between them. -autoSplitPDF.selectText.3=Upload the single large scanned PDF file and let Stirling PDF handle the rest. -autoSplitPDF.selectText.4=Divider pages are automatically detected and removed, guaranteeing a neat final document. -autoSplitPDF.formPrompt=Submit PDF containing Stirling-PDF Page dividers: -autoSplitPDF.duplexMode=Duplex Mode (Front and back scanning) -autoSplitPDF.dividerDownload1=Download 'Auto Splitter Divider (minimal).pdf' -autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf' -autoSplitPDF.submit=Submit +autoSplitPDF.title=PDF automatisch teilen +autoSplitPDF.header=PDF automatisch teilen +autoSplitPDF.description=Drucken Sie, fügen Sie ein, scannen Sie, laden Sie hoch, und lassen Sie uns Ihre Dokumente automatisch trennen. Kein manuelles Sortieren erforderlich. +autoSplitPDF.selectText.1=Drucken Sie einige Trennblätter aus (schwarz/weiß ist ausreichend). +autoSplitPDF.selectText.2=Scannen Sie alle Dokumente auf einmal, indem Sie das Trennblatt zwischen die Dokumente einlegen. +autoSplitPDF.selectText.3=Laden Sie die einzelne große gescannte PDF-Datei hoch und überlassen Sie Stirling PDF den Rest. +autoSplitPDF.selectText.4=Trennseiten werden automatisch erkannt und entfernt, so dass ein sauberes Enddokument garantiert ist. +autoSplitPDF.formPrompt=PDF mit Stirling-PDF Seitentrennern hochladen: +autoSplitPDF.duplexMode=Duplex-Modus (Scannen von Vorder- und Rückseite) +autoSplitPDF.dividerDownload1=Herunterladen 'Auto Splitter Divider (minimal).pdf' +autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (mit Anleitung).pdf' +autoSplitPDF.submit=Aufteilen #pipeline @@ -385,18 +475,22 @@ pipeline.title=Pipeline #pageLayout -pageLayout.title=Multi Page Layout -pageLayout.header=Multi Page Layout -pageLayout.pagesPerSheet=Pages per sheet: -pageLayout.submit=Submit +pageLayout.title=Mehrseitiges Layout +pageLayout.header=Mehrseitiges Layout +pageLayout.pagesPerSheet=Seiten pro Blatt: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders +pageLayout.submit=Abschicken #scalePages -scalePages.title=Adjust page-scale -scalePages.header=Adjust page-scale -scalePages.pageSize=Size of a page of the document. -scalePages.scaleFactor=Zoom level (crop) of a page. -scalePages.submit=Submit +scalePages.title=Seitengröße anpassen +scalePages.header=Seitengröße anpassen +scalePages.pageSize=Format der Seiten des Dokuments. +scalePages.scaleFactor=Zoomstufe (Ausschnitt) einer Seite. +scalePages.submit=Abschicken #certSign @@ -416,13 +510,13 @@ certSign.submit=PDF signieren #removeBlanks -removeBlanks.title=Leerzeichen entfernen +removeBlanks.title=Leere Seiten entfernen removeBlanks.header=Leere Seiten entfernen removeBlanks.threshold=Schwellenwert: removeBlanks.thresholdDesc=Schwellenwert zur Bestimmung, wie weiß ein weißer Pixel sein muss removeBlanks.whitePercent=Weißprozentsatz (%): removeBlanks.whitePercentDesc=Prozentsatz der Seite, die weiß sein muss, um entfernt zu werden -removeBlanks.submit=Leerzeichen entfernen +removeBlanks.submit=Leere Seiten entfernen #compare @@ -440,12 +534,12 @@ sign.upload=Bild hochladen sign.draw=Signatur zeichnen sign.text=Texteingabe sign.clear=Klar -sign.add=Hinzufügen +sign.add=Signieren #repair repair.title=Reparieren -repair.header=Repair PDFs +repair.header=PDFs reparieren repair.submit=Reparieren @@ -526,6 +620,8 @@ addImage.submit=Bild hinzufügen #merge merge.title=Zusammenführen merge.header=Mehrere PDFs zusammenführen (2+) +merge.sortByName=Nach Namen sortieren +merge.sortByDate=Nach Datum sortieren merge.submit=Zusammenführen @@ -573,7 +669,10 @@ split.submit=Aufteilen imageToPDF.title=Bild zu PDF imageToPDF.header=Bild zu PDF imageToPDF.submit=Umwandeln -imageToPDF.selectText.1=Auf Seite strecken +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=PDF automatisch drehen imageToPDF.selectText.3=Mehrere Dateien verarbeiten (nur aktiv, wenn Sie mit mehreren Bildern arbeiten) imageToPDF.selectText.4=In ein einziges PDF zusammenführen @@ -610,9 +709,9 @@ addPassword.selectText.10=Modifizierung verhindern addPassword.selectText.11=Ändern von Kommentaren verhindern addPassword.selectText.12=Drucken verhindern addPassword.selectText.13=Drucken verschiedener Formate verhindern -addPassword.selectText.14=Owner Password -addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) -addPassword.selectText.16=Restricts the opening of the document itself +addPassword.selectText.14=Passwort des Besitzers +addPassword.selectText.15=Schränkt ein, was mit dem Dokument gemacht werden kann, sobald es geöffnet ist (wird nicht von allen Leseprogrammen unterstützt) +addPassword.selectText.16=Schränkt das Öffnen des Dokuments selbst ein addPassword.submit=Verschlüsseln @@ -626,17 +725,11 @@ watermark.selectText.4=Drehung (0-360): watermark.selectText.5=breiteSpacer (horizontaler Abstand zwischen den einzelnen Wasserzeichen): watermark.selectText.6=höheSpacer (vertikaler Abstand zwischen den einzelnen Wasserzeichen): watermark.selectText.7=Deckkraft (0% - 100 %): +watermark.selectText.8=Wasserzeichen Typ: +watermark.selectText.9=Wasserzeichen-Bild: watermark.submit=Wasserzeichen hinzufügen -#remove-watermark -remove-watermark.title=Wasserzeichen entfernen -remove-watermark.header=Wasserzeichen entfernen -remove-watermark.selectText.1=PDF auswählen, um Wasserzeichen zu entfernen von: -remove-watermark.selectText.2=Wasserzeichentext: -remove-watermark.submit=Wasserzeichen entfernen - - #Change permissions permissions.title=Berechtigungen ändern permissions.header=Berechtigungen ändern @@ -682,13 +775,6 @@ changeMetadata.selectText.5=Benutzerdefinierten Metadateneintrag hinzufügen changeMetadata.submit=Ändern -#xlsToPdf -xlsToPdf.title=Excel in PDF -xlsToPdf.header=Excel in PDF -xlsToPdf.selectText.1=XLS- oder XLSX-Excel-Tabelle zum Konvertieren auswählen -xlsToPdf.convert=konvertieren - - #pdfToPDFA pdfToPDFA.title=PDF zu PDF/A pdfToPDFA.header=PDF zu PDF/A diff --git a/src/main/resources/messages_el_GR.properties b/src/main/resources/messages_el_GR.properties new file mode 100644 index 00000000..715d63a1 --- /dev/null +++ b/src/main/resources/messages_el_GR.properties @@ -0,0 +1,817 @@ +########### +# Generic # +########### +# the direction that the language is written (ltr = left to right, rtl = right to left) +language.direction=ltr + +pdfPrompt=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE PDF(s) +multiPdfPrompt=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE PDFs (2+) +multiPdfDropPrompt=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE (\u03AE \u03C4\u03C1\u03AC\u03B2\u03B7\u03B3\u03BC\u03B1 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 \u03BA\u03B1\u03B9 \u03B1\u03C0\u03CC\u03B8\u03B5\u03C3\u03B7) \u03CC\u03BB\u03C9\u03BD \u03C4\u03C9\u03BD PDF \u03C0\u03BF\u03C5 \u03C7\u03C1\u03B5\u03B9\u03AC\u03B6\u03B5\u03C3\u03C4\u03B5 +imgPrompt=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE \u0395\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2(\u0395\u03B9\u03BA\u03CC\u03BD\u03C9\u03BD) +genericSubmit=\u03A5\u03C0\u03BF\u03B2\u03BF\u03BB\u03AE +processTimeWarning=\u03A0\u03C1\u03BF\u03C3\u03BF\u03C7\u03AE: \u0391\u03C5\u03C4\u03AE \u03B7 \u03B4\u03B9\u03B1\u03B4\u03B9\u03BA\u03B1\u03C3\u03AF\u03B1 \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B4\u03B9\u03B1\u03C1\u03BA\u03AD\u03C3\u03B5\u03B9 \u03AD\u03C9\u03C2 \u03BA\u03B1\u03B9 \u03AD\u03BD\u03B1 \u03BB\u03B5\u03C0\u03C4\u03CC \u03B1\u03BD\u03AC\u03BB\u03BF\u03B3\u03B1 \u03BC\u03B5 \u03C4\u03BF \u03BC\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03C4\u03BF\u03C5 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 +pageOrderPrompt=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03C3\u03BC\u03AD\u03BD\u03B7 \u03A3\u03B5\u03B9\u03C1\u03AC \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 (\u03A0\u03C1\u03BF\u03C3\u03B8\u03AD\u03C3\u03C4\u03B5 \u03BC\u03AF\u03B1 \u03BB\u03AF\u03C3\u03C4\u03B5 \u03B1\u03C0\u03BF \u03B1\u03C1\u03B9\u03B8\u03BC\u03BF\u03CD\u03C2 \u03C3\u03B5\u03BB\u03B9\u03B4\u03CE\u03BD, \u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03AD\u03BD\u03B5\u03C2 \u03BC\u03B5 \u03BA\u03CC\u03BC\u03BC\u03B1 \u03AE \u03C3\u03C5\u03BD\u03B1\u03C1\u03C4\u03AE\u03C3\u03B5\u03B9\u03C2 \u03CC\u03C0\u03C9\u03C2 2n+1) : +goToPage=Go +true=\u0391\u03BB\u03B7\u03B8\u03AD\u03C2 +false=\u039B\u03B1\u03BD\u03B8\u03B1\u03C3\u03BC\u03AD\u03BD\u03BF +unknown=\u0386\u03B3\u03BD\u03C9\u03C3\u03C4\u03BF +save=\u0391\u03C0\u03BF\u03B8\u03AE\u03BA\u03B5\u03C5\u03C3\u03B7 +close=\u039A\u03BB\u03B5\u03AF\u03C3\u03B9\u03BC\u03BF +filesSelected=\u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 \u03C0\u03BF\u03C5 \u03B5\u03C0\u03B9\u03BB\u03AD\u03C7\u03B8\u03B7\u03BA\u03B1\u03BD +noFavourites=\u039A\u03B1\u03BD\u03AD\u03BD\u03B1 \u03B1\u03B3\u03B1\u03C0\u03AE\u03BC\u03B5\u03BD\u03BF \u03B4\u03B5\u03BD \u03AD\u03C7\u03B5\u03B9 \u03C0\u03C1\u03BF\u03C3\u03C4\u03B5\u03B8\u03B5\u03AF +bored=\u0392\u03B1\u03C1\u03B9\u03AD\u03C3\u03C4\u03B5 \u03BD\u03B1 \u03C0\u03B5\u03C1\u03B9\u03BC\u03AD\u03BD\u03B5\u03C4\u03B5; +alphabet=\u0391\u03BB\u03C6\u03AC\u03B2\u03B7\u03C4\u03BF +downloadPdf=\u039A\u03B1\u03C4\u03AD\u03B2\u03B1\u03C3\u03BC\u03B1 \u03C4\u03BF\u03C5 PDF +text=\u039A\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF +font=\u0393\u03C1\u03B1\u03BC\u03BC\u03B1\u03C4\u03BF\u03C3\u03B5\u03B9\u03C1\u03AC +selectFillter=-- \u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE -- +pageNum=\u0391\u03C1\u03B9\u03B8\u03BC\u03CC\u03C2 \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 +sizes.small=\u039C\u03B9\u03BA\u03C1\u03CC +sizes.medium=\u039C\u03B5\u03C3\u03B1\u03AF\u03BF +sizes.large=\u039C\u03B5\u03B3\u03AC\u03BB\u03BF +sizes.x-large=\u03A0\u03BF\u03BB\u03CD \u039C\u03B5\u03B3\u03AC\u03BB\u03BF +error.pdfPassword=\u03A4\u03BF PDF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF \u03B5\u03AF\u03BD\u03B1\u03B9 \u03BA\u03BB\u03B5\u03B9\u03B4\u03C9\u03BC\u03AD\u03BD\u03BF \u03BC\u03B5 \u03BA\u03C9\u03B4\u03B9\u03BA\u03CC \u03BA\u03B1\u03B9 \u03B5\u03AF\u03C4\u03B5 \u03B4\u03B5\u03BD \u03AD\u03C7\u03B5\u03C4\u03B5 \u03B5\u03B9\u03C3\u03AC\u03B3\u03B5\u03B9 \u03BA\u03C9\u03B4\u03B9\u03BA\u03CC, \u03B5\u03AF\u03C4\u03B5 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03BB\u03B1\u03BD\u03B8\u03B1\u03C3\u03BC\u03AD\u03BD\u03BF\u03C2 +delete=\u0394\u03B9\u03B1\u03B3\u03C1\u03B1\u03C6\u03AE +username=\u038C\u03BD\u03BF\u03BC\u03B1 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7 +password=\u039A\u03C9\u03B4\u03B9\u03BA\u03CC\u03C2 +welcome=\u039A\u03B1\u03BB\u03C9\u03C2 \u0389\u03BB\u03B8\u03B1\u03C4\u03B5 +property=Property +black=\u039C\u03B1\u03CD\u03C1\u03BF +white=\u0386\u03C3\u03C0\u03C1\u03BF +red=\u039A\u03CC\u03BA\u03BA\u03B9\u03BD\u03BF +green=\u03A0\u03C1\u03AC\u03C3\u03B9\u03BD\u03BF +blue=\u039C\u03C0\u03BB\u03AD +custom=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE... + +changedCredsMessage=\u03A4\u03B1 \u03B4\u03B9\u03B1\u03C0\u03B9\u03C3\u03C4\u03B5\u03C5\u03C4\u03AE\u03C1\u03B9\u03B1 \u03AD\u03C7\u03BF\u03C5\u03BD \u03B1\u03BB\u03BB\u03AC\u03BE\u03B5\u03B9! +notAuthenticatedMessage=\u039F \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7\u03C2 \u03B4\u03B5\u03BD \u03AD\u03C7\u03B5\u03B9 \u03B1\u03C5\u03B8\u03B5\u03BD\u03C4\u03B9\u03BA\u03BF\u03C0\u03BF\u03B9\u03B7\u03B8\u03B5\u03AF. +userNotFoundMessage=\u039F \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7\u03C2 \u03B4\u03B5\u03BD \u03B2\u03C1\u03AD\u03B8\u03B7\u03BA\u03B5. +incorrectPasswordMessage=\u039F \u03C4\u03C1\u03AD\u03C7\u03C9\u03BD \u03BA\u03C9\u03B4\u03B9\u03BA\u03CC\u03C2 \u03C0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03BB\u03B1\u03BD\u03B8\u03B1\u03C3\u03BC\u03AD\u03BD\u03BF\u03C2. +usernameExistsMessage=\u03A4\u03BF \u03BD\u03AD\u03BF \u03CC\u03BD\u03BF\u03BC\u03B1 \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7 \u03C5\u03C0\u03AC\u03C1\u03C7\u03B5\u03B9 \u03AE\u03B4\u03B7. + + + +############# +# NAVBAR # +############# +navbar.convert=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE +navbar.security=\u0391\u03C3\u03C6\u03AC\u03BB\u03B5\u03B9\u03B1 +navbar.other=\u0394\u03B9\u03AC\u03C6\u03BF\u03C1\u03B1 +navbar.darkmode=\u039C\u03B1\u03CD\u03C1\u03BF \u0398\u03AD\u03BC\u03B1 +navbar.pageOps=\u039B\u03B5\u03B9\u03C4\u03BF\u03C5\u03C1\u03B3\u03AF\u03B5\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 +navbar.settings=\u03A1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03B9\u03C2 + +############# +# SETTINGS # +############# +settings.title=\u03A1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03B9\u03C2 +settings.update=\u03A5\u03C0\u03AC\u03C1\u03C7\u03B5\u03B9 \u03B4\u03B9\u03B1\u03B8\u03AD\u03C3\u03B9\u03BC\u03B7 \u03B5\u03BD\u03B7\u03BC\u03AD\u03C1\u03C9\u03C3\u03B7 +settings.appVersion=\u0388\u03BA\u03B4\u03BF\u03C3\u03B7 \u03B5\u03C6\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE\u03C2: App Version: +settings.downloadOption.title=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03B5\u03C4\u03B5 \u03C4\u03B7\u03BD \u03B5\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE \u03BB\u03AE\u03C8\u03B7\u03C2 (\u0393\u03B9\u03B1 \u03BB\u03AE\u03C8\u03B5\u03B9\u03C2 \u03BC\u03B5\u03BC\u03BF\u03BD\u03C9\u03BC\u03AD\u03BD\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD \u03C7\u03C9\u03C1\u03AF\u03C2 zip): +settings.downloadOption.1=\u0386\u03BD\u03BF\u03B9\u03B3\u03BC\u03B1 \u03C3\u03C4\u03BF \u03AF\u03B4\u03B9\u03BF \u03C0\u03B1\u03C1\u03AC\u03B8\u03C5\u03C1\u03BF +settings.downloadOption.2=\u0386\u03BD\u03BF\u03B9\u03B3\u03BC\u03B1 \u03C3\u03B5 \u03BD\u03AD\u03BF \u03C0\u03B1\u03C1\u03AC\u03B8\u03C5\u03C1\u03BF +settings.downloadOption.3=\u039B\u03AE\u03C8\u03B7 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 +settings.zipThreshold=Zip \u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 \u03CC\u03C4\u03B1\u03BD \u03BF \u03B1\u03C1\u03B9\u03B8\u03BC\u03CC\u03C2 \u03C4\u03C9\u03BD \u03BB\u03B7\u03C6\u03B8\u03AD\u03BD\u03C4\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD \u03B5\u03AF\u03BD\u03B1\u03B9 \u03C0\u03BF\u03BB\u03CD \u03BC\u03B5\u03B3\u03AC\u03BB\u03BF\u03C2 +settings.signOut=\u0391\u03C0\u03BF\u03C3\u03CD\u03BD\u03B4\u03B5\u03C3\u03B7 +settings.accountSettings=\u03A1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03B9\u03C2 \u039B\u03BF\u03B3\u03B1\u03C1\u03B9\u03B1\u03C3\u03BC\u03BF\u03CD + + + +changeCreds.title=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE \u0394\u03B9\u03B1\u03C0\u03B9\u03C3\u03C4\u03B5\u03C5\u03C4\u03B7\u03C1\u03AF\u03C9\u03BD +changeCreds.header=\u0395\u03BD\u03B7\u03BC\u03AD\u03C1\u03C9\u03C3\u03B7 \u03C4\u03C9\u03BD \u03BB\u03B5\u03C0\u03C4\u03BF\u03BC\u03B5\u03C1\u03B5\u03B9\u03CE\u03BD \u03C4\u03BF\u03C5 \u039B\u03BF\u03B3\u03B1\u03C1\u03B9\u03B1\u03C3\u03BC\u03BF\u03CD \u03C3\u03B1\u03C2 +changeCreds.changeUserAndPassword=\u03A7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF\u03C4\u03B5 \u03C4\u03B1 \u03C0\u03C1\u03BF\u03B5\u03C0\u03B9\u03BB\u03B5\u03B3\u03BC\u03AD\u03BD\u03B1 \u03B4\u03B9\u03B1\u03C0\u03B9\u03C3\u03C4\u03B5\u03C5\u03C4\u03AE\u03C1\u03B9\u03B1 \u03C3\u03CD\u03BD\u03B4\u03B5\u03C3\u03B7\u03C2. \u03A0\u03B1\u03C1\u03B1\u03BA\u03B1\u03BB\u03CE \u03B5\u03B9\u03C3\u03AC\u03B3\u03B5\u03C4\u03B5 \u03BD\u03AD\u03BF \u03BA\u03C9\u03B4\u03B9\u03BA\u03CC \u03C0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 (\u03BA\u03B1\u03B9 \u03CC\u03BD\u03BF\u03BC\u03B1 \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7 \u03B1\u03BD \u03C4\u03BF \u03B5\u03C0\u03B9\u03B8\u03C5\u03BC\u03B5\u03AF\u03C4\u03B5) +changeCreds.newUsername=\u039D\u03AD\u03BF \u038C\u03BD\u03BF\u03BC\u03B1 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7 +changeCreds.oldPassword=\u03A4\u03C1\u03AD\u03C7\u03C9\u03BD \u039A\u03C9\u03B4\u03B9\u03BA\u03CC\u03C2 \u03A0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 +changeCreds.newPassword=\u039D\u03AD\u03BF\u03C2 \u039A\u03C9\u03B4\u03B9\u03BA\u03CC\u03C2 \u03A0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 +changeCreds.confirmNewPassword=\u0395\u03C0\u03B9\u03B2\u03B5\u03B2\u03B1\u03AF\u03C9\u03C3\u03B7 \u039D\u03AD\u03BF\u03C5 \u039A\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD \u03A0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 +changeCreds.submit=\u03A5\u03C0\u03BF\u03B2\u03BF\u03BB\u03AE \u0391\u03BB\u03BB\u03B1\u03B3\u03CE\u03BD + + + +account.title=\u03A1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03B9\u03C2 \u039B\u03BF\u03B3\u03B1\u03C1\u03B9\u03B1\u03C3\u03BC\u03BF\u03CD +account.accountSettings=\u03A1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03B9\u03C2 \u039B\u03BF\u03B3\u03B1\u03C1\u03B9\u03B1\u03C3\u03BC\u03BF\u03CD +account.adminSettings=\u03A1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03B9\u03C2 \u0394\u03B9\u03B1\u03C7\u03B5\u03B9\u03C1\u03B9\u03C3\u03C4\u03AE - \u03A0\u03C1\u03BF\u03B2\u03BF\u03BB\u03AE \u03BA\u03B1\u03B9 \u03C0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03C7\u03C1\u03B7\u03C3\u03C4\u03CE\u03BD +account.userControlSettings=\u03A1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03B9\u03C2 \u03A7\u03B5\u03B9\u03C1\u03B9\u03C3\u03BC\u03BF\u03CD \u03A7\u03C1\u03B7\u03C3\u03C4\u03CE\u03BD +account.changeUsername=\u039D\u03AD\u03BF \u038C\u03BD\u03BF\u03BC\u03B1 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7 +account.changeUsername=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE \u039F\u03BD\u03CC\u03BC\u03B1\u03C4\u03BF\u03C2 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7 +account.password=\u0395\u03C0\u03B9\u03B2\u03B5\u03B2\u03B1\u03AF\u03C9\u03C3\u03B7 \u039A\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD \u03A0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 +account.oldPassword=\u03A0\u03B1\u03BB\u03B9\u03CC\u03C2 \u039A\u03C9\u03B4\u03B9\u03BA\u03CC\u03C2 \u03A0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 +account.newPassword=\u039D\u03AD\u03BF\u03C2 \u039A\u03C9\u03B4\u03B9\u03BA\u03CC\u03C2 \u03A0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 +account.changePassword=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE \u039A\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD \u03A0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 +account.confirmNewPassword=\u0395\u03C0\u03B9\u03B2\u03B5\u03B2\u03B1\u03AF\u03C9\u03C3\u03B7 \u039D\u03AD\u03BF\u03C5 \u039A\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD +account.signOut=\u0391\u03C0\u03BF\u03C3\u03CD\u03BD\u03B4\u03B5\u03C3\u03B7 +account.yourApiKey=\u03A4\u03BF \u03BA\u03BB\u03B5\u03B9\u03B4\u03AF \u03C3\u03B1\u03C2 \u03B3\u03B9\u03B1 \u03C4\u03B7 \u03B4\u03B9\u03B5\u03C0\u03B1\u03C6\u03AE \u03C0\u03C1\u03BF\u03B3\u03C1\u03B1\u03BC\u03BC\u03B1\u03C4\u03B9\u03C3\u03BC\u03BF\u03CD \u03B5\u03C6\u03B1\u03C1\u03BC\u03BF\u03B3\u03CE\u03BD (API key) +account.syncTitle=\u03A3\u03C5\u03B3\u03C7\u03C1\u03BF\u03BD\u03B9\u03C3\u03BC\u03CC\u03C2 \u03C4\u03C9\u03BD \u03C1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03C9\u03BD \u03C4\u03BF\u03C5 \u03C6\u03C5\u03BB\u03BB\u03BF\u03BC\u03B5\u03C4\u03C1\u03B7\u03C4\u03AE (Web Browser) \u03BC\u03B5 \u03C4\u03BF\u03BD \u03BB\u03BF\u03B3\u03B1\u03C1\u03B9\u03B1\u03C3\u03BC\u03CC +account.settingsCompare=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7 \u03A1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03C9\u03BD: +account.property=Property +account.webBrowserSettings=\u03A1\u03CD\u03B8\u03BC\u03B9\u03C3\u03B7 \u03C6\u03C5\u03BB\u03BB\u03BF\u03BC\u03B5\u03C4\u03C1\u03B7\u03C4\u03AE (Web Browser) +account.syncToBrowser=\u03A3\u03C5\u03B3\u03C7\u03C1\u03BF\u03BD\u03B9\u03C3\u03BC\u03CC\u03C2 \u039B\u03BF\u03B3\u03B1\u03C1\u03B9\u03B1\u03C3\u03BC\u03BF\u03CD -> \u03A6\u03C5\u03BB\u03BB\u03BF\u03BC\u03B5\u03C4\u03C1\u03B7\u03C4\u03AE (Web Browser) +account.syncToAccount= \u03A3\u03C5\u03B3\u03C7\u03C1\u03BF\u03BD\u03B9\u03C3\u03BC\u03CC\u03C2 \u039B\u03BF\u03B3\u03B1\u03C1\u03B9\u03B1\u03C3\u03BC\u03BF\u03CD <- \u03A6\u03C5\u03BB\u03BB\u03BF\u03BC\u03B5\u03C4\u03C1\u03B7\u03C4\u03AE (Web Browser) + + +adminUserSettings.title= \u03A1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03B9\u03C2 \u03B5\u03BB\u03AD\u03B3\u03C7\u03BF\u03C5 \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7 +adminUserSettings.header=\u03A1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03B9\u03C2 \u03B5\u03BB\u03AD\u03B3\u03C7\u03BF\u03C5 \u0394\u03B9\u03B1\u03C7\u03B5\u03B9\u03C1\u03B9\u03C3\u03C4\u03AE +adminUserSettings.admin=\u0394\u03B9\u03B1\u03C7\u03B5\u03B9\u03C1\u03B9\u03C3\u03C4\u03AE\u03C2 +adminUserSettings.user=\u03A7\u03C1\u03AE\u03C3\u03C4\u03B7\u03C2 +adminUserSettings.addUser=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03BD\u03AD\u03BF\u03C5 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7 +adminUserSettings.roles=\u03A1\u03CC\u03BB\u03BF\u03B9 +adminUserSettings.role=\u03A1\u03CC\u03BB\u03BF\u03C2 +adminUserSettings.actions=\u0395\u03BD\u03AD\u03C1\u03B3\u03B5\u03B9\u03B5\u03C2 +adminUserSettings.apiUser=\u03A0\u03B5\u03C1\u03B9\u03BF\u03C1\u03B9\u03C3\u03BC\u03AD\u03BD\u03BF\u03C2 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7\u03C2 \u03B3\u03B9\u03B1 \u03B4\u03B9\u03B5\u03C0\u03B1\u03C6\u03AE \u03C0\u03C1\u03BF\u03B3\u03C1\u03B1\u03BC\u03BC\u03B1\u03C4\u03B9\u03C3\u03BC\u03BF\u03CD \u03B5\u03C6\u03B1\u03C1\u03BC\u03BF\u03B3\u03CE\u03BD (API User) +adminUserSettings.webOnlyUser=\u03A7\u03C1\u03AE\u03C3\u03C4\u03B7\u03C2 \u03BC\u03CC\u03BD\u03BF \u0399\u03C3\u03C4\u03BF\u03CD +adminUserSettings.forceChange = \u0391\u03BD\u03B1\u03B3\u03BA\u03AC\u03C3\u03C4\u03B5 \u03C4\u03BF\u03BD \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7 \u03BD\u03B1 \u03B1\u03BB\u03BB\u03AC\u03BE\u03B5\u03B9 \u03C4\u03BF \u03CC\u03BD\u03BF\u03BC\u03B1 \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7/\u03BA\u03C9\u03B4\u03B9\u03BA\u03CC \u03C0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 \u03BA\u03B1\u03C4\u03AC \u03C4\u03B7 \u03C3\u03CD\u03BD\u03B4\u03B5\u03C3\u03B7 +adminUserSettings.submit=\u0391\u03C0\u03BF\u03B8\u03AE\u03BA\u03B5\u03C5\u03C3\u03B7 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7 + +############# +# HOME-PAGE # +############# +home.desc= \u0397 \u03C4\u03BF\u03C0\u03B9\u03BA\u03AC \u03C6\u03B9\u03BB\u03BF\u03BE\u03B5\u03BD\u03BF\u03CD\u03BC\u03B5\u03BD\u03B7 one-stop-shop \u03C3\u03B1\u03C2 \u03B3\u03B9\u03B1 \u03CC\u03BB\u03B5\u03C2 \u03C4\u03B9\u03C2 \u03B1\u03BD\u03AC\u03B3\u03BA\u03B5\u03C2 \u03C3\u03B1\u03C2 \u03C3\u03B5 PDF. + + +home.multiTool.title=PDF \u03A0\u03BF\u03BB\u03C5\u03B5\u03C1\u03B3\u03B1\u03BB\u03B5\u03AF\u03BF +home.multiTool.desc=\u03A3\u03C5\u03B3\u03C7\u03CE\u03BD\u03B5\u03C5\u03C3\u03B7, \u03A0\u03B5\u03C1\u03B9\u03C3\u03C4\u03C1\u03BF\u03C6\u03AE, \u0391\u03BD\u03B1\u03B4\u03B9\u03AC\u03C4\u03B1\u03BE\u03B7 \u03BA\u03B1\u03B9 \u039A\u03B1\u03C4\u03AC\u03C1\u03B3\u03B7\u03C3\u03B7 \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD +multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side,interactive,intractable,move + +home.merge.title=\u03A3\u03C5\u03B3\u03C7\u03CE\u03BD\u03B5\u03C5\u03C3\u03B7 +home.merge.desc=\u03A3\u03C5\u03B3\u03C7\u03CE\u03BD\u03B5\u03C5\u03C3\u03B7 \u03C0\u03BF\u03BB\u03BB\u03CE\u03BD PDF \u03C3\u03B5 \u03AD\u03BD\u03B1 \u03BC\u03B5 \u03B5\u03CD\u03BA\u03BF\u03BB\u03BF \u03C4\u03C1\u03CC\u03C0\u03BF. +merge.tags=merge,Page operations,Back end,server side + +home.split.title=\u0394\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03CC\u03C2 +home.split.desc=\u0394\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03CC\u03C2 \u03C4\u03C9\u03BD PDF \u03C3\u03B5 \u03C0\u03BF\u03BB\u03BB\u03AC \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03B1. +split.tags=Page operations,divide,Multi Page,cut,server side + +home.rotate.title=\u03A0\u03B5\u03C1\u03B9\u03C3\u03C4\u03C1\u03BF\u03C6\u03AE +home.rotate.desc=\u03A0\u03B5\u03C1\u03B9\u03C3\u03C4\u03C1\u03BF\u03C6\u03AE \u03C4\u03C9\u03BD PDF \u03C3\u03B1\u03C2 \u03BC\u03B5 \u03B5\u03CD\u03BA\u03BF\u03BB\u03BF \u03C4\u03C1\u03CC\u03C0\u03BF. +rotate.tags=server side + + +home.imageToPdf.title=\u0395\u03B9\u03BA\u03CC\u03BD\u03B1 \u03C3\u03B5 PDF +home.imageToPdf.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 (PNG, JPEG, GIF) \u03C3\u03B5 PDF. +imageToPdf.tags=conversion,img,jpg,picture,photo + +home.pdfToImage.title=PDF \u03C3\u03B5 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1 +home.pdfToImage.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03B5\u03BD\u03CC\u03C2 PDF \u03C3\u03B5 \u03BC\u03AF\u03B1 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1. (PNG, JPEG, GIF) +pdfToImage.tags=conversion,img,jpg,picture,photo + +home.pdfOrganiser.title=\u039F\u03C1\u03B3\u03AC\u03BD\u03C9\u03C3\u03B7 +home.pdfOrganiser.desc=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7/\u0391\u03BD\u03B1\u03B4\u03B9\u03AC\u03C4\u03B1\u03BE\u03B7 \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD \u03BC\u03B5 \u03BF\u03C0\u03BF\u03B9\u03B1\u03B4\u03AE\u03C0\u03BF\u03C4\u03B5 \u03C3\u03B5\u03B9\u03C1\u03AC +pdfOrganiser.tags=duplex,even,odd,sort,move + + +home.addImage.title=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u0395\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 +home.addImage.desc=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03BC\u03B9\u03B1\u03C2 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 \u03C3\u03B5 \u03BC\u03B9\u03B1 \u03BA\u03B1\u03B8\u03BF\u03C1\u03B9\u03C3\u03BC\u03AD\u03BD\u03B7 \u03B8\u03AD\u03C3\u03B7 \u03C3\u03C4\u03BF PDF +addImage.tags=img,jpg,picture,photo + +home.watermark.title=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03A5\u03B4\u03B1\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2 +home.watermark.desc=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03B5\u03BD\u03CC\u03C2 \u03C0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03C3\u03BC\u03AD\u03BD\u03BF\u03C5 \u03C5\u03B4\u03B1\u03C4\u03BF\u03B3\u03C1\u03AC\u03C6\u03B7\u03BC\u03B1\u03C4\u03BF\u03C2 \u03C3\u03C4\u03BF \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03CC PDF. +watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo + +home.permissions.title=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE \u0394\u03B9\u03BA\u03B1\u03B9\u03C9\u03BC\u03AC\u03C4\u03C9\u03BD +home.permissions.desc=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE \u03C4\u03C9\u03BD \u0394\u03B9\u03BA\u03B1\u03B9\u03C9\u03BC\u03AC\u03C4\u03C9\u03BD \u03C3\u03C4\u03BF \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF PDF +permissions.tags=read,write,edit,print + + +home.removePages.title=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 +home.removePages.desc=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03BC\u03AE \u03B5\u03C0\u03B9\u03B8\u03C5\u03BC\u03B7\u03C4\u03CE\u03BD \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD \u03B1\u03C0\u03BF \u03C4\u03BF \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF PDF. +removePages.tags=Remove pages,delete pages + +home.addPassword.title=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03BA\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD +home.addPassword.desc=\u039A\u03C1\u03C5\u03C0\u03C4\u03BF\u03B3\u03C1\u03AC\u03C6\u03B7\u03C3\u03B7 - \u03BA\u03BB\u03B5\u03AF\u03B4\u03C9\u03BC\u03B1 \u03C4\u03BF\u03C5 PDF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 \u03BC\u03B5 \u03AD\u03BD\u03B1\u03BD \u03BA\u03C9\u03B4\u03B9\u03BA\u03CC. +addPassword.tags=secure,security + +home.removePassword.title=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u039A\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD +home.removePassword.desc=\u039A\u03B1\u03C4\u03AC\u03C1\u03B3\u03AE\u03C3\u03B7 \u03C4\u03B7\u03C2 \u03C0\u03C1\u03BF\u03C3\u03C4\u03B1\u03C3\u03AF\u03B1\u03C2 \u03BC\u03B5 \u03BA\u03C9\u03B4\u03B9\u03BA\u03CC \u03C0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 \u03B1\u03C0\u03CC \u03C4\u03BF \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF PDF. +removePassword.tags=secure,Decrypt,security,unpassword,delete password + +home.compressPdfs.title=\u03A3\u03C5\u03BC\u03C0\u03AF\u03B5\u03C3\u03B7 +home.compressPdfs.desc=\u03A3\u03C5\u03BC\u03C0\u03AF\u03B5\u03C3\u03B7 \u03C4\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD PDF \u03B3\u03B9\u03B1 \u03C4\u03B7\u03BD \u03BC\u03B5\u03AF\u03C9\u03C3\u03B7 \u03C4\u03BF\u03C5 \u03BC\u03B5\u03B3\u03AD\u03B8\u03BF\u03C5\u03C2 \u03C4\u03BF\u03C5\u03C2. +compressPdfs.tags=squish,small,tiny + + +home.changeMetadata.title=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE \u039C\u03B5\u03C4\u03B1\u03B4\u03B5\u03B4\u03BF\u03BC\u03AD\u03BD\u03C9\u03BD +home.changeMetadata.desc=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE/\u039A\u03B1\u03C4\u03AC\u03C1\u03B3\u03B7\u03C3\u03B7/\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03BC\u03B5\u03C4\u03B1\u03B4\u03B5\u03B4\u03BF\u03BC\u03AD\u03BD\u03C9\u03BD \u03B1\u03C0\u03CC \u03AD\u03BD\u03B1 \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF PDF. +changeMetadata.tags==Title,author,date,creation,time,publisher,producer,stats + +home.fileToPDF.title=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03B5\u03BD\u03CC\u03C2 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 \u03C3\u03B5 PDF +home.fileToPDF.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C3\u03C7\u03B5\u03B4\u03CC\u03BD \u03BF\u03C0\u03BF\u03B9\u03BF\u03C5\u03B4\u03AE\u03C0\u03BF\u03C4\u03B5 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 \u03C3\u03B5 PDF (DOCX, PNG, XLS, PPT, TXT and more) +fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint + +home.ocr.title=\u03BF\u03C0\u03C4\u03B9\u03BA\u03AE \u03B1\u03BD\u03B1\u03B3\u03BD\u03CE\u03C1\u03B9\u03C3\u03B7 \u03C7\u03B1\u03C1\u03B1\u03BA\u03C4\u03AE\u03C1\u03C9\u03BD (OCR) / \u03A3\u03B1\u03C1\u03CE\u03C3\u03B5\u03B9\u03C2 Cleanup +home.ocr.desc=\u03A4\u03BF Cleanup \u03C3\u03B1\u03C1\u03CE\u03BD\u03B5\u03B9 \u03BA\u03B1\u03B9 \u03B1\u03BD\u03B9\u03C7\u03BD\u03B5\u03CD\u03B5\u03B9 \u03BA\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF \u03B1\u03C0\u03CC \u03B5\u03B9\u03BA\u03CC\u03BD\u03B5\u03C2 \u03BC\u03AD\u03C3\u03B1 \u03C3\u03B5 \u03AD\u03BD\u03B1 PDF \u03BA\u03B1\u03B9 \u03C4\u03BF \u03C0\u03C1\u03BF\u03C3\u03B8\u03AD\u03C4\u03B5\u03B9 \u03BE\u03B1\u03BD\u03AC \u03C9\u03C2 \u03BA\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF +ocr.tags=recognition,text,image,scan,read,identify,detection,editable + + +home.extractImages.title=\u0395\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE \u03B5\u03B9\u03BA\u03CC\u03BD\u03C9\u03BD +home.extractImages.desc=\u0395\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE \u03CC\u03BB\u03C9\u03BD \u03C4\u03C9\u03BD \u03B5\u03B9\u03BA\u03CC\u03BD\u03C9\u03BD \u03B1\u03C0\u03BF \u03AD\u03BD\u03B1 PDF \u03BA\u03B1\u03B9 \u03B1\u03C0\u03BF\u03B8\u03AE\u03BA\u03B5\u03C5\u03C3\u03B7 \u03B1\u03C5\u03C4\u03CE\u03BD \u03C3\u03B5 zip +extractImages.tags=picture,photo,save,archive,zip,capture,grab + +home.pdfToPDFA.title=PDF \u03C3\u03B5 PDF/A +home.pdfToPDFA.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE PDF \u03C3\u03B5 PDF/A \u03B3\u03B9\u03B1 \u03BC\u03B1\u03BA\u03C1\u03BF\u03C7\u03C1\u03CC\u03BD\u03B9\u03B1 \u03B1\u03C0\u03BF\u03B8\u03AE\u03BA\u03B5\u03C5\u03C3\u03B7 +pdfToPDFA.tags=archive,long-term,standard,conversion,storage,preservation + +home.PDFToWord.title=PDF \u03C3\u03B5 Word +home.PDFToWord.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03BF\u03C5 PDF \u03C3\u03B5 Word \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF (DOC, DOCX and ODT) +PDFToWord.tags=doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile + +home.PDFToPresentation.title=PDF \u03C3\u03B5 Powerpoint +home.PDFToPresentation.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03BF\u03C5 PDF \u03C3\u03B5 Powerpoint \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF (PPT, PPTX and ODP) +PDFToPresentation.tags=slides,show,office,microsoft + +home.PDFToText.title=PDF \u03C3\u03B5 RTF (\u039A\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF) +home.PDFToText.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03BF\u03C5 PDF \u03C3\u03B5 \u039A\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF \u03AE \u03C3\u03B5 \u03BC\u03BF\u03C1\u03C6\u03AE RTF +PDFToText.tags=richformat,richtextformat,rich text format + +home.PDFToHTML.title=PDF \u03C3\u03B5 HTML +home.PDFToHTML.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03BF\u03C5 PDF \u03C3\u03B5 \u03BC\u03BF\u03C1\u03C6\u03AE HTML +PDFToHTML.tags=web content,browser friendly + + +home.PDFToXML.title=PDF \u03C3\u03B5 XML +home.PDFToXML.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03BF\u03C5 PDF \u03C3\u03B5 \u03BC\u03BF\u03C1\u03C6\u03AE XML +PDFToXML.tags=data-extraction,structured-content,interop,transformation,convert + +home.ScannerImageSplit.title=\u0391\u03BD\u03AF\u03C7\u03BD\u03B5\u03C5\u03C3\u03B7/\u0394\u03B9\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03C3\u03B1\u03C1\u03C9\u03BC\u03AD\u03BD\u03C9\u03BD \u03C6\u03C9\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03B9\u03CE\u03BD +home.ScannerImageSplit.desc=\u0394\u03B9\u03B1\u03C7\u03C9\u03C1\u03AF\u03C3\u03BC\u03CC\u03C2 \u03C0\u03BF\u03BB\u03BB\u03CE\u03BD \u03C6\u03C9\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AF\u03CE\u03BD \u03BC\u03AD\u03C3\u03B1 \u03B1\u03C0\u03CC \u03BC\u03B9\u03B1 \u03C6\u03C9\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AF\u03B1/PDF +ScannerImageSplit.tags=separate,auto-detect,scans,multi-photo,organize + +home.sign.title=\u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE +home.sign.desc=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03C5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03C2 \u03C3\u03C4\u03BF PDF \u03BC\u03B5 \u03C3\u03C7\u03AD\u03B4\u03B9\u03BF, \u03BA\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF \u03AE \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1. +sign.tags=authorize,initials,drawn-signature,text-sign,image-signature + +home.flatten.title=Flatten +home.flatten.desc=\u039A\u03B1\u03C4\u03AC\u03C1\u03B3\u03B7\u03C3\u03B7 \u03CC\u03BB\u03C9\u03BD \u03C4\u03C9\u03BD \u03B4\u03B9\u03B1\u03B4\u03C1\u03B1\u03C3\u03C4\u03B9\u03BA\u03CE\u03BD \u03C3\u03C4\u03BF\u03B9\u03C7\u03B5\u03AF\u03C9\u03BD \u03BA\u03B1\u03B9 \u03C6\u03BF\u03C1\u03BC\u03CE\u03BD \u03B1\u03C0\u03CC \u03AD\u03BD\u03B1 PDF +flatten.tags=static,deactivate,non-interactive,streamline + +home.repair.title=\u0395\u03C0\u03B9\u03B4\u03B9\u03CC\u03C1\u03B8\u03C9\u03C3\u03B7 +home.repair.desc=\u03A0\u03C1\u03BF\u03C3\u03C0\u03AC\u03B8\u03B5\u03B9\u03B1 \u03B5\u03C0\u03B9\u03B4\u03B9\u03CC\u03C1\u03B8\u03C9\u03C3\u03B7\u03C2 \u03B5\u03BD\u03CC\u03C2 \u03BA\u03B1\u03C4\u03B5\u03C3\u03C4\u03C1\u03B1\u03BC\u03BC\u03AD\u03BD\u03BF\u03C5 PDF +repair.tags=fix,restore,correction,recover + +home.removeBlanks.title=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03BA\u03B5\u03BD\u03CE\u03BD \u03A3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD +home.removeBlanks.desc=\u0391\u03BD\u03AF\u03C7\u03B5\u03C5\u03C3\u03B7 \u03BA\u03B1\u03B9 \u03B1\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03BA\u03B5\u03BD\u03CE\u03BD \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD \u03B1\u03C0\u03CC \u03AD\u03BD\u03B1 \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF +removeBlanks.tags=cleanup,streamline,non-content,organize + +home.compare.title=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7 +home.compare.desc=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7 \u03BA\u03B1\u03B9 \u03B5\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 \u03C4\u03C9\u03BD \u03B4\u03B9\u03B1\u03C6\u03BF\u03C1\u03CE\u03BD \u03BC\u03B5\u03C4\u03B1\u03BE\u03CD \u03B4\u03CD\u03BF PDF \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD +compare.tags=differentiate,contrast,changes,analysis + +home.certSign.title=\u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE \u03BC\u03B5 \u03A0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03CC +home.certSign.desc=\u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE \u03B5\u03BD\u03CC\u03C2 PDF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 \u03BC\u03B5 \u03AD\u03BD\u03B1 \u03A0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03CC/\u039A\u03BB\u03B5\u03B9\u03B4\u03AF (PEM/P12) +certSign.tags=authenticate,PEM,P12,official,encrypt + +home.pageLayout.title=\u0394\u03B9\u03AC\u03C4\u03B1\u03BE\u03B7 \u03C0\u03BF\u03BB\u03BB\u03CE\u03BD \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD +home.pageLayout.desc=\u03A3\u03C5\u03B3\u03C7\u03CE\u03BD\u03B5\u03C5\u03C3\u03B7 \u03C0\u03BF\u03BB\u03BB\u03B1\u03C0\u03BB\u03CE\u03BD \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD \u03B5\u03BD\u03CC\u03C2 \u03B5\u03B3\u03B3\u03C1\u03AC\u03C6\u03BF\u03C5 PDF \u03C3\u03B5 \u03BC\u03AF\u03B1 \u03BC\u03CC\u03BD\u03BF \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1 +pageLayout.tags=merge,composite,single-view,organize + +home.scalePages.title=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE \u03C4\u03BF\u03C5 \u03BC\u03B5\u03B3\u03AD\u03B8\u03BF\u03C5\u03C2/\u03BA\u03BB\u03AF\u03BC\u03B1\u03BA\u03B1\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 +home.scalePages.desc=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE \u03C4\u03BF\u03C5 \u03BC\u03B5\u03B3\u03AD\u03B8\u03BF\u03C5\u03C2/\u03BA\u03BB\u03AF\u03BC\u03B1\u03BA\u03B1\u03C2 \u03BC\u03AF\u03B1\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 \u03BA\u03B1\u03B9/\u03B7 \u03C4\u03BF\u03C5 \u03C0\u03B5\u03C1\u03B9\u03B5\u03C7\u03BF\u03BC\u03AD\u03BD\u03BF\u03C5 \u03C4\u03B7\u03C2. +scalePages.tags=resize,modify,dimension,adapt + +home.pipeline.title=Pipeline (\u0393\u03B9\u03B1 \u03C0\u03C1\u03BF\u03C7\u03C9\u03C1\u03B7\u03BC\u03AD\u03BD\u03BF\u03C5\u03C2) +home.pipeline.desc= \u0395\u03BA\u03C4\u03AD\u03BB\u03B5\u03C3\u03B7 \u03C0\u03BF\u03BB\u03BB\u03B1\u03C0\u03BB\u03CE\u03BD \u03B5\u03BD\u03B5\u03C1\u03B3\u03B5\u03B9\u03CE\u03BD \u03C3\u03B5 \u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 PDF \u03BF\u03C1\u03AF\u03B6\u03BF\u03BD\u03C4\u03B1\u03C2 pipeline scripts +pipeline.tags=automate,sequence,scripted,batch-process + +home.add-page-numbers.title=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03B1\u03C1\u03B9\u03B8\u03BC\u03CE\u03BD \u03C3\u03B5 \u03A3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 +home.add-page-numbers.desc=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03B1\u03C1\u03B9\u03B8\u03BC\u03CE\u03BD \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD \u03C3\u03B5 \u03AD\u03BD\u03B1 \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF \u03C3\u03B5 \u03BC\u03B9\u03B1 \u03BA\u03B1\u03B8\u03BF\u03C1\u03B9\u03C3\u03BC\u03AD\u03BD\u03B7 \u03B8\u03AD\u03C3\u03B7 +add-page-numbers.tags=paginate,label,organize,index + +home.auto-rename.title=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B7 \u03BC\u03B5\u03C4\u03BF\u03BD\u03BF\u03BC\u03B1\u03C3\u03AF\u03B1 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 PDF +home.auto-rename.desc=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B7 \u03BC\u03B5\u03C4\u03BF\u03BD\u03BF\u03BC\u03B1\u03C3\u03AF\u03B1 \u03B5\u03BD\u03CC\u03C2 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 PDF \u03BC\u03B5 \u03B2\u03AC\u03C3\u03B7 \u03C4\u03B7\u03BD \u03BA\u03B5\u03C6\u03B1\u03BB\u03AF\u03B4\u03B1 \u03C0\u03BF\u03C5 \u03AD\u03C7\u03B5\u03B9 \u03B5\u03BD\u03C4\u03BF\u03C0\u03B9\u03C3\u03C4\u03B5\u03AF +auto-rename.tags=auto-detect,header-based,organize,relabel + +home.adjust-contrast.title=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE \u03C7\u03C1\u03C9\u03BC\u03AC\u03C4\u03C9\u03BD/\u0391\u03BD\u03C4\u03AF\u03B8\u03B5\u03C3\u03B7 +home.adjust-contrast.desc=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE \u03C4\u03B7\u03C2 \u03B1\u03BD\u03C4\u03AF\u03B8\u03B5\u03C3\u03B7\u03C2, \u03C4\u03BF\u03C5 \u03BA\u03BF\u03C1\u03B5\u03C3\u03BC\u03BF\u03CD \u03BA\u03B1\u03B9 \u03C4\u03B7\u03C2 \u03C6\u03C9\u03C4\u03B5\u03B9\u03BD\u03CC\u03C4\u03B7\u03C4\u03B1\u03C2 \u03B5\u03BD\u03CC\u03C2 PDF +adjust-contrast.tags=color-correction,tune,modify,enhance + +home.crop.title=\u03A0\u03B5\u03C1\u03B9\u03BA\u03BF\u03C0\u03AE PDF +home.crop.desc=\u03A0\u03B5\u03C1\u03B9\u03BA\u03BF\u03C0\u03AE \u03B5\u03BD\u03CC\u03C2 PDF \u03B3\u03B9\u03B1 \u03BD\u03B1 \u03BC\u03B5\u03B9\u03C9\u03B8\u03B5\u03AF \u03C4\u03BF \u03BC\u03AD\u03B3\u03B5\u03B8\u03CC\u03C2 \u03C4\u03BF\u03C5 (\u03B4\u03B9\u03B1\u03C4\u03B7\u03C1\u03B5\u03AF \u03C4\u03BF \u03BA\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF!) +crop.tags=trim,shrink,edit,shape + +home.autoSplitPDF.title=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03BF\u03C2 \u03B4\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03CC\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD +home.autoSplitPDF.desc=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03BF\u03C2 \u03B4\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03CC\u03C2 \u03C3\u03B1\u03C1\u03C9\u03BC\u03AD\u03BD\u03BF\u03C5 PDF \u03BC\u03B5 \u03C6\u03C5\u03C3\u03B9\u03BA\u03CC \u03C3\u03B1\u03C1\u03C9\u03BC\u03AD\u03BD\u03BF \u03B4\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03C4\u03AE \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD QR Code +autoSplitPDF.tags=QR-based,separate,scan-segment,organize + +home.sanitizePdf.title=\u0391\u03C0\u03BF\u03BB\u03CD\u03BC\u03B1\u03BD\u03C3\u03B7 +home.sanitizePdf.desc=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03C3\u03B5\u03BD\u03B1\u03C1\u03AF\u03C9\u03BD \u03BA\u03B1\u03B9 \u03AC\u03BB\u03BB\u03C9\u03BD \u03C3\u03C4\u03BF\u03B9\u03C7\u03B5\u03AF\u03C9\u03BD \u03B1\u03C0\u03CC \u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 PDF +sanitizePdf.tags=clean,secure,safe,remove-threats + +home.URLToPDF.title=URL/\u0399\u03C3\u03C4\u03CC\u03C4\u03BF\u03C0\u03BF\u03C2 \u03C3\u03B5 PDF +home.URLToPDF.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03BF\u03C0\u03BF\u03B9\u03B1\u03B4\u03AE\u03C0\u03BF\u03C4\u03B5 \u03B4\u03B9\u03B5\u03CD\u03B8\u03C5\u03BD\u03C3\u03B7\u03C2 URL http(s) \u03C3\u03B5 PDF +URLToPDF.tags=web-capture,save-page,web-to-doc,archive + +home.HTMLToPDF.title=HTML \u03C3\u03B5 PDF +home.HTMLToPDF.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03BF\u03C0\u03BF\u03B9\u03BF\u03C5\u03B4\u03AE\u03C0\u03BF\u03C4\u03B5 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 HTML \u03AE zip \u03C3\u03B5 PDF +HTMLToPDF.tags=markup,web-content,transformation,convert + + +home.MarkdownToPDF.title=Markdown \u03C3\u03B5 PDF +home.MarkdownToPDF.desc=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03BF\u03C0\u03BF\u03B9\u03BF\u03C5\u03B4\u03AE\u03C0\u03BF\u03C4\u03B5 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 Markdown \u03C3\u03B5 PDF +MarkdownToPDF.tags=markup,web-content,transformation,convert + + +home.getPdfInfo.title=\u039B\u03AE\u03C8\u03B7 \u03CC\u03BB\u03C9\u03BD \u03C4\u03C9\u03BD \u03C0\u03BB\u03B7\u03C1\u03BF\u03C6\u03BF\u03C1\u03B9\u03CE\u03BD \u03B1\u03C0\u03CC \u03C4\u03BF PDF +home.getPdfInfo.desc=\u039B\u03AE\u03C8\u03B7 \u03CC\u03BB\u03C9\u03BD \u03C4\u03C9\u03BD \u03C0\u03B9\u03B8\u03B1\u03BD\u03CE\u03BD \u03C0\u03BB\u03B7\u03C1\u03BF\u03C6\u03BF\u03C1\u03B9\u03CE\u03BD \u03B1\u03C0\u03CC \u03C4\u03B1 \u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 PDF +getPdfInfo.tags=infomation,data,stats,statistics + + +home.extractPage.title=\u0395\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD +home.extractPage.desc=\u0395\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE \u03C4\u03C9\u03BD \u03B5\u03C0\u03B9\u03BB\u03B5\u03B3\u03BC\u03AD\u03BD\u03C9\u03BD \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD \u03B1\u03C0\u03CC \u03AD\u03BD\u03B1 PDF +extractPage.tags=extract + + +home.PdfToSinglePage.title=PDF \u03C3\u03B5 \u03BC\u03AF\u03B1 \u03BC\u03B5\u03B3\u03AC\u03BB\u03B7 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1 +home.PdfToSinglePage.desc=\u03A3\u03C5\u03B3\u03C7\u03CE\u03BD\u03B5\u03C5\u03C3\u03B7 \u03CC\u03BB\u03C9\u03BD \u03C4\u03C9\u03BD \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD PDF \u03C3\u03B5 \u03BC\u03B9\u03B1 \u03BC\u03B5\u03B3\u03AC\u03BB\u03B7 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1 +PdfToSinglePage.tags=single page + + +home.showJS.title=\u0395\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 Javascript +home.showJS.desc=\u0391\u03BD\u03B6\u03AE\u03C4\u03B7\u03C3\u03B7 \u03BA\u03B1\u03B9 \u03B5\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 \u03BA\u03CE\u03B4\u03B9\u03BA\u03B1 Javascript \u03C0\u03BF\u03C5 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03B5\u03BD\u03C3\u03C9\u03BC\u03B1\u03C4\u03C9\u03BC\u03AD\u03BD\u03BF \u03BC\u03AD\u03C3\u03B1 \u03C3\u03B5 \u03AD\u03BD\u03B1 PDF +showJS.tags=JS + +home.autoRedact.title=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03BF \u039C\u03B1\u03CD\u03C1\u03B9\u03C3\u03BC\u03B1 \u039A\u03B5\u03B9\u03BC\u03AD\u03BD\u03BF\u03C5 +home.autoRedact.desc=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B7 \u03B5\u03C0\u03B5\u03BE\u03B5\u03C1\u03B3\u03B1\u03C3\u03AF\u03B1 (\u039C\u03B1\u03CD\u03C1\u03B9\u03C3\u03BC\u03B1) \u03BA\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF\u03C5 \u03C3\u03B5 PDF \u03BC\u03B5 \u03B2\u03AC\u03C3\u03B7 \u03C4\u03BF \u03BA\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF \u03B5\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AE\u03C2 +showJS.tags=Redact,Hide,black out,black,marker,hidden + +########################### +# # +# WEB PAGES # +# # +########################### +#login +login.title=\u0395\u03AF\u03C3\u03BF\u03B4\u03BF\u03C2 +login.signin=\u0395\u03AF\u03C3\u03BF\u03B4\u03BF\u03C2 +login.rememberme=\u039D\u03B1 \u039C\u03B5 \u0398\u03C5\u03BC\u03AC\u03C3\u03B1\u03B9 +login.invalid=\u039B\u03AC\u03B8\u03BF\u03C2 \u03CC\u03BD\u03BF\u03BC\u03B1 \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7 \u03AE \u03BA\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD \u03C0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2. +login.locked=\u039F \u03BB\u03BF\u03B3\u03B1\u03C1\u03B9\u03B1\u03C3\u03BC\u03CC\u03C2 \u03C3\u03B1\u03C2 \u03AD\u03C7\u03B5\u03B9 \u03BA\u03BB\u03B5\u03B9\u03B4\u03C9\u03B8\u03B5\u03AF. +login.signinTitle=\u03A0\u03B1\u03C1\u03B1\u03BA\u03B1\u03BB\u03CE, \u03C3\u03C5\u03BD\u03B4\u03B5\u03B8\u03B5\u03AF\u03C4\u03B5 + + +#auto-redact +autoRedact.title=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03BF \u039C\u03B1\u03CD\u03C1\u03B9\u03C3\u03BC\u03B1 \u039A\u03B5\u03B9\u03BC\u03AD\u03BD\u03BF\u03C5 +autoRedact.header=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03BF \u039C\u03B1\u03CD\u03C1\u03B9\u03C3\u03BC\u03B1 \u039A\u03B5\u03B9\u03BC\u03AD\u03BD\u03BF\u03C5 +autoRedact.colorLabel=\u03A7\u03C1\u03CE\u03BC\u03B1 +autoRedact.textsToRedactLabel=\u039A\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF \u03B3\u03B9\u03B1 \u03BC\u03B1\u03CD\u03C1\u03B9\u03C3\u03BC\u03B1 (\u03B4\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03AD\u03BD\u03BF \u03C3\u03B5 \u03B3\u03C1\u03B1\u03BC\u03BC\u03AD\u03C2) +autoRedact.textsToRedactPlaceholder=\u03C0.\u03C7. \n\u0395\u03BC\u03C0\u03B9\u03C3\u03C4\u03B5\u03C5\u03C4\u03B9\u03BA\u03CC \n\u0391\u03BA\u03C1\u03CE\u03C2 \u03B1\u03C0\u03CC\u03C1\u03C1\u03B7\u03C4\u03BF +autoRedact.useRegexLabel=\u03A7\u03C1\u03AE\u03C3\u03B7 Regex +autoRedact.wholeWordSearchLabel=\u0391\u03BD\u03B1\u03B6\u03AE\u03C4\u03B7\u03C3\u03B7 \u03BF\u03BB\u03CC\u03BA\u03BB\u03B7\u03C1\u03B7\u03C2 \u03C4\u03B7\u03C2 \u03BB\u03AD\u03BE\u03B7\u03C2 +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE PDF \u03C3\u03B5 PDF-\u0395\u03B9\u03BA\u03CC\u03BD\u03B1 (\u03A7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF\u03C4\u03B1\u03B9 \u03B3\u03B9\u03B1 \u03C4\u03B7\u03BD \u03B1\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03BA\u03B5\u03B9\u03BC\u03AD\u03BD\u03BF\u03C5 \u03C0\u03AF\u03C3\u03C9 \u03B1\u03C0\u03CC \u03C4\u03BF \u03C0\u03BB\u03B1\u03AF\u03C3\u03B9\u03BF) +autoRedact.submitButton=\u03A5\u03C0\u03BF\u03B2\u03BF\u03BB\u03AE + + +#showJS +showJS.title=\u0395\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 Javascript +showJS.header=\u0395\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 Javascript +showJS.downloadJS=\u039B\u03AE\u03C8\u03B7 Javascript +showJS.submit=\u0395\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 + + +#pdfToSinglePage +pdfToSinglePage.title=PDF \u03C3\u03B5 \u039C\u03BF\u03BD\u03AE \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1 +pdfToSinglePage.header=PDF \u03C3\u03B5 \u039C\u03BF\u03BD\u03AE \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1 +pdfToSinglePage.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C3\u03B5 \u039C\u03BF\u03BD\u03AE \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1 + + +#pageExtracter +pageExtracter.title=\u0395\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE \u03A3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD +pageExtracter.header=E\u0395\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE \u03A3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD +pageExtracter.submit=\u0395\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE + + +#getPdfInfo +getPdfInfo.title=\u0391\u03BD\u03AC\u03BA\u03C4\u03B7\u03C3\u03B7 \u03C0\u03BB\u03B7\u03C1\u03BF\u03C6\u03BF\u03C1\u03B9\u03CE\u03BD \u03B1\u03C0\u03CC \u03C4\u03BF PDF +getPdfInfo.header=\u0391\u03BD\u03AC\u03BA\u03C4\u03B7\u03C3\u03B7 \u03C0\u03BB\u03B7\u03C1\u03BF\u03C6\u03BF\u03C1\u03B9\u03CE\u03BD \u03B1\u03C0\u03CC \u03C4\u03BF PDF +getPdfInfo.submit=\u0391\u03BD\u03AC\u03BA\u03C4\u03B7\u03C3\u03B7 \u03C0\u03BB\u03B7\u03C1\u03BF\u03C6\u03BF\u03C1\u03B9\u03CE\u03BD +getPdfInfo.downloadJson=\u039B\u03AE\u03C8\u03B7 JSON + + +#markdown-to-pdf +MarkdownToPDF.title=Markdown \u03C3\u03B5 PDF +MarkdownToPDF.header=Markdown \u03C3\u03B5 PDF +MarkdownToPDF.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE +MarkdownToPDF.help=\u0395\u03C1\u03B3\u03B1\u03C3\u03AF\u03B1 \u03C3\u03B5 \u03B5\u03BE\u03AD\u03BB\u03B9\u03BE\u03B7 +MarkdownToPDF.credit=\u03A7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF WeasyPrint + + + +#url-to-pdf +URLToPDF.title=URL \u03C3\u03B5 PDF +URLToPDF.header=URL \u03C3\u03B5 PDF +URLToPDF.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE +URLToPDF.credit=\u03A7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF WeasyPrint + + +#html-to-pdf +HTMLToPDF.title=HTML \u03C3\u03B5 PDF +HTMLToPDF.header=HTML \u03C3\u03B5 PDF +HTMLToPDF.help=\u0394\u03AD\u03C7\u03B5\u03C4\u03B1\u03B9 \u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 \u03C4\u03CD\u03C0\u03BF\u03C5 HTML \u03BA\u03B1\u03B9 \u03C4\u03CD\u03C0\u03BF\u03C5 ZIP \u03C0\u03BF\u03C5 \u03C0\u03B5\u03C1\u03B9\u03AD\u03C7\u03BF\u03C5\u03BD html/css/\u03B5\u03B9\u03BA\u03CC\u03BD\u03B5\u03C2 \u03BA.\u03BB\u03C0. \u03C0\u03BF\u03C5 \u03B1\u03C0\u03B1\u03B9\u03C4\u03BF\u03CD\u03BD\u03C4\u03B1\u03B9 +HTMLToPDF.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE +HTMLToPDF.credit=\u03A7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF WeasyPrint + + +#sanitizePDF +sanitizePDF.title=\u0391\u03C0\u03BF\u03BB\u03CD\u03BC\u03B1\u03BD\u03C3\u03B7 PDF +sanitizePDF.header=\u0391\u03C0\u03BF\u03BB\u03CD\u03BC\u03B1\u03BD\u03C3\u03B7 \u03B5\u03BD\u03CC\u03C2 PDF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 +sanitizePDF.selectText.1=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 JavaScript +sanitizePDF.selectText.2=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03B5\u03BC\u03C3\u03C9\u03BC\u03B1\u03C4\u03C9\u03BC\u03AD\u03BD\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD +sanitizePDF.selectText.3=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03BC\u03B5\u03C4\u03B1\u03B4\u03B5\u03B4\u03BF\u03BC\u03AD\u03BD\u03C9\u03BD +sanitizePDF.selectText.4=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03C3\u03C5\u03BD\u03B4\u03AD\u03C3\u03BC\u03C9\u03BD (links) +sanitizePDF.selectText.5=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03B3\u03C1\u03B1\u03BC\u03BC\u03B1\u03C4\u03BF\u03C3\u03B5\u03B9\u03C1\u03CE\u03BD +sanitizePDF.submit=\u0391\u03C0\u03BF\u03BB\u03CD\u03BC\u03B1\u03BD\u03C3\u03B7 PDF + + +#addPageNumbers +addPageNumbers.title=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03B1\u03C1\u03B9\u03B8\u03BC\u03CE\u03BD \u03C3\u03B5 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 +addPageNumbers.header=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03B1\u03C1\u03B9\u03B8\u03BC\u03CE\u03BD \u03C3\u03B5 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 +addPageNumbers.selectText.1=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE PDF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5: +addPageNumbers.selectText.2=\u039C\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03C0\u03B5\u03C1\u03B9\u03B8\u03C9\u03C1\u03AF\u03BF\u03C5 +addPageNumbers.selectText.3=\u0398\u03AD\u03C3\u03B7 +addPageNumbers.selectText.4=\u0391\u03C1\u03B9\u03B8\u03BC\u03CC\u03C2 \u03C0\u03BF\u03C5 \u03BE\u03B5\u03BA\u03B9\u03BD\u03AC \u03B7 \u03B1\u03C1\u03AF\u03B8\u03BC\u03B7\u03C3\u03B7 +addPageNumbers.selectText.5=\u03A3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 \u03C3\u03B5 \u03B1\u03C1\u03B9\u03B8\u03BC\u03CC +addPageNumbers.selectText.6=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03C3\u03BC\u03AD\u03BD\u03BF \u039A\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF +addPageNumbers.customTextDesc=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03C3\u03BC\u03AD\u03BD\u03BF \u039A\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF +addPageNumbers.numberPagesDesc=\u03A0\u03BF\u03B9\u03AD\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 \u03BD\u03B1 \u03B1\u03C1\u03B9\u03B8\u03BC\u03B7\u03B8\u03BF\u03CD\u03BD, \u03C0\u03C1\u03BF\u03B5\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE 'all' (\u03CC\u03BB\u03B5\u03C2), \u03B5\u03C0\u03AF\u03C3\u03B7\u03C2 \u03B4\u03AD\u03C7\u03B5\u03C4\u03B1\u03B9 1-5 \u03AE 2,5,9 \u03BA.\u03BB.\u03C0 +addPageNumbers.customNumberDesc=\u03A0\u03C1\u03BF\u03B5\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE \u03C3\u03B5 {n}, \u03B4\u03AD\u03C7\u03B5\u03C4\u03B1\u03B9 \u03B5\u03C0\u03AF\u03C3\u03B7\u03C2 "\u03A3\u03B5\u03BB\u03AF\u03B4\u03B1 {n} \u03B1\u03C0\u03CC {total}", "Text-{n}", "{filename}-{n} +addPageNumbers.submit=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03B1\u03C1\u03B9\u03B8\u03BC\u03CE\u03BD \u03C3\u03B5 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 + + +#auto-rename +auto-rename.title=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B7 \u03BC\u03B5\u03C4\u03BF\u03BD\u03BF\u03BC\u03B1\u03C3\u03AF\u03B1 +auto-rename.header=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B7 \u03BC\u03B5\u03C4\u03BF\u03BD\u03BF\u03BC\u03B1\u03C3\u03AF\u03B1 PDF +auto-rename.submit=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B7 \u03BC\u03B5\u03C4\u03BF\u03BD\u03BF\u03BC\u03B1\u03C3\u03AF\u03B1 + + +#adjustContrast +adjustContrast.title=\u03A0\u03C1\u03BF\u03C3\u03C0\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE \u03C4\u03B7\u03C2 \u0391\u03BD\u03C4\u03AF\u03B8\u03B5\u03C3\u03B7\u03C2 +adjustContrast.header=\u03A0\u03C1\u03BF\u03C3\u03C0\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE \u03C4\u03B7\u03C2 \u0391\u03BD\u03C4\u03AF\u03B8\u03B5\u03C3\u03B7\u03C2 +adjustContrast.contrast=\u0391\u03BD\u03C4\u03AF\u03B8\u03B5\u03C3\u03B7: +adjustContrast.brightness=\u03A6\u03C9\u03C4\u03B5\u03B9\u03BD\u03CC\u03C4\u03B7\u03C4\u03B1: +adjustContrast.saturation=\u039A\u03BF\u03C1\u03B5\u03C3\u03BC\u03CC\u03C2: +adjustContrast.download=\u039B\u03AE\u03C8\u03B7 + + +#crop +crop.title=\u039A\u03BF\u03C0\u03AE +crop.header=\u039A\u03BF\u03C0\u03AE \u0395\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 +crop.submit=\u03A5\u03C0\u03BF\u03B2\u03BF\u03BB\u03AE + + +#autoSplitPDF +autoSplitPDF.title=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B7 \u03B4\u03B9\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 PDF +autoSplitPDF.header=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B7 \u03B4\u03B9\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 PDF +autoSplitPDF.description=\u0395\u03BA\u03C4\u03C5\u03C0\u03CE\u03C3\u03C4\u03B5, \u03B5\u03B9\u03C3\u03AC\u03B3\u03B5\u03C4\u03B5, \u03C3\u03B1\u03C1\u03CE\u03C3\u03C4\u03B5, \u03B1\u03BD\u03B5\u03B2\u03AC\u03C3\u03C4\u03B5 \u03BA\u03B1\u03B9 \u03B1\u03C6\u03AE\u03C3\u03C4\u03B5 \u03BC\u03B1\u03C2 \u03BD\u03B1 \u03B4\u03B9\u03B1\u03C7\u03C9\u03C1\u03AF\u03C3\u03BF\u03C5\u03BC\u03B5 \u03B1\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B1 \u03C4\u03B1 \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03AC \u03C3\u03B1\u03C2. \u0394\u03B5\u03BD \u03B1\u03C0\u03B1\u03B9\u03C4\u03B5\u03AF\u03C4\u03B1\u03B9 \u03C7\u03B5\u03B9\u03C1\u03BF\u03BA\u03AF\u03BD\u03B7\u03C4\u03B7 \u03C4\u03B1\u03BE\u03B9\u03BD\u03CC\u03BC\u03B7\u03C3\u03B7. +autoSplitPDF.selectText.1=\u0395\u03BA\u03C4\u03C5\u03C0\u03CE\u03C3\u03C4\u03B5 \u03BC\u03B5\u03C1\u03B9\u03BA\u03AC \u03C6\u03CD\u03BB\u03BB\u03B1 \u03B4\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03BF\u03CD \u03B1\u03C0\u03CC \u03BA\u03AC\u03C4\u03C9 (\u03C4\u03BF \u03B1\u03C3\u03C0\u03C1\u03CC\u03BC\u03B1\u03C5\u03C1\u03BF \u03B5\u03AF\u03BD\u03B1\u03B9 \u03BC\u03B9\u03B1 \u03C7\u03B1\u03C1\u03AC). +autoSplitPDF.selectText.2=\u03A3\u03B1\u03C1\u03CE\u03C3\u03C4\u03B5 \u03CC\u03BB\u03B1 \u03C4\u03B1 \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03AC \u03C3\u03B1\u03C2 \u03C4\u03B1\u03C5\u03C4\u03CC\u03C7\u03C1\u03BF\u03BD\u03B1, \u03BC\u03B5 \u03C4\u03BF \u03BD\u03B1 \u03B5\u03B9\u03C3\u03AC\u03B3\u03B5\u03C4\u03B5 \u03C4\u03BF \u03B4\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03C4\u03B9\u03BA\u03CC \u03C6\u03CD\u03BB\u03BB\u03BF \u03BC\u03B5\u03C4\u03B1\u03BE\u03CD \u03C4\u03BF\u03C5\u03C2. +autoSplitPDF.selectText.3=\u0391\u03BD\u03B5\u03B2\u03AC\u03C3\u03C4\u03B5 \u03AD\u03BD\u03B1 \u03BC\u03B5\u03B3\u03AC\u03BB\u03BF \u03C3\u03B1\u03C1\u03C9\u03BC\u03AD\u03BD\u03BF PDF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF \u03BA\u03B1\u03B9 \u03B1\u03C6\u03AE\u03C3\u03C4\u03B5 \u03C4\u03BF Stirling PDF \u03BD\u03B1 \u03C7\u03B5\u03B9\u03C1\u03B9\u03C3\u03C4\u03B5\u03AF \u03C4\u03B1 \u03C5\u03C0\u03CC\u03BB\u03BF\u03B9\u03C0\u03B1 +autoSplitPDF.selectText.4=\u039F\u03B9 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 \u03B4\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03BF\u03CD \u03B5\u03BD\u03C4\u03BF\u03C0\u03AF\u03B6\u03BF\u03BD\u03C4\u03B1\u03B9 \u03BA\u03B1\u03B9 \u03B1\u03C6\u03B1\u03B9\u03C1\u03BF\u03CD\u03BD\u03C4\u03B1\u03B9 \u03B1\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B1, \u03B4\u03B9\u03B1\u03C3\u03C6\u03B1\u03BB\u03AF\u03B6\u03BF\u03BD\u03C4\u03B1\u03C2 \u03AD\u03BD\u03B1 \u03C0\u03C1\u03BF\u03C3\u03B5\u03B3\u03BC\u03AD\u03BD\u03BF \u03C4\u03B5\u03BB\u03B9\u03BA\u03CC \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF. +autoSplitPDF.formPrompt=\u03A5\u03C0\u03BF\u03B2\u03BF\u03BB\u03AE PDF \u03C0\u03BF\u03C5 \u03C0\u03B5\u03C1\u03B9\u03AD\u03C7\u03B5\u03B9 \u03B4\u03B9\u03B1\u03B9\u03C1\u03AD\u03C4\u03B5\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD Stirling-PDF: +autoSplitPDF.duplexMode=\u039B\u03B5\u03B9\u03C4\u03BF\u03C5\u03C1\u03B3\u03AF\u03B1 \u03B4\u03B9\u03C0\u03BB\u03AE\u03C2 \u03CC\u03C8\u03B7\u03C2 (\u03A3\u03AC\u03C1\u03C9\u03C3\u03B7 \u03BC\u03C0\u03C1\u03BF\u03C3\u03C4\u03AC \u03BA\u03B1\u03B9 \u03C0\u03AF\u03C3\u03C9) +autoSplitPDF.dividerDownload1=\u039B\u03AE\u03C8\u03B7 'Auto Splitter Divider (minimal).pdf' +autoSplitPDF.dividerDownload2=\u039B\u03AE\u03C8\u03B7 'Auto Splitter Divider (with instructions).pdf' +autoSplitPDF.submit=\u03A5\u03C0\u03BF\u03B2\u03BF\u03BB\u03AE + + +#pipeline +pipeline.title=Pipeline + + +#pageLayout +pageLayout.title=\u0394\u03B9\u03AC\u03C4\u03B1\u03BE\u03B7 \u03C0\u03BF\u03BB\u03BB\u03CE\u03BD \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD +pageLayout.header=\u0394\u03B9\u03AC\u03C4\u03B1\u03BE\u03B7 \u03C0\u03BF\u03BB\u03BB\u03CE\u03BD \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD +pageLayout.pagesPerSheet=\u03A3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 \u03B1\u03BD\u03AC \u03C6\u03CD\u03BB\u03BB\u03BF: +pageLayout.addBorder=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03C0\u03B5\u03C1\u03B9\u03B3\u03C1\u03B1\u03BC\u03BC\u03AC\u03C4\u03C9\u03BD +pageLayout.submit=\u03A5\u03C0\u03BF\u03B2\u03BF\u03BB\u03AE + + +#scalePages +scalePages.title=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE \u03BA\u03BB\u03AF\u03BC\u03B1\u03BA\u03B1\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 +scalePages.header=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE \u03BA\u03BB\u03AF\u03BC\u03B1\u03BA\u03B1\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 +scalePages.pageSize=\u039C\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03BC\u03B9\u03B1\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 \u03C4\u03BF\u03C5 \u03B5\u03B3\u03B3\u03C1\u03AC\u03C6\u03BF\u03C5. +scalePages.scaleFactor=\u0395\u03C0\u03AF\u03C0\u03B5\u03B4\u03BF \u03B6\u03BF\u03C5\u03BC (\u03C0\u03B5\u03C1\u03B9\u03BA\u03BF\u03C0\u03AE) \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2. +scalePages.submit=\u03A5\u03C0\u03BF\u03B2\u03BF\u03BB\u03AE + + +#certSign +certSign.title=\u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE \u03A0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03BF\u03CD +certSign.header=\u03A5\u03C0\u03BF\u03B3\u03C1\u03AC\u03C8\u03C4\u03B5 \u03AD\u03BD\u03B1 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF PDF \u03BC\u03B5 \u03C4\u03BF \u03C0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03CC \u03C3\u03B1\u03C2 (\u0395\u03C1\u03B3\u03B1\u03C3\u03AF\u03B1 \u03C3\u03B5 \u03B5\u03BE\u03AD\u03BB\u03B9\u03BE\u03B7) +certSign.selectPDF=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 PDF \u03B3\u03B9\u03B1 \u03C5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE: +certSign.selectKey=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 \u03C4\u03BF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF \u03C4\u03BF\u03C5 \u03B9\u03B4\u03B9\u03C9\u03C4\u03B9\u03BA\u03BF\u03CD \u03BA\u03BB\u03B5\u03B9\u03B4\u03B9\u03BF\u03CD \u03C3\u03B1\u03C2 (\u03BC\u03BF\u03C1\u03C6\u03AE PKCS#8, \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 .pem \u03AE .der): +certSign.selectCert=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 \u03C4\u03BF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF \u03C0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03BF\u03CD \u03C3\u03B1\u03C2 (\u03BC\u03BF\u03C1\u03C6\u03AE X.509, \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 .pem \u03AE .der): +certSign.selectP12=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 \u03C4\u03BF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF PKCS#12 Keystore (.p12 \u03AE .pfx) (\u03A0\u03C1\u03BF\u03B1\u03B9\u03C1\u03B5\u03C4\u03B9\u03BA\u03CC, \u03B5\u03AC\u03BD \u03C0\u03B1\u03C1\u03AD\u03C7\u03B5\u03C4\u03B1\u03B9, \u03B8\u03B1 \u03C0\u03C1\u03AD\u03C0\u03B5\u03B9 \u03BD\u03B1 \u03C0\u03B5\u03C1\u03B9\u03AD\u03C7\u03B5\u03B9 \u03C4\u03BF \u03B9\u03B4\u03B9\u03C9\u03C4\u03B9\u03BA\u03CC \u03BA\u03BB\u03B5\u03B9\u03B4\u03AF \u03BA\u03B1\u03B9 \u03C4\u03BF \u03C0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03CC \u03C3\u03B1\u03C2): +certSign.certType=\u03A4\u03CD\u03C0\u03BF\u03C2 \u03A0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03BF\u03CD +certSign.password=\u0395\u03B9\u03C3\u03B1\u03B3\u03AC\u03B3\u03B5\u03C4\u03B5 \u03C4\u03BF\u03BD \u03BA\u03C9\u03B4\u03B9\u03BA\u03CC \u03C4\u03BF\u03C5 Keystore \u03AE \u03C4\u03BF\u03C5 \u0399\u03B4\u03B9\u03C9\u03C4\u03B9\u03BA\u03BF\u03CD \u039A\u03BB\u03B5\u03B9\u03B4\u03B9\u03BF\u03CD (\u03B5\u03AC\u03BD \u03C5\u03C0\u03AC\u03C1\u03C7\u03B5\u03B9): +certSign.showSig=\u0395\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 \u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03C2 +certSign.reason=\u0391\u03B9\u03C4\u03AF\u03B1 +certSign.location=\u03A4\u03BF\u03C0\u03BF\u03B8\u03B5\u03C3\u03AF\u03B1 +certSign.name=\u038C\u03BD\u03BF\u03BC\u03B1 +certSign.submit=\u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE PDF + + +#removeBlanks +removeBlanks.title=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u039A\u03B5\u03BD\u03CE\u03BD +removeBlanks.header=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u039A\u03B5\u03BD\u03CE\u03BD \u03A3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD +removeBlanks.threshold=\u038C\u03C1\u03B9\u03BF \u03BB\u03B5\u03C5\u03BA\u03CC\u03C4\u03B7\u03C4\u03B1\u03C2 pixel: +removeBlanks.thresholdDesc=\u038C\u03C1\u03B9\u03BF \u03B3\u03B9\u03B1 \u03C4\u03BF\u03BD \u03C0\u03C1\u03BF\u03C3\u03B4\u03B9\u03BF\u03C1\u03B9\u03C3\u03BC\u03CC \u03C4\u03BF\u03C5 \u03C0\u03CC\u03C3\u03BF \u03BB\u03B5\u03C5\u03BA\u03CC \u03C0\u03C1\u03AD\u03C0\u03B5\u03B9 \u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03AD\u03BD\u03B1 \u03BB\u03B5\u03C5\u03BA\u03CC \u03B5\u03B9\u03BA\u03BF\u03BD\u03BF\u03C3\u03C4\u03BF\u03B9\u03C7\u03B5\u03AF\u03BF (pixel) \u03B3\u03B9\u03B1 \u03BD\u03B1 \u03C4\u03B1\u03BE\u03B9\u03BD\u03BF\u03BC\u03B7\u03B8\u03B5\u03AF \u03C9\u03C2 "\u039B\u03B5\u03C5\u03BA\u03CC". 0 = \u039C\u03B1\u03CD\u03C1\u03BF, 255 \u03BA\u03B1\u03B8\u03B1\u03C1\u03CC \u03BB\u03B5\u03C5\u03BA\u03CC. +removeBlanks.whitePercent=\u03A0\u03BF\u03C3\u03BF\u03C3\u03C4\u03CC \u039B\u03B5\u03C5\u03BA\u03BF\u03CD (%): +removeBlanks.whitePercentDesc=\u03A4\u03BF \u03C0\u03BF\u03C3\u03BF\u03C3\u03C4\u03CC \u03C4\u03B7\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 \u03C0\u03BF\u03C5 \u03C0\u03C1\u03AD\u03C0\u03B5\u03B9 \u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 "\u03BB\u03B5\u03C5\u03BA\u03AC" pixel \u03B3\u03B9\u03B1 \u03BD\u03B1 \u03B1\u03C6\u03B1\u03B9\u03C1\u03B5\u03B8\u03B5\u03AF +removeBlanks.submit=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u039A\u03B5\u03BD\u03CE\u03BD + + +#compare +compare.title=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7 +compare.header=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7 PDFs +compare.document.1=\u0388\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF 1 +compare.document.2=\u0388\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF 2 +compare.submit=\u03A3\u03CD\u03B3\u03BA\u03C1\u03B9\u03C3\u03B7 + + +#sign +sign.title=\u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE +sign.header=\u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE PDFs +sign.upload=\u0391\u03BD\u03AD\u03B2\u03B1\u03C3\u03BC\u03B1 \u0395\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 +sign.draw=\u03A3\u03C7\u03B5\u03B4\u03AF\u03B1\u03C3\u03B7 \u03C5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03C2 +sign.text=\u0395\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AE \u03BA\u03B5\u03B9\u03BC\u03AD\u03BD\u03BF\u03C5 +sign.clear=\u039A\u03B1\u03B8\u03AC\u03C1\u03B9\u03C3\u03BC\u03B1 +sign.add=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 + + +#repair +repair.title=\u0395\u03C0\u03B9\u03B4\u03B9\u03CC\u03C1\u03B8\u03C9\u03C3\u03B7 +repair.header=\u0395\u03C0\u03B9\u03B4\u03B9\u03CC\u03C1\u03B8\u03C9\u03C3\u03B7 PDFs +repair.submit=\u0395\u03C0\u03B9\u03B4\u03B9\u03CC\u03C1\u03B8\u03C9\u03C3\u03B7 + + +#flatten +flatten.title=Flatten +flatten.header=Flatten PDFs +flatten.submit=Flatten + + +#ScannerImageSplit +ScannerImageSplit.selectText.1=\u038C\u03C1\u03B9\u03BF \u03B3\u03C9\u03BD\u03AF\u03B1\u03C2: +ScannerImageSplit.selectText.2=\u039F\u03C1\u03AF\u03B6\u03B5\u03B9 \u03C4\u03B7\u03BD \u03B5\u03BB\u03AC\u03C7\u03B9\u03C3\u03C4\u03B7 \u03B1\u03C0\u03CC\u03BB\u03C5\u03C4\u03B7 \u03B3\u03C9\u03BD\u03AF\u03B1 \u03C0\u03BF\u03C5 \u03B1\u03C0\u03B1\u03B9\u03C4\u03B5\u03AF\u03C4\u03B1\u03B9 \u03B3\u03B9\u03B1 \u03C4\u03B7\u03BD \u03C0\u03B5\u03C1\u03B9\u03C3\u03C4\u03C1\u03BF\u03C6\u03AE \u03C4\u03B7\u03C2 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 (\u03C0\u03C1\u03BF\u03B5\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE: 10). +ScannerImageSplit.selectText.3=\u0391\u03BD\u03B5\u03BA\u03C4\u03B9\u03BA\u03CC\u03C4\u03B7\u03C4\u03B1 (Tolerance): +ScannerImageSplit.selectText.4=\u039A\u03B1\u03B8\u03BF\u03C1\u03AF\u03B6\u03B5\u03B9 \u03C4\u03BF \u03B5\u03CD\u03C1\u03BF\u03C2 \u03C4\u03B7\u03C2 \u03C7\u03C1\u03C9\u03BC\u03B1\u03C4\u03B9\u03BA\u03AE\u03C2 \u03B4\u03B9\u03B1\u03BA\u03CD\u03BC\u03B1\u03BD\u03C3\u03B7\u03C2 \u03B3\u03CD\u03C1\u03C9 \u03B1\u03C0\u03CC \u03C4\u03BF \u03B5\u03BA\u03C4\u03B9\u03BC\u03CE\u03BC\u03B5\u03BD\u03BF \u03C7\u03C1\u03CE\u03BC\u03B1 \u03C6\u03CC\u03BD\u03C4\u03BF\u03C5 (\u03C0\u03C1\u03BF\u03B5\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE: 30). +ScannerImageSplit.selectText.5=\u0395\u03BB\u03AC\u03C7\u03B9\u03C3\u03C4\u03B7 \u03A0\u03B5\u03C1\u03B9\u03BF\u03C7\u03AE: +ScannerImageSplit.selectText.6=\u039F\u03C1\u03AF\u03B6\u03B5\u03B9 \u03C4\u03BF \u03B5\u03BB\u03AC\u03C7\u03B9\u03C3\u03C4\u03BF \u03CC\u03C1\u03B9\u03BF \u03B5\u03C0\u03B9\u03C6\u03AC\u03BD\u03B5\u03B9\u03B1\u03C2 \u03B3\u03B9\u03B1 \u03BC\u03B9\u03B1 \u03C6\u03C9\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AF\u03B1 (\u03C0\u03C1\u03BF\u03B5\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE: 10000). +ScannerImageSplit.selectText.7=\u0395\u03BB\u03AC\u03C7\u03B9\u03C3\u03C4\u03B7 \u03B5\u03C0\u03B9\u03C6\u03AC\u03BD\u03B5\u03B9\u03B1 \u03C0\u03B5\u03C1\u03B9\u03B3\u03C1\u03AC\u03BC\u03BC\u03B1\u03C4\u03BF\u03C2: +ScannerImageSplit.selectText.8=\u03A1\u03C5\u03B8\u03BC\u03AF\u03B6\u03B5\u03B9 \u03C4\u03BF \u03B5\u03BB\u03AC\u03C7\u03B9\u03C3\u03C4\u03BF \u03CC\u03C1\u03B9\u03BF \u03C0\u03B5\u03C1\u03B9\u03B3\u03C1\u03AC\u03BC\u03BC\u03B1\u03C4\u03BF\u03C2 \u03B3\u03B9\u03B1 \u03BC\u03B9\u03B1 \u03C6\u03C9\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AF\u03B1 +ScannerImageSplit.selectText.9=\u039C\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03C0\u03B5\u03C1\u03B9\u03B3\u03C1\u03AC\u03BC\u03BC\u03B1\u03C4\u03BF\u03C2: +ScannerImageSplit.selectText.10=\u039F\u03C1\u03AF\u03B6\u03B5\u03B9 \u03C4\u03BF \u03BC\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03C4\u03BF\u03C5 \u03C0\u03B5\u03C1\u03B9\u03B3\u03C1\u03AC\u03BC\u03BC\u03B1\u03C4\u03BF\u03C2 \u03C0\u03BF\u03C5 \u03C0\u03C1\u03BF\u03C3\u03C4\u03AF\u03B8\u03B5\u03C4\u03B1\u03B9 \u03BA\u03B1\u03B9 \u03B1\u03C6\u03B1\u03B9\u03C1\u03B5\u03AF\u03C4\u03B1\u03B9 \u03B3\u03B9\u03B1 \u03BD\u03B1 \u03B1\u03C0\u03BF\u03C4\u03C1\u03AD\u03C0\u03BF\u03BD\u03C4\u03B1\u03B9 \u03BB\u03B5\u03C5\u03BA\u03AC \u03C0\u03B5\u03C1\u03B9\u03B3\u03C1\u03AC\u03BC\u03BC\u03B1\u03C4\u03B1 \u03C3\u03C4\u03B7\u03BD \u03AD\u03BE\u03BF\u03B4\u03BF (\u03C0\u03C1\u03BF\u03B5\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE: 1). + + +#OCR +ocr.title=\u039F\u03C0\u03C4\u03B9\u03BA\u03AE \u03B1\u03BD\u03B1\u03B3\u03BD\u03CE\u03C1\u03B9\u03C3\u03B7 \u03C7\u03B1\u03C1\u03B1\u03BA\u03C4\u03AE\u03C1\u03C9\u03BD (OCR) / \u03A3\u03B1\u03C1\u03CE\u03C3\u03B5\u03B9\u03C2 Cleanup +ocr.header=\u03A3\u03B1\u03C1\u03CE\u03C3\u03B5\u03B9\u03C2 Cleanup / OCR (Optical Character Recognition - \u039F\u03C0\u03C4\u03B9\u03BA\u03AE \u03B1\u03BD\u03B1\u03B3\u03BD\u03CE\u03C1\u03B9\u03C3\u03B7 \u03C7\u03B1\u03C1\u03B1\u03BA\u03C4\u03AE\u03C1\u03C9\u03BD) +ocr.selectText.1=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 \u03B3\u03BB\u03CE\u03C3\u03C3\u03B5\u03C2 \u03C0\u03BF\u03C5 \u03C0\u03C1\u03CC\u03BA\u03B5\u03B9\u03C4\u03B1\u03B9 \u03BD\u03B1 \u03B5\u03BD\u03C4\u03BF\u03C0\u03B9\u03C3\u03C4\u03BF\u03CD\u03BD \u03C3\u03C4\u03BF PDF (\u039F\u03B9 \u03C0\u03BF\u03C5 \u03B1\u03BD\u03B1\u03C6\u03AD\u03C1\u03BF\u03BD\u03C4\u03B1\u03B9 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03B1\u03C5\u03C4\u03AD\u03C2 \u03C0\u03BF\u03C5 \u03B1\u03BD\u03B9\u03C7\u03BD\u03B5\u03CD\u03BF\u03BD\u03C4\u03B1\u03B9 \u03B1\u03C5\u03C4\u03AE\u03BD \u03C4\u03B7 \u03C3\u03C4\u03B9\u03B3\u03BC\u03AE): +ocr.selectText.2=\u0394\u03B7\u03BC\u03B9\u03BF\u03C5\u03C1\u03B3\u03AE\u03C3\u03C4\u03B5 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF \u03BA\u03B5\u03B9\u03BC\u03AD\u03BD\u03BF\u03C5 \u03C0\u03BF\u03C5 \u03C0\u03B5\u03C1\u03B9\u03AD\u03C7\u03B5\u03B9 \u03BA\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF OCR \u03BC\u03B1\u03B6\u03AF \u03BC\u03B5 \u03C4\u03BF PDF \u03C0\u03BF\u03C5 \u03AD\u03C7\u03B5\u03B9 \u03C5\u03C0\u03BF\u03B2\u03BB\u03B7\u03B8\u03B5\u03AF \u03C3\u03B5 OCR +ocr.selectText.3=\u039F\u03B9 \u03C3\u03C9\u03C3\u03C4\u03AD\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 \u03C3\u03B1\u03C1\u03CE\u03B8\u03B7\u03BA\u03B1\u03BD \u03BC\u03B5 \u03BB\u03BF\u03BE\u03AE \u03B3\u03C9\u03BD\u03AF\u03B1 \u03C0\u03B5\u03C1\u03B9\u03C3\u03C4\u03C1\u03AD\u03C6\u03BF\u03BD\u03C4\u03AC\u03C2 \u03C4\u03B5\u03C2 \u03C3\u03C4\u03B7 \u03B8\u03AD\u03C3\u03B7 \u03C4\u03BF\u03C5\u03C2 +ocr.selectText.4=C\u039A\u03B1\u03B8\u03B1\u03C1\u03AF\u03C3\u03C4\u03B5 \u03C4\u03B7 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1, \u03CE\u03C3\u03C4\u03B5 \u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03BB\u03B9\u03B3\u03CC\u03C4\u03B5\u03C1\u03BF \u03C0\u03B9\u03B8\u03B1\u03BD\u03CC \u03C4\u03BF OCR \u03BD\u03B1 \u03B2\u03C1\u03B5\u03B9 \u03BA\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF \u03C3\u03C4\u03BF \u03B8\u03CC\u03C1\u03C5\u03B2\u03BF \u03C6\u03CC\u03BD\u03C4\u03BF\u03C5 (background noise). (\u039A\u03B1\u03BC\u03AF\u03B1 \u03B1\u03BB\u03BB\u03B1\u03B3\u03AE \u03C3\u03C4\u03BF \u03B5\u03BE\u03B1\u03B3\u03CC\u03BC\u03B5\u03BD\u03BF) +ocr.selectText.5=\u039A\u03B1\u03B8\u03B1\u03C1\u03AF\u03C3\u03C4\u03B5 \u03C4\u03B7 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1, \u03CE\u03C3\u03C4\u03B5 \u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03BB\u03B9\u03B3\u03CC\u03C4\u03B5\u03C1\u03BF \u03C0\u03B9\u03B8\u03B1\u03BD\u03CC \u03C4\u03BF OCR \u03BD\u03B1 \u03B2\u03C1\u03B5\u03B9 \u03BA\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF \u03C3\u03C4\u03BF \u03B8\u03CC\u03C1\u03C5\u03B2\u03BF \u03C6\u03CC\u03BD\u03C4\u03BF\u03C5, \u03B4\u03B9\u03B1\u03C4\u03B7\u03C1\u03B5\u03AF \u03C4\u03B7\u03BD \u03B5\u03BA\u03BA\u03B1\u03B8\u03AC\u03C1\u03B9\u03C3\u03B7 \u03C3\u03C4\u03BF \u03C0\u03B1\u03C1\u03B1\u03B3\u03CC\u03BC\u03B5\u03BD\u03BF. +ocr.selectText.6=\u0391\u03B3\u03BD\u03BF\u03B5\u03AF \u03C4\u03B9\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 \u03C0\u03BF\u03C5 \u03AD\u03C7\u03BF\u03C5\u03BD \u03B4\u03B9\u03B1\u03B4\u03C1\u03B1\u03C3\u03C4\u03B9\u03BA\u03CC \u03BA\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF, \u03BC\u03CC\u03BD\u03BF \u03C4\u03B9\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 OCR \u03C0\u03BF\u03C5 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B5\u03C2 +ocr.selectText.7=\u0395\u03C0\u03B9\u03B2\u03BF\u03BB\u03AE OCR, \u03B8\u03B1 \u03C0\u03C1\u03B1\u03B3\u03BC\u03B1\u03C4\u03BF\u03C0\u03BF\u03B9\u03AE\u03C3\u03B5\u03B9 \u039F\u03C0\u03C4\u03B9\u03BA\u03AE \u03B1\u03BD\u03B1\u03B3\u03BD\u03CE\u03C1\u03B9\u03C3\u03B7 \u03C7\u03B1\u03C1\u03B1\u03BA\u03C4\u03AE\u03C1\u03C9\u03BD \u03C3\u03B5 \u03BA\u03AC\u03B8\u03B5 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1 \u03B1\u03C6\u03B1\u03B9\u03C1\u03CE\u03BD\u03C4\u03B1\u03C2 \u03CC\u03BB\u03BF \u03C4\u03B1 \u03C3\u03C4\u03BF\u03B9\u03C7\u03B5\u03AF\u03B1 \u03C4\u03BF\u03C5 \u03B1\u03C1\u03C7\u03B9\u03BA\u03BF\u03CD \u03BA\u03B5\u03AF\u03BC\u03AD\u03BD\u03BF\u03C5 +ocr.selectText.8=\u039A\u03B1\u03BD\u03BF\u03BD\u03B9\u03BA\u03CC (\u0398\u03B1 \u03C0\u03B1\u03C1\u03AC\u03BE\u03B5\u03B9 \u03C3\u03C6\u03AC\u03BB\u03BC\u03B1 \u03B1\u03BD \u03C4\u03BF PDF \u03C0\u03B5\u03C1\u03B9\u03AD\u03C7\u03B5\u03B9 \u03BA\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF) +ocr.selectText.9=\u0395\u03C0\u03B9\u03C0\u03C1\u03CC\u03C3\u03B8\u03B5\u03C4\u03B5\u03C2 \u03A1\u03C5\u03B8\u03BC\u03AF\u03C3\u03B5\u03B9\u03C2 +ocr.selectText.10=\u039B\u03B5\u03B9\u03C4\u03BF\u03C5\u03C1\u03B3\u03AF\u03B1 OCR +ocr.selectText.11=\u039A\u03B1\u03C4\u03AC\u03C1\u03B3\u03B7\u03C3\u03B7 \u03B5\u03B9\u03BA\u03CC\u03BD\u03C9\u03BD \u03BC\u03B5\u03C4\u03AC \u03C4\u03BF OCR (\u039A\u03B1\u03C4\u03B1\u03C1\u03B3\u03B5\u03AF \u039F\u039B\u0395\u03A3 \u03C4\u03B9\u03C2 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B5\u03C2, \u03B5\u03AF\u03BD\u03B1\u03B9 \u03C7\u03C1\u03AE\u03C3\u03B9\u03BC\u03BF \u03BC\u03CC\u03BD\u03BF \u03B1\u03BD \u03B1\u03C0\u03BF\u03C4\u03B5\u03BB\u03B5\u03AF \u03BC\u03AD\u03C1\u03BF\u03C2 \u03C4\u03BF\u03C5 \u03B2\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2 \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE\u03C2) +ocr.selectText.12=\u03A4\u03CD\u03C0\u03BF\u03C2 \u03B1\u03C0\u03CC\u03B4\u03BF\u03C3\u03B7\u03C2 (\u0393\u03B9\u03B1 \u03C0\u03C1\u03BF\u03C7\u03C9\u03C1\u03B7\u03BC\u03AD\u03BD\u03BF\u03C5\u03C2) +ocr.help=\u0394\u03B9\u03B1\u03B2\u03AC\u03C3\u03C4\u03B5 \u03B1\u03C5\u03C4\u03AE\u03BD \u03C4\u03B7\u03BD \u03C4\u03B5\u03BA\u03BC\u03B7\u03C1\u03AF\u03C9\u03C3\u03B7 \u03C3\u03C7\u03B5\u03C4\u03B9\u03BA\u03AC \u03BC\u03B5 \u03C4\u03BF\u03BD \u03C4\u03C1\u03CC\u03C0\u03BF \u03C7\u03C1\u03AE\u03C3\u03B7\u03C2 \u03B1\u03C5\u03C4\u03AE\u03C2 \u03B3\u03B9\u03B1 \u03AC\u03BB\u03BB\u03B5\u03C2 \u03B3\u03BB\u03CE\u03C3\u03C3\u03B5\u03C2 \u03AE/\u03BA\u03B1\u03B9 \u03BC\u03B7 \u03C7\u03C1\u03AE\u03C3\u03B7\u03C2 \u03C3\u03B5 docker +ocr.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF OCRmyPDF \u03BA\u03B1\u03B9 Tesseract \u03B3\u03B9\u03B1 OCR. +ocr.submit=\u0395\u03C0\u03B5\u03BE\u03B5\u03C1\u03B3\u03B1\u03C3\u03AF\u03B1 PDF \u03BC\u03B5 OCR + + +#extractImages +extractImages.title=\u0395\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE \u0395\u03B9\u03BA\u03CC\u03BD\u03C9\u03BD +extractImages.header=\u0395\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE \u0395\u03B9\u03BA\u03CC\u03BD\u03C9\u03BD +extractImages.selectText=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 \u03BC\u03BF\u03C1\u03C6\u03AE \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 \u03B3\u03B9\u03B1 \u03BD\u03B1 \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03AD\u03C8\u03B5\u03C4\u03B5 \u03C4\u03B9\u03C2 \u03B5\u03BE\u03B1\u03B3\u03CC\u03BC\u03B5\u03BD\u03B5\u03C2 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B5\u03C2 +extractImages.submit=\u0395\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE + + +#File to PDF +fileToPDF.title=\u0391\u03C1\u03C7\u03B5\u03AF\u03BF \u03C3\u03B5 PDF +fileToPDF.header=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03BF\u03C0\u03BF\u03B9\u03BF\u03C5\u03B4\u03AE\u03C0\u03BF\u03C4\u03B5 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 \u03C3\u03B5 PDF +fileToPDF.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF LibreOffice \u03BA\u03B1\u03B9 Unoconv \u03B3\u03B9\u03B1 \u03C4\u03B7\u03BD \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD. +fileToPDF.supportedFileTypes=\u039F\u03B9 \u03C5\u03C0\u03BF\u03C3\u03C4\u03B7\u03C1\u03B9\u03B6\u03CC\u03BC\u03B5\u03BD\u03BF\u03B9 \u03C4\u03CD\u03C0\u03BF\u03B9 \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD \u03B8\u03B1 \u03C0\u03C1\u03AD\u03C0\u03B5\u03B9 \u03BD\u03B1 \u03C0\u03B5\u03C1\u03B9\u03BB\u03B1\u03BC\u03B2\u03AC\u03BD\u03BF\u03C5\u03BD \u03C4\u03B1 \u03C0\u03B1\u03C1\u03B1\u03BA\u03AC\u03C4\u03C9, \u03C9\u03C3\u03C4\u03CC\u03C3\u03BF, \u03B3\u03B9\u03B1 \u03BC\u03B9\u03B1 \u03C0\u03BB\u03AE\u03C1\u03B7 \u03B5\u03BD\u03B7\u03BC\u03B5\u03C1\u03C9\u03BC\u03AD\u03BD\u03B7 \u03BB\u03AF\u03C3\u03C4\u03B1 \u03BC\u03B5 \u03C4\u03B9\u03C2 \u03C5\u03C0\u03BF\u03C3\u03C4\u03B7\u03C1\u03B9\u03B6\u03CC\u03BC\u03B5\u03BD\u03B5\u03C2 \u03BC\u03BF\u03C1\u03C6\u03AD\u03C2, \u03B1\u03BD\u03B1\u03C4\u03C1\u03AD\u03BE\u03C4\u03B5 \u03C3\u03C4\u03B7\u03BD \u03C4\u03B5\u03BA\u03BC\u03B7\u03C1\u03AF\u03C9\u03C3\u03B7 \u03C4\u03BF\u03C5 LibreOffice +fileToPDF.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C3\u03B5 PDF + + +#compress +compress.title=\u03A3\u03C5\u03BC\u03C0\u03AF\u03B5\u03C3\u03B7 +compress.header=\u03A3\u03C5\u03BC\u03C0\u03AF\u03B5\u03C3\u03B7 PDF +compress.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF Ghostscript \u03B3\u03B9\u03B1 PDF \u03A3\u03C5\u03BC\u03C0\u03AF\u03B5\u03C3\u03B7/\u0392\u03B5\u03BB\u03C4\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03AF\u03B7\u03C3\u03B7. +compress.selectText.1=\u03A7\u03B5\u03B9\u03C1\u03BF\u03BA\u03AF\u03BD\u03B7\u03C4\u03B7 \u039B\u03B5\u03B9\u03C4\u03BF\u03C5\u03C1\u03B3\u03AF\u03B1 - \u0391\u03C0\u03CC 1 \u03AD\u03C9\u03C2 4 +compress.selectText.2=\u0395\u03C0\u03AF\u03C0\u03B5\u03B4\u03BF \u0392\u03B5\u03BB\u03C4\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03AF\u03B7\u03C3\u03B7\u03C2: +compress.selectText.3=4 (\u03A0\u03BF\u03BB\u03CD \u03BA\u03B1\u03BA\u03CC \u03B3\u03B9\u03B1 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B5\u03C2 \u03BA\u03B5\u03B9\u03BC\u03AD\u03BD\u03BF\u03C5) +compress.selectText.4=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B7 \u039B\u03B5\u03B9\u03C4\u03BF\u03C5\u03C1\u03B3\u03AF\u03B1 - Auto mode - \u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03CC\u03B6\u03B5\u03B9 \u03B1\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B1 \u03C4\u03B7\u03BD \u03C0\u03BF\u03B9\u03CC\u03C4\u03B7\u03C4\u03B1 \u03B3\u03B9\u03B1 \u03BB\u03AE\u03C8\u03B7 PDF \u03C3\u03C4\u03BF \u03B1\u03BA\u03C1\u03B9\u03B2\u03AD\u03C2 \u03BC\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 +compress.selectText.5=\u0391\u03BD\u03B1\u03BC\u03B5\u03BD\u03CC\u03BC\u03B5\u03BD\u03BF \u039C\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 PDF (\u03C0.\u03C7 25MB, 10.8MB, 25KB) +compress.submit=\u03A3\u03C5\u03BC\u03C0\u03AF\u03B5\u03C3\u03B7 + + +#Add image +addImage.title=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u0395\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 +addImage.header=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u0395\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 \u03C3\u03B5 PDF +addImage.everyPage=\u039A\u03AC\u03B8\u03B5 \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1? +addImage.upload=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u0395\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 +addImage.submit=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u0395\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 + + +#merge +merge.title=\u03A3\u03C5\u03B3\u03C7\u03CE\u03BD\u03B5\u03C5\u03C3\u03B7 +merge.header=\u03A3\u03C5\u03B3\u03C7\u03CE\u03BD\u03B5\u03C5\u03C3\u03B7 \u03C0\u03BF\u03BB\u03BB\u03B1\u03C0\u03BB\u03CE\u03BD PDFs (2+) +merge.sortByName=\u03A4\u03B1\u03BE\u03B9\u03BD\u03CC\u03BC\u03B7\u03C3\u03B7 \u03BC\u03B5 \u03B2\u03AC\u03C3\u03B7 \u03C4\u03BF \u038C\u03BD\u03BF\u03BC\u03B1 +merge.sortByDate=\u03A4\u03B1\u03BE\u03B9\u03BD\u03CC\u03BC\u03B7\u03C3\u03B7 \u03BC\u03B5 \u03B2\u03AC\u03C3\u03B7 \u03C4\u03B7\u03BD \u0397\u03BC\u03B5\u03C1\u03BF\u03BC\u03B7\u03BD\u03AF\u03B1 +merge.submit=\u03A3\u03C5\u03B3\u03C7\u03CE\u03BD\u03B5\u03C5\u03C3\u03B7 + + +#pdfOrganiser +pdfOrganiser.title=\u0394\u03B9\u03BF\u03C1\u03B3\u03B1\u03BD\u03C9\u03C4\u03AE\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 +pdfOrganiser.header=\u0394\u03B9\u03BF\u03C1\u03B3\u03B1\u03BD\u03C9\u03C4\u03AE\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 PDF +pdfOrganiser.submit=\u0391\u03BD\u03B1\u03B4\u03B9\u03AC\u03C4\u03B1\u03BE\u03B7 \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD + + +#multiTool +multiTool.title=PDF \u03A0\u03BF\u03BB\u03C5\u03B5\u03C1\u03B3\u03B1\u03BB\u03B5\u03AF\u03BF +multiTool.header=PDF \u03A0\u03BF\u03BB\u03C5\u03B5\u03C1\u03B3\u03B1\u03BB\u03B5\u03AF\u03BF + + +#pageRemover +pageRemover.title=\u0391\u03C6\u03B1\u03B9\u03C1\u03B5\u03C4\u03AE\u03C2 \u03A3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD +pageRemover.header=\u0391\u03C6\u03B1\u03B9\u03C1\u03B5\u03C4\u03AE\u03C2 \u03A3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD PDF +pageRemover.pagesToDelete=\u03A3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 \u03C0\u03C1\u03BF\u03C2 \u03B4\u03B9\u03B1\u03B3\u03C1\u03B1\u03C6\u03AE (\u0395\u03B9\u03C3\u03B1\u03B3\u03AC\u03B3\u03B5\u03C4\u03B5 \u03BC\u03B9\u03B1 \u03BB\u03AF\u03C3\u03C4\u03B1 \u03BC\u03B5 \u03B1\u03C1\u03B9\u03B8\u03BC\u03BF\u03CD\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD \u03B4\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03AD\u03BD\u03C9\u03BD \u03BC\u03B5 \u03BA\u03CC\u03BC\u03BC\u03B1\u03C4\u03B1): +pageRemover.submit=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u03A3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD + + +#rotate +rotate.title=\u03A0\u03B5\u03C1\u03B9\u03C3\u03C4\u03C1\u03BF\u03C6\u03AE PDF +rotate.header=\u03A0\u03B5\u03C1\u03B9\u03C3\u03C4\u03C1\u03BF\u03C6\u03AE PDF +rotate.\u03A0\u03B5\u03C1\u03B9\u03C3\u03C4\u03C1\u03BF\u03C6\u03AE=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE \u03B3\u03C9\u03BD\u03AF\u03B1\u03C2 \u03C0\u03B5\u03C1\u03B9\u03C3\u03C4\u03C1\u03BF\u03C6\u03AE\u03C2 (\u03C3\u03B5 \u03C0\u03BF\u03BB\u03BB\u03B1\u03C0\u03BB\u03AC\u03C3\u03B9\u03BF \u03C4\u03C9\u03BD 90 \u03BC\u03BF\u03B9\u03C1\u03CE\u03BD): +rotate.submit=Rotate + + +#merge +split.title=\u0394\u03B9\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 PDF +split.header=\u0394\u03B9\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 PDF +split.desc.1=\u039F\u03B9 \u03B1\u03C1\u03B9\u03B8\u03BC\u03BF\u03AF \u03C0\u03BF\u03C5 \u03B5\u03C0\u03B9\u03BB\u03AD\u03B3\u03B5\u03C4\u03B5 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03BF \u03B1\u03C1\u03B9\u03B8\u03BC\u03CC\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 \u03C3\u03C4\u03BF\u03BD \u03BF\u03C0\u03BF\u03AF\u03BF \u03B8\u03AD\u03BB\u03B5\u03C4\u03B5 \u03BD\u03B1 \u03BA\u03AC\u03BD\u03B5\u03C4\u03B5 \u03B4\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03CC +split.desc.2=\u03A9\u03C2 \u03B5\u03BA \u03C4\u03BF\u03CD\u03C4\u03BF\u03C5, \u03B7 \u03B5\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE 1,3,7-8 \u03B8\u03B1 \u03C7\u03C9\u03C1\u03AF\u03C3\u03B5\u03B9 \u03AD\u03BD\u03B1 \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF 10 \u03C3\u03B5\u03BB\u03AF\u03B4\u03C9\u03BD \u03C3\u03B5 6 \u03BE\u03B5\u03C7\u03C9\u03C1\u03B9\u03C3\u03C4\u03AC \u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 PDF \u03BC\u03B5: +split.desc.3=\u0388\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF #1: \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1 1 +split.desc.4=\u0388\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF #2: \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1 2 \u03BA\u03B1\u03B9 3 +split.desc.5=\u0388\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF #3: \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1 4, 5 \u03BA\u03C3\u03B9 6 +split.desc.6=\u0388\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF #4: \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1 7 +split.desc.7=\u0388\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF #5: \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1 8 +split.desc.8=\u0388\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF #6: \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1 9 \u03BA\u03B1\u03B9 10 +split.splitPages=\u0395\u03B9\u03C3\u03B1\u03B3\u03AC\u03B3\u03B5\u03C4\u03B5 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 \u03B3\u03B9\u03B1 \u03B4\u03B9\u03B1\u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03CC: +split.submit=\u0394\u03B9\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 + + +#merge +imageToPDF.title=\u0395\u03B9\u03BA\u03CC\u03BD\u03B1 \u03C3\u03B5 PDF +imageToPDF.header=\u0395\u03B9\u03BA\u03CC\u03BD\u03B1 \u03C3\u03B5 PDF +imageToPDF.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE +imageToPDF.selectLabel=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AD\u03C2 \u03C0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE\u03C2 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 +imageToPDF.fillPage=\u0393\u03AD\u03BC\u03B9\u03C3\u03BC\u03B1 \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 +imageToPDF.fitDocumentToImage=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 \u03C3\u03B5 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1 +imageToPDF.maintainAspectRatio=\u0394\u03B9\u03B1\u03C4\u03AE\u03C1\u03B7\u03C3\u03B7 \u03B1\u03BD\u03B1\u03BB\u03BF\u03B3\u03B9\u03CE\u03BD \u03B4\u03B9\u03B1\u03C3\u03C4\u03AC\u03C3\u03B5\u03C9\u03BD +imageToPDF.selectText.2=\u0391\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B7 \u03C0\u03B5\u03C1\u03B9\u03C3\u03C4\u03C1\u03BF\u03C6\u03AE PDF +imageToPDF.selectText.3=\u039B\u03BF\u03B3\u03B9\u03BA\u03AE \u03C0\u03BF\u03BB\u03BB\u03CE\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD (\u0395\u03BD\u03B5\u03C1\u03B3\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF\u03C4\u03B1\u03B9 \u03BC\u03CC\u03BD\u03BF \u03B5\u03AC\u03BD \u03B5\u03C1\u03B3\u03AC\u03B6\u03B5\u03C3\u03C4\u03B5 \u03BC\u03B5 \u03C0\u03BF\u03BB\u03BB\u03AD\u03C2 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B5\u03C2) +imageToPDF.selectText.4=\u03A3\u03C5\u03B3\u03C7\u03CE\u03BD\u03B5\u03C5\u03C3\u03B7 \u03C3\u03B5 \u03AD\u03BD\u03B1 PDF +imageToPDF.selectText.5=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C3\u03B5 \u03BE\u03B5\u03C7\u03C9\u03C1\u03B9\u03C3\u03C4\u03AC \u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 PDF + + +#pdfToImage +pdfToImage.title=PDF \u03C3\u03B5 \u0395\u03B9\u03BA\u03CC\u03BD\u03B1 +pdfToImage.header=PDF \u03C3\u03B5 \u0395\u03B9\u03BA\u03CC\u03BD\u03B1 +pdfToImage.selectText=\u039C\u03BF\u03C1\u03C6\u03BF\u03C0\u03BF\u03AF\u03B7\u03C3\u03B7 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1\u03C2 +pdfToImage.singleOrMultiple=\u03A4\u03CD\u03C0\u03BF\u03C2 \u03B1\u03C0\u03BF\u03C4\u03B5\u03BB\u03AD\u03C3\u03BC\u03B1\u03C4\u03BF\u03C2 \u03B1\u03C0\u03CC \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1 \u03C3\u03B5 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1 +pdfToImage.single=\u0395\u03BD\u03B9\u03B1\u03AF\u03B1 \u03BC\u03B5\u03B3\u03AC\u03BB\u03B7 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1 \u03C0\u03BF\u03C5 \u03C3\u03C5\u03BD\u03B4\u03C5\u03AC\u03B6\u03B5\u03B9 \u03CC\u03BB\u03B5\u03C2 \u03C4\u03B9\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B5\u03C2 +pdfToImage.multi=\u03A0\u03BF\u03BB\u03BB\u03B1\u03C0\u03BB\u03AD\u03C2 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B5\u03C2, \u03BC\u03AF\u03B1 \u03B5\u03B9\u03BA\u03CC\u03BD\u03B1 \u03B1\u03BD\u03AC \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1 +pdfToImage.colorType=\u03A4\u03CD\u03C0\u03BF\u03C2 \u03A7\u03C1\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2 +pdfToImage.color=\u03A7\u03C1\u03CE\u03BC\u03B1 +pdfToImage.grey=\u039A\u03BB\u03AF\u03BC\u03B1\u03BA\u03B1 \u03C4\u03BF\u03C5 \u03B3\u03BA\u03C1\u03B9 +pdfToImage.blackwhite=\u0391\u03C3\u03C0\u03C1\u03CC\u03BC\u03B1\u03C5\u03C1\u03BF (\u039C\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03C7\u03B1\u03B8\u03BF\u03CD\u03BD \u03B4\u03B5\u03B4\u03BF\u03BC\u03AD\u03BD\u03B1!) +pdfToImage.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE + + +#addPassword +addPassword.title=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u039A\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD +addPassword.header=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u039A\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD (\u039A\u03C1\u03C5\u03C0\u03C4\u03BF\u03B3\u03C1\u03AC\u03C6\u03B7\u03C3\u03B7) +addPassword.selectText.1=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 PDF \u03B3\u03B9\u03B1 \u03BA\u03C1\u03C5\u03C0\u03C4\u03BF\u03B3\u03C1\u03AC\u03C6\u03B7\u03C3\u03B7 +addPassword.selectText.2=\u039A\u03C9\u03B4\u03B9\u03BA\u03CC\u03C2 \u03A7\u03C1\u03AE\u03C3\u03C4\u03B7 +addPassword.selectText.3=\u039C\u03AE\u03BA\u03BF\u03C2 \u039A\u03BB\u03B5\u03B9\u03B4\u03B9\u03BF\u03CD \u039A\u03C1\u03C5\u03C0\u03C4\u03BF\u03B3\u03C1\u03AC\u03C6\u03B7\u03C3\u03B7\u03C2 +addPassword.selectText.4=\u039F\u03B9 \u03C5\u03C8\u03B7\u03BB\u03CC\u03C4\u03B5\u03C1\u03B5\u03C2 \u03C4\u03B9\u03BC\u03AD\u03C2 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03B9\u03C3\u03C7\u03C5\u03C1\u03CC\u03C4\u03B5\u03C1\u03B5\u03C2, \u03B1\u03BB\u03BB\u03AC \u03BF\u03B9 \u03C7\u03B1\u03BC\u03B7\u03BB\u03CC\u03C4\u03B5\u03C1\u03B5\u03C2 \u03C4\u03B9\u03BC\u03AD\u03C2 \u03AD\u03C7\u03BF\u03C5\u03BD \u03BA\u03B1\u03BB\u03CD\u03C4\u03B5\u03C1\u03B7 \u03C3\u03C5\u03BC\u03B2\u03B1\u03C4\u03CC\u03C4\u03B7\u03C4\u03B1. +addPassword.selectText.5=\u0394\u03B9\u03BA\u03B1\u03B9\u03CE\u03BC\u03B1\u03C4\u03B1 \u03B3\u03B9\u03B1 \u03C1\u03CD\u03B8\u03BC\u03B9\u03C3\u03B7 (\u03A3\u03C5\u03BD\u03B9\u03C3\u03C4\u03AC\u03C4\u03B1\u03B9 \u03BD\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF\u03C4\u03B1\u03B9 \u03BC\u03B1\u03B6\u03AF \u03BC\u03B5 \u03C4\u03BF\u03BD \u03BA\u03C9\u03B4\u03B9\u03BA\u03CC \u03C0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 \u03BA\u03B1\u03C4\u03CC\u03C7\u03BF\u03C5 (Owner password) +addPassword.selectText.6=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03C3\u03C5\u03BD\u03B1\u03C1\u03BC\u03BF\u03BB\u03CC\u03B3\u03B7\u03C3\u03B7\u03C2 \u03B5\u03B3\u03B3\u03C1\u03AC\u03C6\u03BF\u03C5 +addPassword.selectText.7=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03B5\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE\u03C2 \u03C0\u03B5\u03C1\u03B9\u03B5\u03C7\u03BF\u03BC\u03AD\u03BD\u03BF\u03C5 +addPassword.selectText.8=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03B5\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE\u03C2 \u03B3\u03B9\u03B1 \u03C0\u03C1\u03BF\u03C3\u03B2\u03B1\u03C3\u03B9\u03BC\u03CC\u03C4\u03B7\u03C4\u03B1 +addPassword.selectText.9=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03C3\u03C5\u03BC\u03C0\u03BB\u03AE\u03C1\u03C9\u03C3\u03B7\u03C2 \u03C6\u03CC\u03C1\u03BC\u03B1\u03C2 +addPassword.selectText.10=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C1\u03BF\u03C0\u03BF\u03C0\u03BF\u03AF\u03B7\u03C3\u03B7\u03C2 +addPassword.selectText.11=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C1\u03BF\u03C0\u03BF\u03C0\u03BF\u03AF\u03B7\u03C3\u03B7\u03C2 annotation +addPassword.selectText.12=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03B5\u03BA\u03C4\u03CD\u03C0\u03C9\u03C3\u03B7\u03C2 +addPassword.selectText.13=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03B5\u03BA\u03C4\u03CD\u03C0\u03C9\u03C3\u03B7\u03C2 \u03C3\u03B5 \u03B4\u03B9\u03B1\u03C6\u03BF\u03C1\u03B5\u03C4\u03B9\u03BA\u03BF\u03CD\u03C2 \u03C4\u03CD\u03C0\u03BF\u03C5\u03C2 \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD +addPassword.selectText.14=\u039A\u03C9\u03B4\u03B9\u03BA\u03CC\u03C2 \u03C0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 \u03BA\u03B1\u03C4\u03CC\u03C7\u03BF\u03C5 +addPassword.selectText.15=\u03A0\u03B5\u03C1\u03B9\u03BF\u03C1\u03AF\u03B6\u03B5\u03B9 \u03CC,\u03C4\u03B9 \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B3\u03AF\u03BD\u03B5\u03B9 \u03BC\u03B5 \u03C4\u03BF \u03AD\u03B3\u03B3\u03C1\u03B1\u03C6\u03BF \u03BC\u03B5\u03C4\u03AC \u03C4\u03BF \u03AC\u03BD\u03BF\u03B9\u03B3\u03BC\u03B1 (\u0394\u03B5\u03BD \u03C5\u03C0\u03BF\u03C3\u03C4\u03B7\u03C1\u03AF\u03B6\u03B5\u03C4\u03B1\u03B9 \u03B1\u03C0\u03CC \u03CC\u03BB\u03BF\u03C5\u03C2 \u03C4\u03BF\u03C5\u03C2 \u03B1\u03BD\u03B1\u03B3\u03BD\u03CE\u03C3\u03C4\u03B5\u03C2 PDF) +addPassword.selectText.16=\u03A0\u03B5\u03C1\u03B9\u03BF\u03C1\u03AF\u03B6\u03B5\u03B9 \u03C4\u03BF \u03AC\u03BD\u03BF\u03B9\u03B3\u03BC\u03B1 \u03C4\u03BF\u03C5 \u03AF\u03B4\u03B9\u03BF\u03C5 \u03C4\u03BF\u03C5 \u03B5\u03B3\u03B3\u03C1\u03AC\u03C6\u03BF\u03C5 +addPassword.submit=\u039A\u03C1\u03C5\u03C0\u03C4\u03BF\u03B3\u03C1\u03AC\u03C6\u03B7\u03C3\u03B7 + + +#watermark +watermark.title=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03A5\u03B4\u03B1\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2 +watermark.header=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03A5\u03B4\u03B1\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2 +watermark.selectText.1=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 PDF \u03B3\u03B9\u03B1 \u03C4\u03B7\u03BD \u03C0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03C4\u03BF\u03C5 \u03C5\u03B4\u03B1\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2: +watermark.selectText.2=\u039A\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF \u03A5\u03B4\u03B1\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2: +watermark.selectText.3=\u039C\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u039A\u03B5\u03B9\u03BC\u03AD\u03BD\u03BF\u03C5: +watermark.selectText.4=\u03A0\u03B5\u03C1\u03B9\u03C3\u03C4\u03C1\u03BF\u03C6\u03AE (0-360): +watermark.selectText.5=widthSpacer (\u039A\u03B5\u03BD\u03CC \u03BC\u03B5\u03C4\u03B1\u03BE\u03CD \u03BA\u03AC\u03B8\u03B5 \u03C5\u03B4\u03B1\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2 \u03BF\u03C1\u03B9\u03B6\u03CC\u03BD\u03C4\u03B9\u03B1): +watermark.selectText.6=heightSpacer (\u039A\u03B5\u03BD\u03CC \u03BC\u03B5\u03C4\u03B1\u03BE\u03CD \u03BA\u03AC\u03B8\u03B5 \u03C5\u03B4\u03B1\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2 \u03BA\u03AC\u03B8\u03B5\u03C4\u03B1): +watermark.selectText.7=\u0391\u03B4\u03B9\u03B1\u03C6\u03AC\u03BD\u03B5\u03B9\u03B1 (Opacity) (0% - 100%): +watermark.selectText.8=\u03A4\u03CD\u03C0\u03BF\u03C2 \u03A5\u03B4\u03B1\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2: +watermark.selectText.9=\u0395\u03B9\u03BA\u03CC\u03BD\u03B1 \u03A5\u03B4\u03B1\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2: +watermark.submit=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03A5\u03B4\u03B1\u03C4\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03BC\u03B1\u03C4\u03BF\u03C2 + + +#Change permissions +permissions.title=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE \u0394\u03B9\u03BA\u03B1\u03B9\u03C9\u03BC\u03AC\u03C4\u03C9\u03BD +permissions.header=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE \u0394\u03B9\u03BA\u03B1\u03B9\u03C9\u03BC\u03AC\u03C4\u03C9\u03BD +permissions.warning=\u03A0\u03C1\u03BF\u03B5\u03B9\u03B4\u03BF\u03C0\u03BF\u03AF\u03B7\u03C3\u03B7 \u03CC\u03C4\u03B9 \u03B1\u03C5\u03C4\u03AC \u03C4\u03B1 \u03B4\u03B9\u03BA\u03B1\u03B9\u03CE\u03BC\u03B1\u03C4\u03B1 \u03B4\u03B5\u03BD \u03B1\u03BB\u03BB\u03AC\u03B6\u03BF\u03C5\u03BD, \u03C3\u03C5\u03BD\u03B9\u03C3\u03C4\u03AC\u03C4\u03B1\u03B9 \u03BD\u03B1 \u03C4\u03B1 \u03BF\u03C1\u03AF\u03C3\u03B5\u03C4\u03B5 \u03BC\u03B5 \u03BA\u03C9\u03B4\u03B9\u03BA\u03CC \u03C0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 \u03BC\u03AD\u03C3\u03C9 \u03C4\u03B7\u03C2 \u03C3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 \u03C0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7\u03C2 \u03BA\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD \u03C0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7\u03C2 +permissions.selectText.1=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 PDF \u03B3\u03B9\u03B1 \u03BD\u03B1 \u03B1\u03BB\u03BB\u03AC\u03BE\u03B5\u03C4\u03B5 \u03C4\u03B1 \u03B4\u03B9\u03BA\u03B1\u03B9\u03CE\u03BC\u03B1\u03C4\u03B1 +permissions.selectText.2=\u0394\u03B9\u03BA\u03B1\u03B9\u03CE\u03BC\u03B1\u03C4\u03B1 \u03B3\u03B9\u03B1 \u03C1\u03CD\u03B8\u03BC\u03B9\u03C3\u03B7 +permissions.selectText.3=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03C3\u03C5\u03BD\u03B1\u03C1\u03BC\u03BF\u03BB\u03CC\u03B3\u03B7\u03C3\u03B7\u03C2 \u03B5\u03B3\u03B3\u03C1\u03AC\u03C6\u03BF\u03C5 +permissions.selectText.4=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03B5\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE\u03C2 \u03C0\u03B5\u03C1\u03B9\u03B5\u03C7\u03BF\u03BC\u03AD\u03BD\u03BF\u03C5 +permissions.selectText.5=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03B5\u03BE\u03B1\u03B3\u03C9\u03B3\u03AE\u03C2 \u03B3\u03B9\u03B1 \u03C0\u03C1\u03BF\u03C3\u03B2\u03B1\u03C3\u03B9\u03BC\u03CC\u03C4\u03B7\u03C4\u03B1 +permissions.selectText.6=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03C3\u03C5\u03BC\u03C0\u03BB\u03AE\u03C1\u03C9\u03C3\u03B7\u03C2 \u03C6\u03CC\u03C1\u03BC\u03B1\u03C2 +permissions.selectText.7=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C1\u03BF\u03C0\u03BF\u03C0\u03BF\u03AF\u03B7\u03C3\u03B7\u03C2 +permissions.selectText.8=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C1\u03BF\u03C0\u03BF\u03C0\u03BF\u03AF\u03B7\u03C3\u03B7\u03C2 annotation +permissions.selectText.9=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03B5\u03BA\u03C4\u03CD\u03C0\u03C9\u03C3\u03B7\u03C2 +permissions.selectText.10=\u0391\u03C0\u03BF\u03C4\u03C1\u03BF\u03C0\u03AE \u03B5\u03BA\u03C4\u03CD\u03C0\u03C9\u03C3\u03B7\u03C2 \u03C3\u03B5 \u03B4\u03B9\u03B1\u03C6\u03BF\u03C1\u03B5\u03C4\u03B9\u03BA\u03BF\u03CD\u03C2 \u03C4\u03CD\u03C0\u03BF\u03C5\u03C2 \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD +permissions.submit=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE + + +#remove password +removePassword.title=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u039A\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD +removePassword.header=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 \u039A\u03C9\u03B4\u03B9\u03BA\u03BF\u03CD (\u0391\u03C0\u03BF\u03BA\u03C1\u03C5\u03C0\u03C4\u03BF\u03B3\u03C1\u03AC\u03C6\u03B7\u03C3\u03B7) +removePassword.selectText.1=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 PDF \u03B3\u03B9\u03B1 \u03B1\u03C0\u03BF\u03BA\u03C1\u03C5\u03C0\u03C4\u03BF\u03B3\u03C1\u03AC\u03C6\u03B7\u03C3\u03B7 +removePassword.selectText.2=\u039A\u03C9\u03B4\u03B9\u03BA\u03CC\u03C2 +removePassword.submit=\u0391\u03C6\u03B1\u03AF\u03C1\u03B5\u03C3\u03B7 + + +#changeMetadata +changeMetadata.title=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE \u039C\u03B5\u03C4\u03B1\u03B4\u03B5\u03B4\u03BF\u03BC\u03AD\u03BD\u03C9\u03BD +changeMetadata.header=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE \u039C\u03B5\u03C4\u03B1\u03B4\u03B5\u03B4\u03BF\u03BC\u03AD\u03BD\u03C9\u03BD +changeMetadata.selectText.1=\u03A0\u03B1\u03C1\u03B1\u03BA\u03B1\u03BB\u03BF\u03CD\u03BC\u03B5 \u03B5\u03C0\u03B5\u03BE\u03B5\u03C1\u03B3\u03B1\u03C3\u03C4\u03B5\u03AF\u03C4\u03B5 \u03C4\u03B9\u03C2 \u03BC\u03B5\u03C4\u03B1\u03B2\u03BB\u03B7\u03C4\u03AD\u03C2 \u03C0\u03BF\u03C5 \u03B8\u03AD\u03BB\u03B5\u03C4\u03B5 \u03BD\u03B1 \u03B1\u03BB\u03BB\u03AC\u03BE\u03B5\u03C4\u03B5 +changeMetadata.selectText.2=\u0394\u03B9\u03B1\u03B3\u03C1\u03B1\u03C6\u03AE \u03CC\u03BB\u03C9\u03BD \u03C4\u03C9\u03BD \u03BC\u03B5\u03C4\u03B1\u03B4\u03B5\u03B4\u03BF\u03BC\u03AD\u03BD\u03C9\u03BD +changeMetadata.selectText.3=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03C0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03C3\u03BC\u03AD\u03BD\u03C9\u03BD \u03BC\u03B5\u03C4\u03B1\u03B4\u03B5\u03B4\u03BF\u03BC\u03AD\u03BD\u03C9\u03BD: +changeMetadata.author=\u03A3\u03C5\u03BD\u03C4\u03AC\u03BA\u03C4\u03B7\u03C2: +changeMetadata.creationDate=\u0397\u03BC\u03B5\u03C1\u03BF\u03BC\u03B7\u03BD\u03AF\u03B1 \u0394\u03B7\u03BC\u03B9\u03BF\u03C5\u03C1\u03B3\u03AF\u03B1\u03C2 (yyyy/MM/dd HH:mm:ss): +changeMetadata.creator=\u0394\u03B7\u03BC\u03B9\u03BF\u03C5\u03C1\u03B3\u03CC\u03C2: +changeMetadata.keywords=\u039B\u03AD\u03BE\u03B5\u03B9\u03C2-\u03BA\u03BB\u03B5\u03B9\u03B4\u03B9\u03AC: +changeMetadata.modDate=\u0397\u03BC\u03B5\u03C1\u03BF\u03BC\u03B7\u03BD\u03AF\u03B1 \u03A4\u03C1\u03BF\u03C0\u03BF\u03C0\u03BF\u03AF\u03B7\u03C3\u03B7\u03C2 (yyyy/MM/dd HH:mm:ss): +changeMetadata.producer=\u03A0\u03B1\u03C1\u03B1\u03B3\u03C9\u03B3\u03CC\u03C2: +changeMetadata.subject=\u0398\u03AD\u03BC\u03B1: +changeMetadata.title=\u03A4\u03AF\u03C4\u03BB\u03BF\u03C2: +changeMetadata.trapped=Trapped: +changeMetadata.selectText.4=\u0386\u03BB\u03BB\u03B1 \u03BC\u03B5\u03C4\u03B1\u03B4\u03B5\u03B4\u03BF\u03BC\u03AD\u03BD\u03B1: +changeMetadata.selectText.5=\u03A0\u03C1\u03BF\u03C3\u03B8\u03AE\u03BA\u03B7 \u03B5\u03B3\u03B3\u03C1\u03B1\u03C6\u03AE\u03C2 \u03C0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03C3\u03BC\u03AD\u03BD\u03C9\u03BD \u03BC\u03B5\u03C4\u03B1\u03B4\u03B5\u03B4\u03BF\u03BC\u03AD\u03BD\u03C9\u03BD +changeMetadata.submit=\u0391\u03BB\u03BB\u03B1\u03B3\u03AE + + +#pdfToPDFA +pdfToPDFA.title=PDF \u03C3\u03B5 PDF/A +pdfToPDFA.header=PDF \u03C3\u03B5 PDF/A +pdfToPDFA.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF OCRmyPDF \u03B3\u03B9\u03B1 PDF/A \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE +pdfToPDFA.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE + + +#PDFToWord +PDFToWord.title=PDF \u03C3\u03B5 Word +PDFToWord.header=PDF \u03C3\u03B5 Word +PDFToWord.selectText.1=\u03A4\u03CD\u03C0\u03BF\u03C2 \u0391\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 \u0395\u03BE\u03B1\u03B3\u03CC\u03BC\u03B5\u03BD\u03BF\u03C5 +PDFToWord.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF LibreOffice \u03B3\u03B9\u03B1 \u03C4\u03B7 \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD. +PDFToWord.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE + + +#PDFToPresentation +PDFToPresentation.title=PDF \u03C3\u03B5 Powerpoint +PDFToPresentation.header=PDF \u03C3\u03B5 Powerpoint +PDFToPresentation.selectText.1=\u03A4\u03CD\u03C0\u03BF\u03C2 \u0391\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 \u0395\u03BE\u03B1\u03B3\u03CC\u03BC\u03B5\u03BD\u03BF\u03C5 +PDFToPresentation.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF LibreOffice \u03B3\u03B9\u03B1 \u03C4\u03B7 \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD. +PDFToPresentation.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE + + +#PDFToText +PDFToText.title=PDF \u03C3\u03B5 RTF (\u039A\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF) +PDFToText.header=PDF \u03C3\u03B5 RTF (\u039A\u03B5\u03AF\u03BC\u03B5\u03BD\u03BF) +PDFToText.selectText.1=\u03A4\u03CD\u03C0\u03BF\u03C2 \u0391\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 \u0395\u03BE\u03B1\u03B3\u03CC\u03BC\u03B5\u03BD\u03BF\u03C5 +PDFToText.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF LibreOffice \u03B3\u03B9\u03B1 \u03C4\u03B7 \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD. +PDFToText.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE + + +#PDFToHTML +PDFToHTML.title=PDF \u03C3\u03B5 HTML +PDFToHTML.header=PDF \u03C3\u03B5 HTML +PDFToHTML.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF LibreOffice \u03B3\u03B9\u03B1 \u03C4\u03B7 \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD. +PDFToHTML.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE + + +#PDFToXML +PDFToXML.title=PDF \u03C3\u03B5 XML +PDFToXML.header=PDF \u03C3\u03B5 XML +PDFToXML.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF LibreOffice \u03B3\u03B9\u03B1 \u03C4\u03B7 \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD. +PDFToXML.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \ No newline at end of file diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 977260a5..cc7978c4 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -31,6 +31,24 @@ sizes.medium=Medium sizes.large=Large sizes.x-large=X-Large error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +delete=Delete +username=Username +password=Password +welcome=Welcome +property=Property +black=Black +white=White +red=Red +green=Green +blue=Blue +custom=Custom... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -38,7 +56,7 @@ error.pdfPassword=The PDF Document is passworded and either the password was not ############# navbar.convert=Convert navbar.security=Security -navbar.other=Other +navbar.other=Miscellaneous navbar.darkmode=Dark Mode navbar.pageOps=Page Operations navbar.settings=Settings @@ -54,6 +72,55 @@ settings.downloadOption.1=Open in same window settings.downloadOption.2=Open in new window settings.downloadOption.3=Download file settings.zipThreshold=Zip files when the number of downloaded files exceeds +settings.signOut=Sign Out +settings.accountSettings=Account Settings + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Account Settings +account.accountSettings=Account Settings +account.adminSettings=Admin Settings - View and Add Users +account.userControlSettings=User Control Settings +account.changeUsername=New Username +account.changeUsername=Change Username +account.password=Confirmation Password +account.oldPassword=Old password +account.newPassword=New Password +account.changePassword=Change Password +account.confirmNewPassword=Confirm New Password +account.signOut=Sign Out +account.yourApiKey=Your API Key +account.syncTitle=Sync browser settings with Account +account.settingsCompare=Settings Comparison: +account.property=Property +account.webBrowserSettings=Web Browser Setting +account.syncToBrowser=Sync Account -> Browser +account.syncToAccount=Sync Account <- Browser + + +adminUserSettings.title=User Control Settings +adminUserSettings.header=Admin User Control Settings +adminUserSettings.admin=Admin +adminUserSettings.user=User +adminUserSettings.addUser=Add New User +adminUserSettings.roles=Roles +adminUserSettings.role=Role +adminUserSettings.actions=Actions +adminUserSettings.apiUser=Limited API User +adminUserSettings.webOnlyUser=Web Only User +adminUserSettings.forceChange = Force user to change username/password on login +adminUserSettings.submit=Save User ############# # HOME-PAGE # @@ -260,11 +327,37 @@ home.showJS.title=Show Javascript home.showJS.desc=Searches and displays any JS injected into a PDF showJS.tags=JS +home.autoRedact.title=Auto Redact +home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text +showJS.tags=Redact,Hide,black out,black,marker,hidden + ########################### # # # WEB PAGES # # # ########################### +#login +login.title=Sign in +login.signin=Sign in +login.rememberme=Remember me +login.invalid=Invalid username or password. +login.locked=Your account has been locked. +login.signinTitle=Please sign in + + +#auto-redact +autoRedact.title=Auto Redact +autoRedact.header=Auto Redact +autoRedact.colorLabel=Colour +autoRedact.textsToRedactLabel=Text to Redact (line-separated) +autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret +autoRedact.useRegexLabel=Use Regex +autoRedact.wholeWordSearchLabel=Whole Word Search +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) +autoRedact.submitButton=Submit + + #showJS showJS.title=Show Javascript showJS.header=Show Javascript @@ -335,6 +428,9 @@ addPageNumbers.selectText.3=Position addPageNumbers.selectText.4=Starting Number addPageNumbers.selectText.5=Pages to Number addPageNumbers.selectText.6=Custom Text +addPageNumbers.customTextDesc=Custom Text +addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} addPageNumbers.submit=Add Page Numbers @@ -382,6 +478,7 @@ pipeline.title=Pipeline pageLayout.title=Multi Page Layout pageLayout.header=Multi Page Layout pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.addBorder=Add Borders pageLayout.submit=Submit @@ -520,6 +617,8 @@ addImage.submit=Add image #merge merge.title=Merge merge.header=Merge multiple PDFs (2+) +merge.sortByName=Sort by name +merge.sortByDate=Sort by date merge.submit=Merge @@ -567,7 +666,10 @@ split.submit=Split imageToPDF.title=Image to PDF imageToPDF.header=Image to PDF imageToPDF.submit=Convert -imageToPDF.selectText.1=Stretch to fit +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=Auto rotate PDF imageToPDF.selectText.3=Multi file logic (Only enabled if working with multiple images) imageToPDF.selectText.4=Merge into single PDF @@ -578,9 +680,9 @@ imageToPDF.selectText.5=Convert to separate PDFs pdfToImage.title=PDF to Image pdfToImage.header=PDF to Image pdfToImage.selectText=Image Format -pdfToImage.singleOrMultiple=Image result type -pdfToImage.single=Single Big Image -pdfToImage.multi=Multiple Images +pdfToImage.singleOrMultiple=Page to Image result type +pdfToImage.single=Single Big Image Combing all pages +pdfToImage.multi=Multiple Images, one image per page pdfToImage.colorType=Colour type pdfToImage.color=Colour pdfToImage.grey=Greyscale @@ -620,17 +722,11 @@ watermark.selectText.4=Rotation (0-360): watermark.selectText.5=widthSpacer (Space between each watermark horizontally): watermark.selectText.6=heightSpacer (Space between each watermark vertically): watermark.selectText.7=Opacity (0% - 100%): +watermark.selectText.8=Watermark Type: +watermark.selectText.9=Watermark Image: watermark.submit=Add Watermark -#remove-watermark -remove-watermark.title=Remove Watermark -remove-watermark.header=Remove Watermark -remove-watermark.selectText.1=Select PDF to remove watermark from: -remove-watermark.selectText.2=Watermark Text: -remove-watermark.submit=Remove Watermark - - #Change permissions permissions.title=Change Permissions permissions.header=Change Permissions @@ -676,13 +772,6 @@ changeMetadata.selectText.5=Add Custom Metadata Entry changeMetadata.submit=Change -#xlsToPdf -xlsToPdf.title=Excel to PDF -xlsToPdf.header=Excel to PDF -xlsToPdf.selectText.1=Select XLS or XLSX Excel sheet to convert -xlsToPdf.convert=convert - - #pdfToPDFA pdfToPDFA.title=PDF To PDF/A pdfToPDFA.header=PDF To PDF/A diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties index b1b05e24..bc712951 100644 --- a/src/main/resources/messages_en_US.properties +++ b/src/main/resources/messages_en_US.properties @@ -1,7 +1,7 @@ ########### # Generic # ########### -# the direction that the language is written (ltr = left to right, rtl = right to left) +# the direction that the language is written (ltr=left to right, rtl = right to left) language.direction=ltr pdfPrompt=Select PDF(s) @@ -25,12 +25,30 @@ downloadPdf=Download PDF text=Text font=Font selectFillter=-- Select -- -pageNum=Page Number +pageNum=Page Number sizes.small=Small sizes.medium=Medium sizes.large=Large sizes.x-large=X-Large error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +delete=Delete +username=Username +password=Password +welcome=Welcome +property=Property +black=Black +white=White +red=Red +green=Green +blue=Blue +custom=Custom... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -38,7 +56,7 @@ error.pdfPassword=The PDF Document is passworded and either the password was not ############# navbar.convert=Convert navbar.security=Security -navbar.other=Other +navbar.other=Miscellaneous navbar.darkmode=Dark Mode navbar.pageOps=Page Operations navbar.settings=Settings @@ -54,6 +72,55 @@ settings.downloadOption.1=Open in same window settings.downloadOption.2=Open in new window settings.downloadOption.3=Download file settings.zipThreshold=Zip files when the number of downloaded files exceeds +settings.signOut=Sign Out +settings.accountSettings=Account Settings + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Account Settings +account.accountSettings=Account Settings +account.adminSettings=Admin Settings - View and Add Users +account.userControlSettings=User Control Settings +account.changeUsername=Change Username +account.changeUsername=Change Username +account.password=Confirmation Password +account.oldPassword=Old password +account.newPassword=New Password +account.changePassword=Change Password +account.confirmNewPassword=Confirm New Password +account.signOut=Sign Out +account.yourApiKey=Your API Key +account.syncTitle=Sync browser settings with Account +account.settingsCompare=Settings Comparison: +account.property=Property +account.webBrowserSettings=Web Browser Setting +account.syncToBrowser=Sync Account -> Browser +account.syncToAccount=Sync Account <- Browser + + +adminUserSettings.title=User Control Settings +adminUserSettings.header=Admin User Control Settings +adminUserSettings.admin=Admin +adminUserSettings.user=User +adminUserSettings.addUser=Add New User +adminUserSettings.roles=Roles +adminUserSettings.role=Role +adminUserSettings.actions=Actions +adminUserSettings.apiUser=Limited API User +adminUserSettings.webOnlyUser=Web Only User +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Save User ############# # HOME-PAGE # @@ -71,7 +138,7 @@ merge.tags=merge,Page operations,Back end,server side home.split.title=Split home.split.desc=Split PDFs into multiple documents -split.tags=Page operations,divide,Multi Page,cut,server side +split.tags=Page operations,divide,Multi Page,cut,server side home.rotate.title=Rotate home.rotate.desc=Easily rotate your PDFs. @@ -208,7 +275,7 @@ home.add-page-numbers.desc=Add Page numbers throughout a document in a set locat add-page-numbers.tags=paginate,label,organize,index home.auto-rename.title=Auto Rename PDF File -home.auto-rename.desc=Auto renames a PDF file based on its detected header +home.auto-rename.desc=Auto renames a PDF file based on its detected header auto-rename.tags=auto-detect,header-based,organize,relabel home.adjust-contrast.title=Adjust Colors/Contrast @@ -260,11 +327,37 @@ home.showJS.title=Show Javascript home.showJS.desc=Searches and displays any JS injected into a PDF showJS.tags=JS +home.autoRedact.title=Auto Redact +home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### +#login +login.title=Sign in +login.signin=Sign in +login.rememberme=Remember me +login.invalid=Invalid username or password. +login.locked=Your account has been locked. +login.signinTitle=Please sign in + + +#auto-redact +autoRedact.title=Auto Redact +autoRedact.header=Auto Redact +autoRedact.colorLabel=Color +autoRedact.textsToRedactLabel=Text to Redact (line-separated) +autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret +autoRedact.useRegexLabel=Use Regex +autoRedact.wholeWordSearchLabel=Whole Word Search +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) +autoRedact.submitButton=Submit + + #showJS showJS.title=Show Javascript showJS.header=Show Javascript @@ -335,6 +428,9 @@ addPageNumbers.selectText.3=Position addPageNumbers.selectText.4=Starting Number addPageNumbers.selectText.5=Pages to Number addPageNumbers.selectText.6=Custom Text +addPageNumbers.customTextDesc=Custom Text +addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} addPageNumbers.submit=Add Page Numbers @@ -382,6 +478,10 @@ pipeline.title=Pipeline pageLayout.title=Multi Page Layout pageLayout.header=Multi Page Layout pageLayout.pagesPerSheet=Pages per sheet: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders pageLayout.submit=Submit @@ -396,16 +496,16 @@ scalePages.submit=Submit #certSign certSign.title=Certificate Signing certSign.header=Sign a PDF with your certificate (Work in progress) -certSign.selectPDF=Select a PDF File for Signing: -certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der): -certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der): -certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate): +certSign.selectPDF=Select a PDF File for Signing: +certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der): +certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der): +certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate): certSign.certType=Certificate Type -certSign.password=Enter Your Keystore or Private Key Password (If Any): +certSign.password=Enter Your Keystore or Private Key Password (If Any): certSign.showSig=Show Signature certSign.reason=Reason certSign.location=Location -certSign.name=Name +certSign.name=Name certSign.submit=Sign PDF @@ -505,7 +605,7 @@ compress.selectText.1=Manual Mode - From 1 to 4 compress.selectText.2=Optimization level: compress.selectText.3=4 (Terrible for text images) compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size -compress.selectText.5=Expected PDF Size (e.g. 25MB, 10.8MB, 25KB) +compress.selectText.5=Expected PDF Size (e.g. 25MB, 10.8MB, 25KB) compress.submit=Compress @@ -520,6 +620,8 @@ addImage.submit=Add image #merge merge.title=Merge merge.header=Merge multiple PDFs (2+) +merge.sortByName=Sort by name +merge.sortByDate=Sort by date merge.submit=Merge @@ -567,7 +669,10 @@ split.submit=Split imageToPDF.title=Image to PDF imageToPDF.header=Image to PDF imageToPDF.submit=Convert -imageToPDF.selectText.1=Stretch to fit +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=Auto rotate PDF imageToPDF.selectText.3=Multi file logic (Only enabled if working with multiple images) imageToPDF.selectText.4=Merge into single PDF @@ -620,17 +725,11 @@ watermark.selectText.4=Rotation (0-360): watermark.selectText.5=widthSpacer (Space between each watermark horizontally): watermark.selectText.6=heightSpacer (Space between each watermark vertically): watermark.selectText.7=Opacity (0% - 100%): +watermark.selectText.8=Watermark Type: +watermark.selectText.9=Watermark Image: watermark.submit=Add Watermark -#remove-watermark -remove-watermark.title=Remove Watermark -remove-watermark.header=Remove Watermark -remove-watermark.selectText.1=Select PDF to remove watermark from: -remove-watermark.selectText.2=Watermark Text: -remove-watermark.submit=Remove Watermark - - #Change permissions permissions.title=Change Permissions permissions.header=Change Permissions @@ -657,7 +756,7 @@ removePassword.submit=Remove #changeMetadata -changeMetadata.title=Change Metadata +changeMetadata.title=Title: changeMetadata.header=Change Metadata changeMetadata.selectText.1=Please edit the variables you wish to change changeMetadata.selectText.2=Delete all metadata @@ -676,13 +775,6 @@ changeMetadata.selectText.5=Add Custom Metadata Entry changeMetadata.submit=Change -#xlsToPdf -xlsToPdf.title=Excel to PDF -xlsToPdf.header=Excel to PDF -xlsToPdf.selectText.1=Select XLS or XLSX Excel sheet to convert -xlsToPdf.convert=convert - - #pdfToPDFA pdfToPDFA.title=PDF To PDF/A pdfToPDFA.header=PDF To PDF/A @@ -725,4 +817,4 @@ PDFToHTML.submit=Convert PDFToXML.title=PDF to XML PDFToXML.header=PDF to XML PDFToXML.credit=This service uses LibreOffice for file conversion. -PDFToXML.submit=Convert \ No newline at end of file +PDFToXML.submit=Convert diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index 6f31beec..26e993c8 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -31,6 +31,24 @@ sizes.medium=Mediano sizes.large=Grande sizes.x-large=Extra grande error.pdfPassword=El documento PDF está protegido con contraseña y no se ha proporcionado o es incorrecta +delete=Borrar +username=Nombre de usuario +password=Contraseña +welcome=Bienvenido +property=Propietario +black=Negro +white=Blanco +red=Rojo +green=Verde +blue=Azul +custom=Personalizado... + +changedCredsMessage=Se cambiaron las credenciales! +notAuthenticatedMessage=Usuario njo autentificado. +userNotFoundMessage=Usuario no encontrado. +incorrectPasswordMessage=La contraseña actual no es correcta. +usernameExistsMessage=El nuevo nombre de usuario está en uso. + ############# @@ -41,12 +59,12 @@ navbar.security=Seguridad navbar.other=Otro navbar.darkmode=Modo oscuro navbar.pageOps=Operaciones de página -navbar.settings=Ajustes +navbar.settings=Configuración ############# # SETTINGS # ############# -settings.title=Ajustes +settings.title=Configuración settings.update=Actualización disponible settings.appVersion=Versión de la aplicación: settings.downloadOption.title=Elegir la opción de descarga (para descargas de un solo archivo sin ZIP): @@ -54,6 +72,55 @@ settings.downloadOption.1=Abrir en la misma ventana settings.downloadOption.2=Abrir en una nueva ventana settings.downloadOption.3=Descargar el fichero settings.zipThreshold=Ficheros ZIP cuando excede el número de ficheros descargados +settings.signOut=Desconectar +settings.accountSettings=Configuración de la cuenta + + + +changeCreds.title=Cambiar Credenciales +changeCreds.header=Actualice los detalles de su cuenta +changeCreds.changeUserAndPassword=Está usando las credenciales por defecto. Por favor, introduzca una nueva contraseña (y usuario si lo desea) +changeCreds.newUsername=Nuevo usuario +changeCreds.oldPassword=Contraseña actual +changeCreds.newPassword=Nueva contraseña +changeCreds.confirmNewPassword=Confirme la nueva contraseña +changeCreds.submit=Enviar cambios + + + +account.title=Configuración de la cuenta +account.accountSettings=Configuración de la cuenta +account.adminSettings=Configuración de Administrador - Ver y Añadir Usuarios +account.userControlSettings=Configuración de control de usuario +account.changeUsername=Cambiar nombre de usuario +account.changeUsername=Cambiar nombre de usuario +account.password=Confirmar contraseña +account.oldPassword=Contraseña anterior +account.newPassword=Nueva Contraseña +account.changePassword=Cambiar Contraseña +account.confirmNewPassword=Confirmar Nueva Contraseña +account.signOut=Cerrar sesión +account.yourApiKey=Su clave API +account.syncTitle=Sincronizar la configuración del navegador con la cuenta +account.settingsCompare=Comparación de configuraciones: +account.property=Propiedad +account.webBrowserSettings=Configuración del navegador +account.syncToBrowser=Sincronizar cuenta -> Navegador +account.syncToAccount=Sincronizar cuenta <- Navegador + + +adminUserSettings.title=Configuración de control de usuario +adminUserSettings.header=Configuración de control de usuario administrador +adminUserSettings.admin=Administrador +adminUserSettings.user=Usuario +adminUserSettings.addUser=Añadir Nuevo Usuario +adminUserSettings.roles=Roles +adminUserSettings.role=Rol +adminUserSettings.actions=Acciones +adminUserSettings.apiUser=Usuario limitado de API +adminUserSettings.webOnlyUser=Usuario solo web +adminUserSettings.forceChange=Forzar usuario a cambiar usuario/contraseña en el acceso +adminUserSettings.submit=Guardar Usuario ############# # HOME-PAGE # @@ -63,7 +130,7 @@ home.desc=Su ventanilla única autohospedada para todas tus necesidades PDF home.multiTool.title=Multi-herramienta PDF home.multiTool.desc=Combinar, rotar, reorganizar y eliminar páginas -multiTool.tags=Multi-herramienta,Multi-operación,Interfaz de usuario,Arrastrar con un click,front end,lado del client +multiTool.tags=Multi-herramienta,Multi-operación,Interfaz de usuario,Arrastrar con un click,front end,lado del cliente home.merge.title=Unir home.merge.desc=Unir fácilmente múltiples PDFs en uno @@ -203,12 +270,12 @@ home.pipeline.title=Secuencia (Avanzado) home.pipeline.desc=Ejecutar varias tareas a PDFs definiendo una secuencia de comandos pipeline.tags=automatizar,secuencia,con script,proceso por lotes -home.add-page-numbers.title=Aádir números de página -home.add-page-numbers.desc=Aádir números de página en un documento en una ubicación concreta +home.add-page-numbers.title=Añadir números de página +home.add-page-numbers.desc=Añadir números de página en un documento en una ubicación concreta add-page-numbers.tags=paginar,etiquetar,organizar,indexar home.auto-rename.title=Auto renombrar archivo PDF -home.auto-rename.desc=Auto renormbrar un archivo PDF según su encabezamiento detecetado +home.auto-rename.desc=Auto renombrar un archivo PDF según el encabezamiento detectado auto-rename.tags=auto-detectar,basado en el encabezamiento,organizar,re-etiquetar home.adjust-contrast.title=Ajustar Color/Contraste @@ -236,31 +303,32 @@ home.HTMLToPDF.desc=Convierte cualquier archivo HTML o ZIP a PDF HTMLToPDF.tags=margen,contenido web,transformación,convertir -home.MarkdownToPDF.title=Markdown to PDF -home.MarkdownToPDF.desc=Converts any Markdown file to PDF -MarkdownToPDF.tags=markup,web-content,transformation,convert +home.MarkdownToPDF.title=Markdown a PDF +home.MarkdownToPDF.desc=Convierte cualquier archivo Markdown a PDF +MarkdownToPDF.tags=margen,contenido web,transformación,convertir -home.getPdfInfo.title=Get ALL Info on PDF -home.getPdfInfo.desc=Grabs any and all information possible on PDFs -getPdfInfo.tags=infomation,data,stats,statistics +home.getPdfInfo.title=Obtener toda la información en PDF +home.getPdfInfo.desc=Obtiene toda la información posible de archivos PDF +getPdfInfo.tags=información,datos,stats,estadísticas -home.extractPage.title=Extract page(s) -home.extractPage.desc=Extracts select pages from PDF -extractPage.tags=extract +home.extractPage.title=Extraer página(s) +home.extractPage.desc=Extraer las páginas seleccionadas del PDF +extractPage.tags=extraer -home.PdfToSinglePage.title=PDF to Single Large Page -home.PdfToSinglePage.desc=Merges all PDF pages into one large single page -PdfToSinglePage.tags=single page +home.PdfToSinglePage.title=PDF a una sola página +home.PdfToSinglePage.desc=Unir todas las páginas del PDF en una sola página +PdfToSinglePage.tags=página única -########################## -### TODO: Translate ### -########################## -home.showJS.title=Show Javascript -home.showJS.desc=Searches and displays any JS injected into a PDF +home.showJS.title=Mostrar Javascript +home.showJS.desc=Busca y muestra cualquier JS contenido en un PDF +showJS.tags=JS + +home.autoRedact.title=Auto Redactar +home.autoRedact.desc=Redactar automáticamente (ocultar) texto en un PDF según el texto introducido showJS.tags=JS ########################### @@ -268,41 +336,60 @@ showJS.tags=JS # WEB PAGES # # # ########################### +#login +login.title=Iniciar sesión +login.signin=Iniciar sesión +login.rememberme=Recordarme +login.invalid=Nombre de usuario o contraseña erróneos. +login.locked=Su cuenta se ha bloqueado. +login.signinTitle=Por favor, inicie sesión + + +#auto-redact +autoRedact.title=Auto Redactar +autoRedact.header=Auto Redactar +autoRedact.colorLabel=Color +autoRedact.textsToRedactLabel=Texto para Redactar (separado por líneas) +autoRedact.textsToRedactPlaceholder=por ej. \nConfidencial \nAlto-Secreto +autoRedact.useRegexLabel=Usar Regex +autoRedact.wholeWordSearchLabel=Búsqueda por palabra completa +autoRedact.customPaddingLabel=Extra Padding personalizado +autoRedact.convertPDFToImageLabel=Convertir PDF a imagen-PDF (Utilizado para quitar el texto detrás del cajetín) +autoRedact.submitButton=Enviar + + #showJS -########################## -### TODO: Translate ### -########################## -showJS.title=Show Javascript -showJS.header=Show Javascript -showJS.downloadJS=Download Javascript -showJS.submit=Show +showJS.title=Mostrar Javascript +showJS.header=Mostrar Javascript +showJS.downloadJS=Descargar Javascript +showJS.submit=Mostrar #pdfToSinglePage -pdfToSinglePage.title=PDF To Single Page -pdfToSinglePage.header=PDF To Single Page -pdfToSinglePage.submit=Convert To Single Page +pdfToSinglePage.title=PDF a página única +pdfToSinglePage.header=PDF a página única +pdfToSinglePage.submit=Convertir a página única #pageExtracter -pageExtracter.title=Extract Pages -pageExtracter.header=Extract Pages -pageExtracter.submit=Extract +pageExtracter.title=Extraer Páginas +pageExtracter.header=Extraer Páginas +pageExtracter.submit=Extraer #getPdfInfo -getPdfInfo.title=Get Info on PDF -getPdfInfo.header=Get Info on PDF -getPdfInfo.submit=Get Info -getPdfInfo.downloadJson=Download JSON +getPdfInfo.title=Obtener Información del PDF +getPdfInfo.header=Obtener Información del PDF +getPdfInfo.submit=Obtener Información +getPdfInfo.downloadJson=Descargar JSON #markdown-to-pdf -MarkdownToPDF.title=Markdown To PDF -MarkdownToPDF.header=Markdown To PDF -MarkdownToPDF.submit=Convert -MarkdownToPDF.help=Work in progress -MarkdownToPDF.credit=Uses WeasyPrint +MarkdownToPDF.title=Markdown a PDF +MarkdownToPDF.header=Markdown a PDF +MarkdownToPDF.submit=Convertir +MarkdownToPDF.help=Tarea en proceso +MarkdownToPDF.credit=Usa WeasyPrint @@ -341,6 +428,9 @@ addPageNumbers.selectText.3=Posición addPageNumbers.selectText.4=Número de inicio addPageNumbers.selectText.5=Páginas a numerar addPageNumbers.selectText.6=Texto personalizado +addPageNumbers.customTextDesc=Texto personalizado +addPageNumbers.numberPagesDesc=Qué páginas numerar, por defecto 'todas', también acepta 1-5 o 2,5,9 etc +addPageNumbers.customNumberDesc=Por defecto a {n}, también acepta 'Página {n} de {total}', 'Texto-{n}', '{nombre de archivo}-{n} addPageNumbers.submit=Añadir Números de Página @@ -388,6 +478,10 @@ pipeline.title=Pipeline pageLayout.title=Diseño de varias páginas pageLayout.header=Diseño de varias páginas pageLayout.pagesPerSheet=Páginas por hoja: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders pageLayout.submit=Entregar @@ -526,6 +620,8 @@ addImage.submit=Añadir imagen #merge merge.title=Unir merge.header=Unir múltiples PDFs (2+) +merge.sortByName=Ordenar por nombre +merge.sortByDate=Ordenar por fecha merge.submit=Unir @@ -550,7 +646,7 @@ pageRemover.submit=Eliminar Páginas #rotate rotate.title=Rotar PDF rotate.header=Rotar PDF -rotate.selectAngle=Select rotation angle (in multiples of 90 degrees): +rotate.selectAngle=Seleccionar ángulo de rotación (en múltiplos de 90 grados): rotate.submit=Rotar @@ -573,7 +669,10 @@ split.submit=Dividir imageToPDF.title=Imagen a PDF imageToPDF.header=Imagen a PDF imageToPDF.submit=Convertir -imageToPDF.selectText.1=Estirar para ajustar +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=Rotación automática del PDF imageToPDF.selectText.3=Lógica de archivos múltiples (únicamente activado si funciona con multiples imágenes) imageToPDF.selectText.4=Unir en un único archivo PDF @@ -626,17 +725,11 @@ watermark.selectText.4=Rotación (0-360): watermark.selectText.5=Ancho (Espacio entre cada marca de agua horizontalmente): watermark.selectText.6=Alto (Espacio entre cada marca de agua verticalmente): watermark.selectText.7=Opacidad (0% - 100%): +watermark.selectText.8=Tipo de marca de agua: +watermark.selectText.9=Imagen de marca de agua: watermark.submit=Añadir marca de agua -#remove-watermark -remove-watermark.title=Eliminar marca de agua -remove-watermark.header=Eliminar marca de agua -remove-watermark.selectText.1=Seleccionar PDF para eliminar la marca de agua: -remove-watermark.selectText.2=Texto de la marca de agua: -remove-watermark.submit=Eliminar marca de agua - - #Change permissions permissions.title=Cambiar permisos permissions.header=Cambiar permisos @@ -676,19 +769,12 @@ changeMetadata.modDate=Fecha de modificación (aaaa/MM/dd HH:mm:ss): changeMetadata.producer=Productor: changeMetadata.subject=Asunto: changeMetadata.title=Título: -changeMetadata.trapped=Trapped: +changeMetadata.trapped=Capturado: changeMetadata.selectText.4=Otros Metadatos: changeMetadata.selectText.5=Agregar entrada de metadatos personalizados changeMetadata.submit=Cambiar -#xlsToPdf -xlsToPdf.title=Excel a PDF -xlsToPdf.header=Excel a PDF -xlsToPdf.selectText.1=Seleccionar hoja de cálculo de Excel XLS o XLSX para convertir -xlsToPdf.convert=Convertir - - #pdfToPDFA pdfToPDFA.title=PDF a PDF/A pdfToPDFA.header=PDF a PDF/A diff --git a/src/main/resources/messages_eu_ES.properties b/src/main/resources/messages_eu_ES.properties index c1e6eba0..e0dc8e5a 100644 --- a/src/main/resources/messages_eu_ES.properties +++ b/src/main/resources/messages_eu_ES.properties @@ -24,13 +24,31 @@ alphabet=Alfabetoa downloadPdf=PDFa deskargatu text=Testua font=Letra-tipoa -selectFillter=-- Select -- +selectFillter=-- Aukeratu filtroa -- pageNum=Orrialde-zenbakia -sizes.small=Small -sizes.medium=Medium -sizes.large=Large -sizes.x-large=X-Large -error.pdfPassword=PDF dokumentua pasahitzarekin babestuta dago eta pasahitza ez da sartu edo akastuna da +sizes.small=Txikia +sizes.medium=Erdikoa +sizes.large=Handia +sizes.x-large=Oso handia +error.pdfPassword=PDF dokumentua pasahitzarekin babestuta dago eta pasahitza ez da sartu edo okerra da +delete=ezabatu +username=Erabiltzaile izena +password=Pasahitza +welcome=Ongi etorria +property=Propietate +black=Beltza +white=Txuria +red=Gorria +green=Berdea +blue=Urdina +custom=Pertsonalizatu... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -54,6 +72,55 @@ settings.downloadOption.1=Ireki leiho berean settings.downloadOption.2=Ireki leiho berrian settings.downloadOption.3=Deskargatu fitxategia settings.zipThreshold=ZIP fitxategiak deskargatutako fitxategi kopurua gainditzen denean +settings.signOut=Saioa itxi +settings.accountSettings=Kontuaren ezarpenak + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Kontuaren ezarpenak +account.accountSettings=Kontuaren ezarpenak +account.adminSettings=Admin ezarpenak - Ikusi eta gehitu Erabiltzaileak +account.userControlSettings=Erabiltzaile ezarpen kontrolak +account.changeUsername=Aldatu erabiltzaile izena +account.changeUsername=Aldatu erabiltzaile izena +account.password=Konfirmatu pasahitza +account.oldPassword=Pasahitz zaharra +account.newPassword=Pasahitz berria +account.changePassword=Aldatu pasahitza +account.confirmNewPassword=Konfirmatu pasahitz berria +account.signOut=Saioa itxi +account.yourApiKey=Zure API Key +account.syncTitle=Sinkronizatu nabigatzailearen ezarpenak zure kontuarekin +account.settingsCompare=Ezarpenen konparaketa: +account.property=Propietatea +account.webBrowserSettings=Web nabigatzailearen ezarpenak +account.syncToBrowser=Sync Kontua -> Nabigatzailea +account.syncToAccount=Sync Kontua <- Nabigatzailea + + +adminUserSettings.title=Erabiltzailearen Ezarpenen Kontrolak +adminUserSettings.header=Admin Erabiltzailearen Ezarpenen Kontrolak +adminUserSettings.admin=Admin +adminUserSettings.user=Erabiltzaile +adminUserSettings.addUser=Erabiltzaile berria +adminUserSettings.roles=Rolak +adminUserSettings.role=Rol +adminUserSettings.actions=Ekintzak +adminUserSettings.apiUser=APIren erabiltzaile mugatua +adminUserSettings.webOnlyUser=Web-erabiltzailea bakarrik +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Gorde Erabiltzailea ############# # HOME-PAGE # @@ -199,68 +266,69 @@ home.scalePages.title=Eskalatu/Doitu orrialdearen tamaina home.scalePages.desc=Eskalatu/Aldatu orrialde baten tamaina eta/edo edukia scalePages.tags=resize,modify,dimension,adapt -home.pipeline.title=Pipeline (Advanced) -home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts +home.pipeline.title=Hodia (Aurreratua) +home.pipeline.desc=Egin hainbat ekintza PDFn, hodi-script-ak definituz pipeline.tags=automate,sequence,scripted,batch-process -home.add-page-numbers.title=Add Page Numbers -home.add-page-numbers.desc=Add Page numbers throughout a document in a set location +home.add-page-numbers.title=Gehitu orrialde-zenbakiak +home.add-page-numbers.desc=Gehitu orrialde-zenbakiak dokumentu batean, kokapen jakin batean add-page-numbers.tags=paginate,label,organize,index -home.auto-rename.title=Auto Rename PDF File -home.auto-rename.desc=Auto renames a PDF file based on its detected header +home.auto-rename.title=Auto Aldatu PDF fitxategiaren izena +home.auto-rename.desc=Automatikoki izena ematen dio detektatutako goiburuan oinarritutako PDF fitxategi bati auto-rename.tags=auto-detect,header-based,organize,relabel -home.adjust-contrast.title=Adjust Colors/Contrast -home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF +home.adjust-contrast.title=Koloreak/kontrastea doitu +home.adjust-contrast.desc=PDF baten kontrastea, saturazioa eta distira doitzea adjust-contrast.tags=color-correction,tune,modify,enhance -home.crop.title=Crop PDF -home.crop.desc=Crop a PDF to reduce its size (maintains text!) +home.crop.title=Moztu PDF +home.crop.desc=Egin klik PDFn tamaina txikitzeko (textua mantentzen du!) crop.tags=trim,shrink,edit,shape -home.autoSplitPDF.title=Auto Split Pages +home.autoSplitPDF.title=Orriak automatikoki banandu home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code autoSplitPDF.tags=QR-based,separate,scan-segment,organize -home.sanitizePdf.title=Sanitize -home.sanitizePdf.desc=Remove scripts and other elements from PDF files +home.sanitizePdf.title=Desinfektatu +home.sanitizePdf.desc=Ezabatu script-ak eta PDF fitxategietako beste elementu batzuk sanitizePdf.tags=clean,secure,safe,remove-threats -home.URLToPDF.title=URL/Website To PDF -home.URLToPDF.desc=Converts any http(s)URL to PDF +home.URLToPDF.title=URL/Website PDF pdf bihurtu +home.URLToPDF.desc=Bihurtu edozein URL PDF fitxategian URLToPDF.tags=web-capture,save-page,web-to-doc,archive -home.HTMLToPDF.title=HTML to PDF -home.HTMLToPDF.desc=Converts any HTML file or zip to PDF +home.HTMLToPDF.title=HTML PDF-ra +home.HTMLToPDF.desc=Bihurtu edozein HTML edo zip fitxategi PDFra HTMLToPDF.tags=markup,web-content,transformation,convert -home.MarkdownToPDF.title=Markdown to PDF -home.MarkdownToPDF.desc=Converts any Markdown file to PDF +home.MarkdownToPDF.title=Markdown PDF-ra +home.MarkdownToPDF.desc=Bihurtu Markdown fitxategi guztiak PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -home.getPdfInfo.title=Get ALL Info on PDF -home.getPdfInfo.desc=Grabs any and all information possible on PDFs +home.getPdfInfo.title=Lortu informazio guztia PDF-tik +home.getPdfInfo.desc=Eskuratu PDF fitxategiko Informazio guztia getPdfInfo.tags=infomation,data,stats,statistics -home.extractPage.title=Extract page(s) -home.extractPage.desc=Extracts select pages from PDF +home.extractPage.title=Orria(k) atera +home.extractPage.desc=Aukeratutako orriak PDF fitxategitik atera extractPage.tags=extract -home.PdfToSinglePage.title=PDF to Single Large Page -home.PdfToSinglePage.desc=Merges all PDF pages into one large single page +home.PdfToSinglePage.title=PDF fitxategia, orrialde handi bakar batera +home.PdfToSinglePage.desc=PDF orri guztiak orri handi bakar batean konbinatzen ditu PdfToSinglePage.tags=single page -########################## -### TODO: Translate ### -########################## -home.showJS.title=Show Javascript -home.showJS.desc=Searches and displays any JS injected into a PDF +home.showJS.title=Javascript erakutsi +home.showJS.desc=Bilatu eta erakutsi PDF batean injektatutako edozein JS +showJS.tags=JS + +home.autoRedact.title=Auto Idatzi +home.autoRedact.desc=Auto Idatzi testua pdf fitxategian sarrerako testuan oinarritua showJS.tags=JS ########################### @@ -268,126 +336,152 @@ showJS.tags=JS # WEB PAGES # # # ########################### +#login +login.title=Saioa hasi +login.signin=Saioa hasi +login.rememberme=Oroitu nazazu +login.invalid=Okerreko erabiltzaile izena edo pasahitza. +login.locked=Zure kontua blokeatu egin da. +login.signinTitle=Mesedez, hasi saioa + + +#auto-redact +autoRedact.title=Auto Idatzi +autoRedact.header=Auto Idatzi +autoRedact.colorLabel=Kolorea +autoRedact.textsToRedactLabel=Idazteko testua (lerro bidez bereizia) +autoRedact.textsToRedactPlaceholder=adib. \nKonfidentziala \nTop-Secret +autoRedact.useRegexLabel=Regex erabili +autoRedact.wholeWordSearchLabel=Hitz osoen bilaketa +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Bihurtu PDF fitxategi bat PDF-Irudi-ra (kaxaren atzean testua ezabatzeko erabilia) +autoRedact.submitButton=Bidali + + #showJS -########################## -### TODO: Translate ### -########################## -showJS.title=Show Javascript -showJS.header=Show Javascript -showJS.downloadJS=Download Javascript -showJS.submit=Show +showJS.title=Javascript erakutsi +showJS.header=Javascript erakutsi +showJS.downloadJS=Javascript deskargatu +showJS.submit=Erakutsi #pdfToSinglePage -pdfToSinglePage.title=PDF To Single Page -pdfToSinglePage.header=PDF To Single Page -pdfToSinglePage.submit=Convert To Single Page +pdfToSinglePage.title=PDF Orrialde bakarrera +pdfToSinglePage.header=PDF Orrialde bakarrera +pdfToSinglePage.submit=Orrialde bakarrera bihurtu #pageExtracter -pageExtracter.title=Extract Pages -pageExtracter.header=Extract Pages -pageExtracter.submit=Extract +pageExtracter.title=Atera orriak +pageExtracter.header=Atera orriak +pageExtracter.submit=Atera #getPdfInfo -getPdfInfo.title=Get Info on PDF -getPdfInfo.header=Get Info on PDF -getPdfInfo.submit=Get Info -getPdfInfo.downloadJson=Download JSON +getPdfInfo.title=Lortu informazioa PDFn +getPdfInfo.header=Lortu informazioa PDFn +getPdfInfo.submit=Lortu informazioa +getPdfInfo.downloadJson=Deskargatu JSON #markdown-to-pdf -MarkdownToPDF.title=Markdown To PDF -MarkdownToPDF.header=Markdown To PDF -MarkdownToPDF.submit=Convert -MarkdownToPDF.help=Work in progress -MarkdownToPDF.credit=Uses WeasyPrint +MarkdownToPDF.title=Markdown PDFra +MarkdownToPDF.header=Markdown PDFra +MarkdownToPDF.submit=Bihurtu +MarkdownToPDF.help=Lanean +MarkdownToPDF.credit=WeasyPrint darabil #url-to-pdf -URLToPDF.title=URL To PDF -URLToPDF.header=URL To PDF -URLToPDF.submit=Convert -URLToPDF.credit=Uses WeasyPrint +URLToPDF.title=URL bat PDF-ra +URLToPDF.header=URL bat PDF-ra +URLToPDF.submit=Bihurty +URLToPDF.credit=WeasyPrint darabil #html-to-pdf -HTMLToPDF.title=HTML To PDF -HTMLToPDF.header=HTML To PDF -HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required -HTMLToPDF.submit=Convert -HTMLToPDF.credit=Uses WeasyPrint +HTMLToPDF.title=HTML bat PDF-ra +HTMLToPDF.header=HTML bat PDF-ra +HTMLToPDF.help=Html/css/images etab dituen HTML eta Zip fitxategiak onartzen ditu +HTMLToPDF.submit=Bihurtu +HTMLToPDF.credit=WeasyPrint darabil #sanitizePDF -sanitizePDF.title=Sanitize PDF -sanitizePDF.header=Sanitize a PDF file -sanitizePDF.selectText.1=Remove JavaScript actions -sanitizePDF.selectText.2=Remove embedded files -sanitizePDF.selectText.3=Remove metadata -sanitizePDF.selectText.4=Remove links -sanitizePDF.selectText.5=Remove fonts -sanitizePDF.submit=Sanitize PDF +sanitizePDF.title=PDF-a desinfektatu +sanitizePDF.header=PDF fitxategi bat desinfektatu +sanitizePDF.selectText.1=Ezabatu JavaScript akzioak +sanitizePDF.selectText.2=Ezabatu embedded fitxategiak +sanitizePDF.selectText.3=Ezabatu metadata +sanitizePDF.selectText.4=Ezabatu esketak +sanitizePDF.selectText.5=Ezabatu iturri letrak +sanitizePDF.submit=Desinfektatu PDF #addPageNumbers -addPageNumbers.title=Add Page Numbers -addPageNumbers.header=Add Page Numbers -addPageNumbers.selectText.1=Select PDF file: -addPageNumbers.selectText.2=Margin Size -addPageNumbers.selectText.3=Position -addPageNumbers.selectText.4=Starting Number -addPageNumbers.selectText.5=Pages to Number -addPageNumbers.selectText.6=Custom Text -addPageNumbers.submit=Add Page Numbers +addPageNumbers.title=Gehitu orrialde-zenbakiak +addPageNumbers.header=Gehitu orrialde-zenbakiak +addPageNumbers.selectText.1=Aukeratu PDF fitxategia: +addPageNumbers.selectText.2=Marjinaren tamaina +addPageNumbers.selectText.3=Posizioa +addPageNumbers.selectText.4=Hasiera-zenbakia +addPageNumbers.selectText.5=Orrialde kopurua +addPageNumbers.selectText.6=Testu pertsonalizatua +addPageNumbers.customTextDesc=Testu pertsonalizatua +addPageNumbers.numberPagesDesc=Zein orri numeratu, lehenetsita 'denak', 1-5 edo 2,5,9 etab onartzen ditu +addPageNumbers.customNumberDesc=Lehenetsoa {n}-ra, '{n} orria {total}-tik', 'Text-{n}', '{filename}-{n}' ere onartzen du +addPageNumbers.submit=Gehitu orrialde-zenbakiak #auto-rename -auto-rename.title=Auto Rename -auto-rename.header=Auto Rename PDF -auto-rename.submit=Auto Rename +auto-rename.title=Aldatu izena +auto-rename.header=PDF Aldatu izena +auto-rename.submit=Aldatu izena #adjustContrast -adjustContrast.title=Adjust Contrast -adjustContrast.header=Adjust Contrast -adjustContrast.contrast=Contrast: -adjustContrast.brightness=Brightness: -adjustContrast.saturation=Saturation: -adjustContrast.download=Download +adjustContrast.title=Doitu kontrastea +adjustContrast.header=Doitu kontrastea +adjustContrast.contrast=Kontrastea: +adjustContrast.brightness=Distira: +adjustContrast.saturation=Asetasuna: +adjustContrast.download=Distira #crop -crop.title=Crop -crop.header=Crop Image -crop.submit=Submit +crop.title=Moztu +crop.header=Irudia Moztu +crop.submit=Bidali #autoSplitPDF -autoSplitPDF.title=Auto Split PDF -autoSplitPDF.header=Auto Split PDF -autoSplitPDF.description=Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed. -autoSplitPDF.selectText.1=Print out some divider sheets from below (Black and white is fine). -autoSplitPDF.selectText.2=Scan all your documents at once by inserting the divider sheet between them. -autoSplitPDF.selectText.3=Upload the single large scanned PDF file and let Stirling PDF handle the rest. -autoSplitPDF.selectText.4=Divider pages are automatically detected and removed, guaranteeing a neat final document. +autoSplitPDF.title=Auto Zatitu PDFa +autoSplitPDF.header=Auto Zatitu PDFa +autoSplitPDF.description=Inprimatu, txertatu, eskaneatu, igo eta utzi guri automatikoki bereizten zure dokumentuak. Ez da laneko eskuzko hautaketarik behar. +autoSplitPDF.selectText.1=Inprimatu beheko zatitze-orri batzuk (beltza eta zuria ondo dago). +autoSplitPDF.selectText.2=Eskaneatu dokumentu guztiak batera, eta sartu banalerroa haien artean. +autoSplitPDF.selectText.3=Igo eskaneatutako PDF artxibo handia, eta utzi Stirling PDFri gainerakoak maneiatzen. +autoSplitPDF.selectText.4=Orrialde zatitzaileak automatikoki detektatu eta kentzen dira, eta azken dokumentu ordenatua bermatzen da. autoSplitPDF.formPrompt=Submit PDF containing Stirling-PDF Page dividers: -autoSplitPDF.duplexMode=Duplex Mode (Front and back scanning) -autoSplitPDF.dividerDownload1=Download 'Auto Splitter Divider (minimal).pdf' -autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf' -autoSplitPDF.submit=Submit +autoSplitPDF.duplexMode=Duplex Mode (Front and back scanning)Duplex modua (aurreko eta atzeko azterketa) +autoSplitPDF.dividerDownload1=Deskargatu 'Auto Splitter Divider (minimal).pdf' +autoSplitPDF.dividerDownload2=Deskargatu 'Auto Splitter Divider (with instructions).pdf' +autoSplitPDF.submit=Bidali #pipeline -pipeline.title=Pipeline +pipeline.title=Hodia #pageLayout pageLayout.title=Hainbat orrialderen diseinua pageLayout.header=Hainbat orrialderen diseinua pageLayout.pagesPerSheet=Orrialdeak orriko: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders pageLayout.submit=Entregatu @@ -526,6 +620,8 @@ addImage.submit=Gehitu irudia #merge merge.title=Elkartu merge.header=Elkartu zenbait PDF (2+) +merge.sortByName=Sort by nameOrdenatu izenaren arabera +merge.sortByDate=Ordenatu dataren arabera merge.submit=Elkartu @@ -550,7 +646,7 @@ pageRemover.submit=Ezabatu orrialdeak #rotate rotate.title=Biratu PDFa rotate.header=Biratu PDFa -rotate.selectAngle=Select rotation angle (in multiples of 90 degrees): +rotate.selectAngle=Hautatu errotazio-angelua (90 graduko multiploetan): rotate.submit=Biratu @@ -573,7 +669,10 @@ split.submit=Zatitu imageToPDF.title=Irudia PDF bihurtu imageToPDF.header=Irudia PDF bihurtu imageToPDF.submit=Bihurtu -imageToPDF.selectText.1=Zabaldu doitzeko +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=PDFaren errotazio automatikoa imageToPDF.selectText.3=Fitxategi askoren logika (gaituta bakarrik zenbait irudirekin ari denean) imageToPDF.selectText.4=Elkartu PDF bakar batean @@ -626,17 +725,11 @@ watermark.selectText.4=Errotazioa (0-360): watermark.selectText.5=Zabalera (ur-marka bakoitzaren arteko espazioa horizontalean): watermark.selectText.6=Altuera (ur-marka bakoitzaren arteko espazioa bertikalean): watermark.selectText.7=Opakutasuna (0% - 100%): +watermark.selectText.8=Watermark Type: +watermark.selectText.9=Watermark Image: watermark.submit=Gehitu ur-marka -#remove-watermark -remove-watermark.title=Ezabatu ur-marka -remove-watermark.header=Ezabatu ur-marka -remove-watermark.selectText.1=Hautatu PDFa ur-marka ezabatzeko: -remove-watermark.selectText.2=Ur-markaren testua: -remove-watermark.submit=Ezabatu ur-marka - - #Change permissions permissions.title=Aldatu baimenak permissions.header=Aldatu baimenak @@ -682,13 +775,6 @@ changeMetadata.selectText.5=Gehitu metadatu pertsonalizatuen sarrera changeMetadata.submit=Aldatu -#xlsToPdf -xlsToPdf.title=Excela PDF bihurtu -xlsToPdf.header=Excela PDF bihurtu -xlsToPdf.selectText.1=Hautatu Excel XLSren edo XLSXren kalkulu-orria bihurtzeko -xlsToPdf.convert=Bikurtu - - #pdfToPDFA pdfToPDFA.title=PDFa PDF/A bihurtu pdfToPDFA.header=PDFa PDF/A bihurtu diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index b26278f5..b1e5f222 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -1,16 +1,16 @@ ########### # Generic # ########### -# the direction that the language is written (ltr=left to right, rtl = right to left) +# the direction that the language is written (ltr = left to right, rtl = right to left) language.direction=ltr -pdfPrompt=Choisir PDF -multiPdfPrompt=Choisir des PDF (2+) +pdfPrompt=Sélectionnez le(s) PDF +multiPdfPrompt=Sélectionnez les PDF multiPdfDropPrompt=Sélectionnez (ou glissez-déposez) tous les PDF dont vous avez besoin imgPrompt=Choisir une image -genericSubmit=Soumettre -processTimeWarning=Attention�: ce processus peut prendre jusqu'à une minute en fonction de la taille du fichier -pageOrderPrompt=Ordre des pages (Entrez une liste de numéros de page séparés par des virgules): +genericSubmit=Envoyer +processTimeWarning=Attention, ce processus peut prendre jusqu\u2019à une minute en fonction de la taille du fichier. +pageOrderPrompt=Ordre des pages (entrez une liste de numéros de page séparés par des virgules ou des fonctions telles que 2n+1)\u00a0: goToPage=Aller true=Vrai false=Faux @@ -19,18 +19,36 @@ save=Enregistrer close=Fermer filesSelected=fichiers sélectionnés noFavourites=Aucun favori ajouté -bored=Ennuyé d'attendre ? +bored=Ennuyé d\u2019attendre\u00a0? alphabet=Alphabet downloadPdf=Télécharger le PDF text=Texte font=Police -selectFillter=-- Select -- +selectFillter=-- Sélectionnez -- pageNum=numéro de page -sizes.small=Small -sizes.medium=Medium -sizes.large=Large -sizes.x-large=X-Large -error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +sizes.small=Petit +sizes.medium=Moyen +sizes.large=Grand +sizes.x-large=Très grand +error.pdfPassword=Le document PDF est protégé par un mot de passe et le mot de passe n\u2019a pas été fourni ou était incorrect +delete=Supprimer +username=Nom d\u2019utilisateur +password=Mot de passe +welcome=Bienvenue +property=Propriété +black=Noir +white=Blanc +red=Rouge +green=Vert +blue=Bleu +custom=Personnalisé\u2026 + +changedCredsMessage=Les identifiants ont été mis à jour\u00a0! +notAuthenticatedMessage=Utilisateur non authentifié. +userNotFoundMessage=Utilisateur non trouvé. +incorrectPasswordMessage=Le mot de passe actuel est incorrect. +usernameExistsMessage=Le nouveau nom d\u2019utilisateur existe déjà. + ############# @@ -40,7 +58,7 @@ navbar.convert=Convertir navbar.security=Sécurité navbar.other=Autre navbar.darkmode=Mode sombre -navbar.pageOps=Opérations de page +navbar.pageOps=Opérations sur les pages navbar.settings=Paramètres ############# @@ -48,336 +66,408 @@ navbar.settings=Paramètres ############# settings.title=Paramètres settings.update=Mise à jour disponible -settings.appVersion=Version de l'application : -settings.downloadOption.title=Choisissez l'option de téléchargement (pour les téléchargements sans fichier unique) : +settings.appVersion=Version de l\u2019application\u00a0: +settings.downloadOption.title=Choisissez l\u2019option de téléchargement (pour les téléchargements à fichier unique non ZIP)\u00a0: settings.downloadOption.1=Ouvrir dans la même fenêtre settings.downloadOption.2=Ouvrir dans une nouvelle fenêtre -settings.downloadOption.3=Fichier téléchargé -settings.zipThreshold=Zip les fichiers lorsque le nombre de fichiers téléchargés dépasse +settings.downloadOption.3=Télécharger le fichier +settings.zipThreshold=Compresser les fichiers en ZIP lorsque le nombre de fichiers téléchargés dépasse +settings.signOut=Déconnexion +settings.accountSettings=Paramètres du compte + + + +changeCreds.title=Modifiez vos identifiants +changeCreds.header=Mettez à jour vos identifiants de connexion +changeCreds.changeUserAndPassword=Vous utilisez les identifiants de connexion par défaut. Veuillez entrer un nouveau mot de passe (et nom d\u2019utilisateur si vous le souhaitez) +changeCreds.newUsername=Nouveau nom d\u2019utilisateur +changeCreds.oldPassword=Mot de passe actuel +changeCreds.newPassword=Nouveau mot de passe +changeCreds.confirmNewPassword=Confirmer le nouveau mot de passe +changeCreds.submit=Soumettre les modifications + + + +account.title=Paramètres du compte +account.accountSettings=Paramètres du compte +account.adminSettings=Paramètres d\u2019administration \u2013 Voir et ajouter des utilisateurs +account.userControlSettings=Contrôle des paramètres des utilisateurs +account.changeUsername=Modifier le nom d\u2019utilisateur +account.changeUsername=Modifier le nom d\u2019utilisateur +account.password=Mot de passe de confirmation +account.oldPassword=Ancien mot de passe +account.newPassword=Nouveau mot de passe +account.changePassword=Modifier le mot de passe +account.confirmNewPassword=Confirmer votre nouveau mot de passe +account.signOut=Déconnexion +account.yourApiKey=Votre clé API +account.syncTitle=Synchroniser les paramètres du navigateur avec le compte +account.settingsCompare=Comparaison des paramètres +account.property=Propriété +account.webBrowserSettings=Paramètres du navigateur +account.syncToBrowser=Synchroniser\u00a0: Compte → Navigateur +account.syncToAccount=Synchroniser\u00a0: Compte ← Navigateur + + +adminUserSettings.title=Administration des paramètres des utilisateurs +adminUserSettings.header=Administration des paramètres des utilisateurs +adminUserSettings.admin=Administateur +adminUserSettings.user=Utilisateur +adminUserSettings.addUser=Ajouter un utilisateur +adminUserSettings.roles=Rôles +adminUserSettings.role=Rôle +adminUserSettings.actions=Actions +adminUserSettings.apiUser=Utilisateur API limité +adminUserSettings.webOnlyUser=Utilisateur Web uniquement +adminUserSettings.forceChange=Forcer l\u2019utilisateur à changer son nom d\u2019utilisateur/mot de passe lors de la connexion +adminUserSettings.submit=Ajouter ############# # HOME-PAGE # ############# -home.desc=Votre guichet unique hébergé localement pour tous vos besoins PDF. +home.desc=Votre application Web hébergée localement pour répondre à tous vos besoins PDF. -home.multiTool.title=Multi-outil PDF -home.multiTool.desc=Fusionner, faire pivoter, réorganiser et supprimer des pages -multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side +home.multiTool.title=Outil multifonction PDF +home.multiTool.desc=Fusionnez, faites pivoter, réorganisez et supprimez des pages. +multiTool.tags=outil multifonction,opération multifonction,interface utilisateur,glisser déposer,front-end,client side,interactif,intransigeant,déplacer,multi tool -home.merge.title=Fusionnez +home.merge.title=Fusionner home.merge.desc=Fusionnez facilement plusieurs PDF en un seul. -merge.tags=merge,Page operations,Back end,server side +merge.tags=fusionner,opérations sur les pages,backeend,server side,merge -home.split.title=Fractionner -home.split.desc=Diviser les PDF en plusieurs documents -split.tags=Page operations,divide,Multi Page,cut,server side +home.split.title=Diviser +home.split.desc=Divisez un PDF en plusieurs documents. +split.tags=opérations sur les pages,diviser,plusieurs pages,cut,server side,divide -home.rotate.title=Tourner +home.rotate.title=Pivoter home.rotate.desc=Faites pivoter facilement vos PDF. -rotate.tags=server side +rotate.tags=pivoter,server side,rotate -home.imageToPdf.title=Image au format PDF -home.imageToPdf.desc=Convertir une image (PNG, JPEG, GIF) en PDF. -imageToPdf.tags=conversion,img,jpg,picture,photo +home.imageToPdf.title=Image en PDF +home.imageToPdf.desc=Convertissez une image (PNG, JPEG, GIF) en PDF. +imageToPdf.tags=pdf,conversion,img,jpg,image,photo -home.pdfToImage.title=PDF vers image -home.pdfToImage.desc=Convertir un PDF en image. (PNG, JPEG, GIF) -pdfToImage.tags=conversion,img,jpg,picture,photo +home.pdfToImage.title=PDF en image +home.pdfToImage.desc=Convertissez un PDF en image (PNG, JPEG, GIF). +pdfToImage.tags=conversion,img,jpg,image,photo -home.pdfOrganiser.title=Organisateur -home.pdfOrganiser.desc=Supprimer/Réorganiser les pages dans n'importe quel ordre -pdfOrganiser.tags=duplex,even,odd,sort,move +home.pdfOrganiser.title=Organiser +home.pdfOrganiser.desc=Supprimez ou réorganisez les pages dans n\u2019importe quel ordre. +pdfOrganiser.tags=organiser,recto-verso,duplex,even,odd,sort,move -home.addImage.title=Ajouter une image au PDF -home.addImage.desc=Ajoute une image à un emplacement défini sur le PDF (Travail en cours) -addImage.tags=img,jpg,picture,photo +home.addImage.title=Ajouter une image +home.addImage.desc=Ajoutez une image à un emplacement défini sur un PDF. +addImage.tags=img,jpg,image,photo home.watermark.title=Ajouter un filigrane -home.watermark.desc=Ajoutez un filigrane personnalisé à votre document PDF. -watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo +home.watermark.desc=Ajoutez un filigrane personnalisé à votre PDF. +watermark.tags=texte,filigrane,label,propriété,droit d\u2019auteur,marque déposée,img,jpg,image,photo,copyright,trademark -home.permissions.title=Modifier les autorisations -home.permissions.desc=Modifier les permissions de votre document PDF -permissions.tags=read,write,edit,print +home.permissions.title=Modifier les permissions +home.permissions.desc=Modifiez les permissions de votre PDF. +permissions.tags=permissions,lire,écrire,modifier,imprimer,read,write,edit,print home.removePages.title=Supprimer -home.removePages.desc=Supprimez les pages inutiles de votre document PDF. -removePages.tags=Remove pages,delete pages +home.removePages.desc=Supprimez les pages inutiles de votre PDF. +removePages.tags=supprimer,remove,delete home.addPassword.title=Ajouter un mot de passe -home.addPassword.desc=Cryptez votre document PDF avec un mot de passe. -addPassword.tags=secure,security +home.addPassword.desc=Chiffrez votre PDF avec un mot de passe. +addPassword.tags=ajouter,sécurité,mot de passe,secure,security home.removePassword.title=Supprimer le mot de passe -home.removePassword.desc=Supprimez la protection par mot de passe de votre document PDF. -removePassword.tags=secure,Decrypt,security,unpassword,delete password +home.removePassword.desc=Supprimez la protection par mot de passe de votre PDF. +removePassword.tags=supprimer,sécurité,mot de passe,secure,decrypt,security,unpassword,delete password home.compressPdfs.title=Compresser -home.compressPdfs.desc=Compressez les PDF pour réduire leur taille de fichier. -compressPdfs.tags=squish,small,tiny +home.compressPdfs.desc=Compressez les PDF pour réduire leur tailles. +compressPdfs.tags=compresser,réduire,taille,squish,small,tiny home.changeMetadata.title=Modifier les métadonnées -home.changeMetadata.desc=Modifier/Supprimer/Ajouter des métadonnées d'un document PDF -changeMetadata.tags==Title,author,date,creation,time,publisher,producer,stats +home.changeMetadata.desc=Modifiez, supprimez ou ajoutez des métadonnées à un PDF. +changeMetadata.tags=métadonnées,titre,auteur,date,création,heure,éditeur,statistiques,title,author,date,creation,time,publisher,producer,stats,metadata -home.fileToPDF.title=Convertir un fichier en PDF -home.fileToPDF.desc=Convertissez presque n\u2019importe quel fichier en PDF (DOCX, PNG, XLS, PPT, TXT et plus) -fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint +home.fileToPDF.title=Fichier en PDF +home.fileToPDF.desc=Convertissez presque n\u2019importe quel fichiers en PDF (DOCX, PNG, XLS, PPT, TXT et plus). +fileToPDF.tags=convertion,transformation,format,document,image,slide,texte,conversion,office,docs,word,excel,powerpoint -home.ocr.title=Exécuter l'OCR sur les scans PDF et/ou de nettoyage -home.ocr.desc=Le nettoyage analyse et détecte le texte des images dans un PDF et le rajoute en tant que texte. -ocr.tags=recognition,text,image,scan,read,identify,detection,editable +home.ocr.title=OCR / Nettoyage des numérisations +home.ocr.desc=Utilisez l\u2019OCR pour analyser et détecter le texte des images d\u2019un PDF et le rajouter en temps que tel. +ocr.tags=ocr,reconnaissance,texte,image,numérisation,scan,read,identify,detection,editable home.extractImages.title=Extraire les images -home.extractImages.desc=Extrait toutes les images d\u2019un PDF et les enregistre au format zip -extractImages.tags=picture,photo,save,archive,zip,capture,grab +home.extractImages.desc=Extrayez toutes les images d\u2019un PDF et enregistrez-les dans un ZIP. +extractImages.tags=image,photo,save,archive,zip,capture,grab -home.pdfToPDFA.title=Convertir PDF en PDF/A -home.pdfToPDFA.desc=Convertir un PDF en PDF/A pour un stockage à long terme -pdfToPDFA.tags=archive,long-term,standard,conversion,storage,preservation +home.pdfToPDFA.title=PDF en PDF/A +home.pdfToPDFA.desc=Convertir un PDF en PDF/A pour un stockage à long terme. +pdfToPDFA.tags=convertion,archive,long-term,standard,conversion,storage,préservation,preservation -home.PDFToWord.title=PDF vers Word -home.PDFToWord.desc=Convertir les formats PDF en Word (DOC, DOCX et ODT) +home.PDFToWord.title=PDF en Word +home.PDFToWord.desc=Convertissez un PDF en Word (DOC, DOCX et ODT). PDFToWord.tags=doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile -home.PDFToPresentation.title=PDF vers présentation -home.PDFToPresentation.desc=Convertir des PDF en formats de présentation (PPT, PPTX et ODP) -PDFToPresentation.tags=slides,show,office,microsoft +home.PDFToPresentation.title=PDF en formats de présentation +home.PDFToPresentation.desc=Convertissez un PDF en format de présentation (PPT, PPTX et ODP). +PDFToPresentation.tags=présentation,slides,show,office,microsoft -home.PDFToText.title=PDF vers texte/RTF -home.PDFToText.desc=Convertir un PDF au format Texte ou RTF +home.PDFToText.title=PDF en RTF (texte) +home.PDFToText.desc=Convertissez un PDF au format RTF (texte). PDFToText.tags=richformat,richtextformat,rich text format -home.PDFToHTML.title=PDF vers HTML -home.PDFToHTML.desc=Convertir le PDF au format HTML -PDFToHTML.tags=web content,browser friendly +home.PDFToHTML.title=PDF en HTML +home.PDFToHTML.desc=Convertissez un PDF au format HTML. +PDFToHTML.tags=html,web content,browser friendly -home.PDFToXML.title=PDF vers XML -home.PDFToXML.desc=Convertir le PDF au format XML -PDFToXML.tags=data-extraction,structured-content,interop,transformation,convert +home.PDFToXML.title=PDF en XML +home.PDFToXML.desc=Convertissez un PDF au format XML. +PDFToXML.tags=xml,extraction de données,contenu structuré,interopérabilité,data-extraction,structured-content,interop,transformation,convert -home.ScannerImageSplit.title=Détecter/diviser les photos numérisées -home.ScannerImageSplit.desc=Divise plusieurs photos à partir d'une photo/PDF -ScannerImageSplit.tags=separate,auto-detect,scans,multi-photo,organize +home.ScannerImageSplit.title=Diviser les photos numérisées +home.ScannerImageSplit.desc=Divisez plusieurs photos à partir d\u2019une photo ou d\u2019un PDF. +ScannerImageSplit.tags=diviser,détecter automatiquement,numériser,separate,auto-detect,scans,multi-photo,organize -home.sign.title=Signe -home.sign.desc=Ajoute une signature au PDF par dessin, texte ou image -sign.tags=authorize,initials,drawn-signature,text-sign,image-signature +home.sign.title=Signer +home.sign.desc=Ajoutez une signature au PDF avec un dessin, du texte ou une image. +sign.tags=signer,authorize,initials,drawn-signature,text-sign,image-signature -home.flatten.title=Aplatir -home.flatten.desc=Supprimer tous les éléments et formulaires interactifs d'un PDF -flatten.tags=static,deactivate,non-interactive,streamline +home.flatten.title=Rendre inerte +home.flatten.desc=Supprimez tous les éléments et formulaires interactifs d\u2019un PDF. +flatten.tags=inerte,static,deactivate,non-interactive,streamline home.repair.title=Réparer -home.repair.desc=Essaye de réparer un PDF corrompu/cassé -repair.tags=fix,restore,correction,recover +home.repair.desc=Essayez de réparer un PDF corrompu ou cassé. +repair.tags=réparer,restaurer,corriger,récupérer,fix,restore,correction,recover home.removeBlanks.title=Supprimer les pages vierges -home.removeBlanks.desc=Détecte et supprime les pages vierges d'un document -removeBlanks.tags=cleanup,streamline,non-content,organize +home.removeBlanks.desc=Détectez et supprimez les pages vierges d\u2019un PDF. +removeBlanks.tags=pages vierges,supprimer,nettoyer,cleanup,streamline,non-content,organize home.compare.title=Comparer -home.compare.desc=Compare et affiche les différences entre 2 documents PDF -compare.tags=differentiate,contrast,changes,analysis +home.compare.desc=Comparez et visualisez les différences entre deux PDF. +compare.tags=comparer,analyser,differentiate,contrast,changes,analysis -home.certSign.title=Sign with Certificate -home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) -certSign.tags=authenticate,PEM,P12,official,encrypt +home.certSign.title=Signer avec un certificat +home.certSign.desc=Signez un PDF avec un certificat ou une clé (PEM/P12). +certSign.tags=signer,chiffrer,certificat,authenticate,PEM,P12,official,encrypt -home.pageLayout.title=Multi-Page Layout -home.pageLayout.desc=Merge multiple pages of a PDF document into a single page -pageLayout.tags=merge,composite,single-view,organize +home.pageLayout.title=Fusionner des pages +home.pageLayout.desc=Fusionnez plusieurs pages d\u2019un PDF en une seule. +pageLayout.tags=fusionner,merge,composite,single-view,organize -home.scalePages.title=Adjust page size/scale -home.scalePages.desc=Change the size/scale of page and/or its contents. -scalePages.tags=resize,modify,dimension,adapt +home.scalePages.title=Ajuster l\u2019échelle ou la taille +home.scalePages.desc=Modifiez la taille ou l\u2019échelle d\u2019une page et/ou de son contenu. +scalePages.tags=ajuster,redimensionner,resize,modify,dimension,adapt -home.pipeline.title=Pipeline (Advanced) -home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts -pipeline.tags=automate,sequence,scripted,batch-process +home.pipeline.title=Pipeline (avancé) +home.pipeline.desc=Exécutez plusieurs actions sur les PDF en définissant des scripts de pipeline. +pipeline.tags=automatiser,séquencer,automate,sequence,scripted,batch-process -home.add-page-numbers.title=Add Page Numbers -home.add-page-numbers.desc=Add Page numbers throughout a document in a set location -add-page-numbers.tags=paginate,label,organize,index +home.add-page-numbers.title=Ajouter des numéros de page +home.add-page-numbers.desc=Ajoutez des numéros de page dans un PDF à un emplacement défini. +add-page-numbers.tags=paginer,numéros,étiqueter,paginate,label,organize,index -home.auto-rename.title=Auto Rename PDF File -home.auto-rename.desc=Auto renames a PDF file based on its detected header -auto-rename.tags=auto-detect,header-based,organize,relabel +home.auto-rename.title=Renommer automatiquement +home.auto-rename.desc=Renommez automatiquement un fichier PDF en fonction de son en-tête détecté. +auto-rename.tags=renommer,détection automatique,réétiqueter,auto-detect,header-based,organize,relabel -home.adjust-contrast.title=Adjust Colors/Contrast -home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF -adjust-contrast.tags=color-correction,tune,modify,enhance +home.adjust-contrast.title=Ajuster les couleurs +home.adjust-contrast.desc=Ajustez le contraste, la saturation et la luminosité d\u2019un PDF. +adjust-contrast.tags=ajuster,couleurs,amélioration,color-correction,tune,modify,enhance -home.crop.title=Crop PDF -home.crop.desc=Crop a PDF to reduce its size (maintains text!) -crop.tags=trim,shrink,edit,shape +home.crop.title=Redimensionner +home.crop.desc=Redimmensionnez un PDF pour réduire sa taille (en conservant le texte\u00a0!). +crop.tags=redimensionner,trim,shrink,edit,shape -home.autoSplitPDF.title=Auto Split Pages -home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code -autoSplitPDF.tags=QR-based,separate,scan-segment,organize +home.autoSplitPDF.title=Séparer automatiquement les pages +home.autoSplitPDF.desc=Séparez automatiquement le PDF numérisé avec le code QR du diviseur de page numérisé. +autoSplitPDF.tags=séparer,QR-based,separate,scan-segment,organize -home.sanitizePdf.title=Sanitize -home.sanitizePdf.desc=Remove scripts and other elements from PDF files -sanitizePdf.tags=clean,secure,safe,remove-threats +home.sanitizePdf.title=Assainir +home.sanitizePdf.desc=Supprimez les scripts et autres éléments des PDF. +sanitizePdf.tags=assainir,sécurisé,clean,secure,safe,remove-threats -home.URLToPDF.title=URL/Website To PDF -home.URLToPDF.desc=Converts any http(s)URL to PDF -URLToPDF.tags=web-capture,save-page,web-to-doc,archive +home.URLToPDF.title=URL en PDF +home.URLToPDF.desc=Convertissez n\u2019importe quelle URL http(s) en PDF. +URLToPDF.tags=pdf,contenu Web,save-page,web-to-doc,archive -home.HTMLToPDF.title=HTML to PDF -home.HTMLToPDF.desc=Converts any HTML file or zip to PDF -HTMLToPDF.tags=markup,web-content,transformation,convert +home.HTMLToPDF.title=HTML en PDF +home.HTMLToPDF.desc=Convertissez n\u2019importe quel fichier HTML ou ZIP en PDF. +HTMLToPDF.tags=html,markup,contenu Web,transformation,convert -home.MarkdownToPDF.title=Markdown to PDF -home.MarkdownToPDF.desc=Converts any Markdown file to PDF -MarkdownToPDF.tags=markup,web-content,transformation,convert +home.MarkdownToPDF.title=Markdown en PDF +home.MarkdownToPDF.desc=Convertissez n\u2019importe quel fichier Markdown en PDF. +MarkdownToPDF.tags=markdown,markup,contenu Web,transformation,convert -home.getPdfInfo.title=Get ALL Info on PDF -home.getPdfInfo.desc=Grabs any and all information possible on PDFs -getPdfInfo.tags=infomation,data,stats,statistics +home.getPdfInfo.title=Récupérer les informations +home.getPdfInfo.desc=Récupérez toutes les informations possibles sur un PDF. +getPdfInfo.tags=récupérer,infomation,data,stats,statistics -home.extractPage.title=Extract page(s) -home.extractPage.desc=Extracts select pages from PDF -extractPage.tags=extract +home.extractPage.title=Extraire des pages +home.extractPage.desc=Extrayez certaines pages du PDF. +extractPage.tags=extraire,extract -home.PdfToSinglePage.title=PDF to Single Large Page -home.PdfToSinglePage.desc=Merges all PDF pages into one large single page -PdfToSinglePage.tags=single page +home.PdfToSinglePage.title=Fusionner en une seule page +home.PdfToSinglePage.desc=Fusionnez toutes les pages PDF en une seule grande page. +PdfToSinglePage.tags=fusionner,merge,une seule page,single page -########################## -### TODO: Translate ### -########################## -home.showJS.title=Show Javascript -home.showJS.desc=Searches and displays any JS injected into a PDF -showJS.tags=JS +home.showJS.title=Afficher le JavaScript +home.showJS.desc=Recherche et affiche tout JavaScript injecté dans un PDF. +showJS.tags=caviarder,redact,auto + +home.autoRedact.title=Caviarder automatiquement +home.autoRedact.desc=Caviardez automatiquement les informations sensibles d\u2019un PDF. +showJS.tags=caviarder,redact,auto ########################### # # # WEB PAGES # # # ########################### +#login +login.title=Connexion +login.signin=Connexion +login.rememberme=Se souvenir de moi +login.invalid=Nom d\u2019utilisateur ou mot de passe invalide. +login.locked=Votre compte a été verrouillé. +login.signinTitle=Veuillez vous connecter + + +#auto-redact +autoRedact.title=Caviarder automatiquement +autoRedact.header=Caviarder automatiquement +autoRedact.colorLabel=Couleur +autoRedact.textsToRedactLabel=Texte à caviarder (séparé par des lignes) +autoRedact.textsToRedactPlaceholder=ex. \nConfidentiel \nTop secret +autoRedact.useRegexLabel=Utiliser une Regex +autoRedact.wholeWordSearchLabel=Recherche de mots entiers +autoRedact.customPaddingLabel=Marge intérieure supplémentaire +autoRedact.convertPDFToImageLabel=Convertir un PDF en PDF-Image (utilisé pour supprimer le texte en arrière-plan) +autoRedact.submitButton=Caviarder + + #showJS -########################## -### TODO: Translate ### -########################## -showJS.title=Show Javascript -showJS.header=Show Javascript -showJS.downloadJS=Download Javascript -showJS.submit=Show +showJS.title=Afficher le JavaScript +showJS.header=Afficher le JavaScript +showJS.downloadJS=Télécharger le JavaScript +showJS.submit=Afficher #pdfToSinglePage -pdfToSinglePage.title=PDF To Single Page -pdfToSinglePage.header=PDF To Single Page -pdfToSinglePage.submit=Convert To Single Page +pdfToSinglePage.title=Fusionner des pages +pdfToSinglePage.header=Fusionner des pages +pdfToSinglePage.submit=Convertir en une seule page #pageExtracter -pageExtracter.title=Extract Pages -pageExtracter.header=Extract Pages -pageExtracter.submit=Extract +pageExtracter.title=Extraire des pages +pageExtracter.header=Extraire des pages +pageExtracter.submit=Extraire #getPdfInfo -getPdfInfo.title=Get Info on PDF -getPdfInfo.header=Get Info on PDF -getPdfInfo.submit=Get Info -getPdfInfo.downloadJson=Download JSON +getPdfInfo.title=Récupérer les informations +getPdfInfo.header=Récupérer les informations +getPdfInfo.submit=Récupérer les informations +getPdfInfo.downloadJson=Télécharger le JSON #markdown-to-pdf -MarkdownToPDF.title=Markdown To PDF -MarkdownToPDF.header=Markdown To PDF -MarkdownToPDF.submit=Convert -MarkdownToPDF.help=Work in progress -MarkdownToPDF.credit=Uses WeasyPrint +MarkdownToPDF.title=Markdown en PDF +MarkdownToPDF.header=Markdown en PDF +MarkdownToPDF.submit=Convertir +MarkdownToPDF.help=(Travail en cours). +MarkdownToPDF.credit=Utilise WeasyPrint. #url-to-pdf -URLToPDF.title=URL To PDF -URLToPDF.header=URL To PDF -URLToPDF.submit=Convert -URLToPDF.credit=Uses WeasyPrint +URLToPDF.title=URL en PDF +URLToPDF.header=URL en PDF +URLToPDF.submit=Convertir +URLToPDF.credit=Utilise WeasyPrint. #html-to-pdf -HTMLToPDF.title=HTML To PDF -HTMLToPDF.header=HTML To PDF -HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required -HTMLToPDF.submit=Convert -HTMLToPDF.credit=Uses WeasyPrint +HTMLToPDF.title=HTML en PDF +HTMLToPDF.header=HTML en PDF +HTMLToPDF.help=Accepte les fichiers HTML et les ZIP contenant du HTML, du CSS, des images, etc. (requis). +HTMLToPDF.submit=Convertir +HTMLToPDF.credit=Utilise WeasyPrint. #sanitizePDF -sanitizePDF.title=Sanitize PDF -sanitizePDF.header=Sanitize a PDF file -sanitizePDF.selectText.1=Remove JavaScript actions -sanitizePDF.selectText.2=Remove embedded files -sanitizePDF.selectText.3=Remove metadata -sanitizePDF.selectText.4=Remove links -sanitizePDF.selectText.5=Remove fonts -sanitizePDF.submit=Sanitize PDF +sanitizePDF.title=Assainir +sanitizePDF.header=Assainir +sanitizePDF.selectText.1=Supprimer les actions JavaScript +sanitizePDF.selectText.2=Supprimer les fichiers intégrés +sanitizePDF.selectText.3=Supprimer les métadonnées +sanitizePDF.selectText.4=Supprimer les liens +sanitizePDF.selectText.5=Supprimer les polices +sanitizePDF.submit=Assainir #addPageNumbers -addPageNumbers.title=Add Page Numbers -addPageNumbers.header=Add Page Numbers -addPageNumbers.selectText.1=Select PDF file: -addPageNumbers.selectText.2=Margin Size +addPageNumbers.title=Ajouter des numéros de page +addPageNumbers.header=Ajouter des numéros de page +addPageNumbers.selectText.1=Sélectionnez le fichier PDF +addPageNumbers.selectText.2=Taille de la marge addPageNumbers.selectText.3=Position -addPageNumbers.selectText.4=Starting Number -addPageNumbers.selectText.5=Pages to Number -addPageNumbers.selectText.6=Custom Text -addPageNumbers.submit=Add Page Numbers +addPageNumbers.selectText.4=Numéro de départ +addPageNumbers.selectText.5=Pages à numéroter +addPageNumbers.selectText.6=Texte personnalisé +addPageNumbers.customTextDesc=Texte personnalisé +addPageNumbers.numberPagesDesc=Quelles pages numéroter, par défaut 'all' (toutes les pages), accepte également 1-5 ou 2,5,9, etc. +addPageNumbers.customNumberDesc=La valeur par défaut est '{n}', accepte également 'Page {n} sur {total}', 'Texte-{n}', '{filename}-{n} +addPageNumbers.submit=Ajouter les numéros de page #auto-rename -auto-rename.title=Auto Rename -auto-rename.header=Auto Rename PDF -auto-rename.submit=Auto Rename +auto-rename.title=Renommer automatiquement +auto-rename.header=Renommer automatiquement +auto-rename.submit=Renommer automatiquement #adjustContrast -adjustContrast.title=Adjust Contrast -adjustContrast.header=Adjust Contrast -adjustContrast.contrast=Contrast: -adjustContrast.brightness=Brightness: -adjustContrast.saturation=Saturation: -adjustContrast.download=Download +adjustContrast.title=Ajuster les couleurs +adjustContrast.header=Ajuster les couleurs +adjustContrast.contrast=Contraste +adjustContrast.brightness=Luminosité +adjustContrast.saturation=Saturation +adjustContrast.download=Télécharger #crop -crop.title=Crop -crop.header=Crop Image -crop.submit=Submit +crop.title=Redimensionner +crop.header=Redimensionner +crop.submit=Envoyer #autoSplitPDF -autoSplitPDF.title=Auto Split PDF -autoSplitPDF.header=Auto Split PDF -autoSplitPDF.description=Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed. -autoSplitPDF.selectText.1=Print out some divider sheets from below (Black and white is fine). -autoSplitPDF.selectText.2=Scan all your documents at once by inserting the divider sheet between them. -autoSplitPDF.selectText.3=Upload the single large scanned PDF file and let Stirling PDF handle the rest. -autoSplitPDF.selectText.4=Divider pages are automatically detected and removed, guaranteeing a neat final document. -autoSplitPDF.formPrompt=Submit PDF containing Stirling-PDF Page dividers: -autoSplitPDF.duplexMode=Duplex Mode (Front and back scanning) -autoSplitPDF.dividerDownload1=Download 'Auto Splitter Divider (minimal).pdf' -autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf' -autoSplitPDF.submit=Submit +autoSplitPDF.title=Séparer automatiquement les pages +autoSplitPDF.header=Séparer automatiquement les pages +autoSplitPDF.description=Imprimez, insérez, numérisez, téléchargez et laissez-nous séparer automatiquement vos documents. Aucun travail de tri manuel nécessaire. +autoSplitPDF.selectText.1=Imprimez des feuilles de séparation ci-dessous (le mode noir et blanc convient). +autoSplitPDF.selectText.2=Numérisez tous vos documents en une seule fois en insérant les feuilles intercalaires entre eux. +autoSplitPDF.selectText.3=Téléchargez le fichier PDF numérisé et laissez Stirling PDF s\u2019occuper du reste. +autoSplitPDF.selectText.4=Les feuilles de séparation sont automatiquement détectées et supprimées, garantissant un document final soigné. +autoSplitPDF.formPrompt=PDF contenant des feuilles de séparation de Stirling PDF\u00a0: +autoSplitPDF.duplexMode=Mode recto-verso +autoSplitPDF.dividerDownload1=Auto Splitter Divider (minimal).pdf +autoSplitPDF.dividerDownload2=Auto Splitter Divider (with instructions).pdf +autoSplitPDF.submit=Séparer #pipeline @@ -385,350 +475,343 @@ pipeline.title=Pipeline #pageLayout -pageLayout.title=Multi Page Layout -pageLayout.header=Multi Page Layout -pageLayout.pagesPerSheet=Pages per sheet: -pageLayout.submit=Submit +pageLayout.title=Fusionner des pages +pageLayout.header=Fusionner des pages +pageLayout.pagesPerSheet=Pages par feuille +pageLayout.addBorder=Ajouter des bordures +pageLayout.submit=Fusionner #scalePages -scalePages.title=Adjust page-scale -scalePages.header=Adjust page-scale -scalePages.pageSize=Size of a page of the document. -scalePages.scaleFactor=Zoom level (crop) of a page. -scalePages.submit=Submit +scalePages.title=Ajuster la taille ou l\u2019échelle +scalePages.header=Ajuster la taille ou l\u2019échelle +scalePages.pageSize=Taille d\u2019une page du document +scalePages.scaleFactor=Niveau de zoom (recadrage) d\u2019une page +scalePages.submit=Ajuster #certSign -certSign.title=Signature du certificat -certSign.header=Signer un PDF avec votre certificat (Travail en cours) -certSign.selectPDF=Sélectionnez un fichier PDF à signer : -certSign.selectKey=Sélectionnez votre fichier de clé privée (format PKCS#8, peut être .pem ou .der) : -certSign.selectCert=Sélectionnez votre fichier de certificat (format X.509, peut être .pem ou .der) : -certSign.selectP12=Sélectionnez votre fichier de magasin de clés PKCS#12 (.p12 ou .pfx) (facultatif, s'il est fourni, il doit contenir votre clé privée et votre certificat) : +certSign.title=Signer avec un certificat +certSign.header=Signer avec un certificat (Travail en cours) +certSign.selectPDF=PDF à signer +certSign.selectKey=Fichier de clé privée (format PKCS#8, peut être .pem ou .der) +certSign.selectCert=Fichier de certificat (format X.509, peut être .pem ou .der) +certSign.selectP12=Fichier keystore de clés PKCS#12 (.p12 ou .pfx) (facultatif, s\u2019il n\u2019est fourni, il doit contenir votre clé privée et votre certificat) certSign.certType=Type de certificat -certSign.password=Entrez votre mot de passe de keystore ou de clé privée (le cas échéant) : +certSign.password=Mot de passe keystore ou clé privée le cas échéant certSign.showSig=Afficher la signature certSign.reason=Raison certSign.location=Emplacement certSign.name=Nom -certSign.submit=Signer le PDF +certSign.submit=Signer #removeBlanks -removeBlanks.title=Supprimer les blancs +removeBlanks.title=Supprimer les pages vierges removeBlanks.header=Supprimer les pages vierges -removeBlanks.threshold=Seuil : -removeBlanks.thresholdDesc=Seuil pour déterminer à quel point un pixel blanc doit être blanc -removeBlanks.whitePercent=Pourcentage blanc (%) : -removeBlanks.whitePercentDesc=Pourcentage de page qui doit être blanche pour être supprimée -removeBlanks.submit=Supprimer les blancs +removeBlanks.threshold=Seuil de blancheur des pixels +removeBlanks.thresholdDesc=Seuil pour déterminer à quel point un pixel blanc doit être blanc pour être classé comme «\u00a0blanc\u00a0» (0 = noir, 255 = blanc pur). +removeBlanks.whitePercent=Pourcentage de blanc +removeBlanks.whitePercentDesc=Pourcentage de la page qui doit contenir des pixels « blancs » à supprimer. +removeBlanks.submit=Supprimer les pages vierges #compare compare.title=Comparer -compare.header=Comparer des PDF +compare.header=Comparer compare.document.1=Document 1 compare.document.2=Document 2 compare.submit=Comparer #sign -sign.title=Signe -sign.header=Signer des PDF -sign.upload=Télécharger l'image +sign.title=Signer +sign.header=Signer +sign.upload=Télécharger une image sign.draw=Dessiner une signature -sign.text=Saisie de texte +sign.text=Saisir de texte sign.clear=Effacer sign.add=Ajouter #repair repair.title=Réparer -repair.header=Réparer les PDF +repair.header=Réparer repair.submit=Réparer #flatten -flatten.title=Aplatir -flatten.header=Aplatir les PDF -flatten.submit=Aplatir +flatten.title=Rendre inerte +flatten.header=Rendre inerte +flatten.submit=Rendre inerte #ScannerImageSplit -ScannerImageSplit.selectText.1=Seuil d'angle : -ScannerImageSplit.selectText.2=Définit l'angle absolu minimum requis pour la rotation de l'image (par défaut : 10). -ScannerImageSplit.selectText.3=Tolérance : -ScannerImageSplit.selectText.4=Détermine la plage de variation de couleur autour de la couleur d'arrière-plan estimée (par défaut : 30). -ScannerImageSplit.selectText.5=Zone minimale : -ScannerImageSplit.selectText.6=Définit le seuil de zone minimum pour une photo (par défaut : 10000). -ScannerImageSplit.selectText.7=Zone de contour minimale : -ScannerImageSplit.selectText.8=Définit le seuil de zone de contour minimum pour une photo -ScannerImageSplit.selectText.9=Taille de la bordure : -ScannerImageSplit.selectText.10=Définit la taille de la bordure ajoutée et supprimée pour éviter les bordures blanches dans la sortie (par défaut : 1). +ScannerImageSplit.selectText.1=Seuil de rotation +ScannerImageSplit.selectText.2=Définit l\u2019angle absolu minimum requis pour la rotation de l\u2019image (par défaut\u00a0: 10). +ScannerImageSplit.selectText.3=Tolérance +ScannerImageSplit.selectText.4=Détermine la plage de variation de couleur autour de la couleur d\u2019arrière-plan estimée (par défaut\u00a0: 20). +ScannerImageSplit.selectText.5=Surface minimale +ScannerImageSplit.selectText.6=Définit la surface minimale pour une photo (par défaut\u00a0: 8\u202f000). +ScannerImageSplit.selectText.7=Surface de contour minimale +ScannerImageSplit.selectText.8=Définit la surface de contour minimale pour une photo (par défaut\u00a0: 500). +ScannerImageSplit.selectText.9=Taille de la bordure +ScannerImageSplit.selectText.10=Définit la taille de la bordure ajoutée et supprimée pour éviter les bordures blanches dans la sortie (par défaut\u00a0: 1). + - #OCR -ocr.title=OCR / Nettoyage de numérisation -ocr.header=Nettoyage des scans / OCR (reconnaissance optique des caractères) -ocr.selectText.1=Sélectionnez les langues à détecter dans le PDF (celles répertoriées sont celles actuellement détectées) : -ocr.selectText.2=Produire un fichier texte contenant du texte OCR avec le PDF OCR -ocr.selectText.3=Les pages correctes ont été numérisées à un angle oblique en les remettant en place -ocr.selectText.4=Nettoyer la page pour qu'il soit moins probable que l'OCR trouve du texte dans le bruit de fond. (Pas de changement de sortie) -ocr.selectText.5=Nettoyer la page afin qu'il soit moins probable que l'OCR trouve du texte dans le bruit de fond, maintient le nettoyage dans la sortie. -ocr.selectText.6=Ignore les pages contenant du texte interactif, seulement les pages OCR qui sont des images -ocr.selectText.7=Forcer l'OCR, OCR chaque page supprimera tous les éléments de texte d'origine -ocr.selectText.8=Normal (Erreur si le PDF contient du texte) -ocr.selectText.9=Paramètres supplémentaires -ocr.selectText.10=Mode ROC -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.title=OCR / Nettoyage des numérisations +ocr.header=OCR (Reconnaissance optique de caractères) / Nettoyage des numérisations +ocr.selectText.1=Langues à détecter dans le PDF (celles listées sont celles actuellement détectées) +ocr.selectText.2=Produire un fichier texte contenant le texte détecté à côté du PDF +ocr.selectText.3=Corriger les pages qui ont été numérisées à un angle oblique en les remettant en place +ocr.selectText.4=Nettoyer la page afin qu\u2019il soit moins probable que l\u2019OCR trouve du texte dans le bruit de fond, sans modifier la sortie +ocr.selectText.5=Nettoyer la page afin qu\u2019il soit moins probable que l\u2019OCR trouve du texte dans le bruit de fond, en modifiant la sortie +ocr.selectText.6=Ignorer les pages contenant du texte interactif, n\u2019analyser que les pages qui sont des images +ocr.selectText.7=Forcer l\u2019OCR, analyser chaque page et supprimer tous les éléments de texte d\u2019origine +ocr.selectText.8=Normal (génère une erreur si le PDF contient du texte) +ocr.selectText.9=Paramètres additionnels +ocr.selectText.10=Mode OCR +ocr.selectText.11=Supprimer les images après l\u2019OCR (Supprime TOUTES les images, utile uniquement si elles font partie de l\u2019étape de conversion) ocr.selectText.12=Type de rendu (avancé) -ocr.help=Veuillez lire cette documentation pour savoir comment l'utiliser pour d'autres langues et/ou une utilisation non dans docker -ocr.credit=Ce service utilise OCRmyPDF et Tesseract pour l'OCR. -ocr.submit=Traiter PDF avec OCR +ocr.help=Veuillez lire cette documentation pour savoir comment utiliser l\u2019OCR pour d\u2019autres langues ou une utilisation hors Docker\u00a0: +ocr.credit=Ce service utilise OCRmyPDF et Tesseract pour l\u2019OCR. +ocr.submit=Traiter #extractImages extractImages.title=Extraire les images extractImages.header=Extraire les images -extractImages.selectText=Sélectionner le format d'image pour convertir les images extraites en +extractImages.selectText=Format d\u2019image dans lequel convertir les images extraites extractImages.submit=Extraire #File to PDF -fileToPDF.title=Fichier au format PDF -fileToPDF.header=Convertir n'importe quel fichier au format PDF +fileToPDF.title=Fichier en PDF +fileToPDF.header=Convertir un fichier en PDF fileToPDF.credit=Ce service utilise LibreOffice et Unoconv pour la conversion de fichiers. -fileToPDF.supportedFileTypes=Les types de fichiers pris en charge doivent inclure les éléments ci-dessous, mais pour une liste complète et mise à jour des formats pris en charge, veuillez vous référer à la documentation de LibreOffice. -fileToPDF.submit=Convertir en PDF +fileToPDF.supportedFileTypes=Les types de fichiers pris en charge doivent inclure les éléments ci-dessous, mais pour une liste complète et mise à jour des formats pris en charge, veuillez vous reporter à la documentation de LibreOffice. +fileToPDF.submit=Convertir #compress compress.title=Compresser -compress.header=Compresser le PDF -compress.credit=Ce service utilise Ghostscript pour PDF Compress/Optimisation. -compress.selectText.1=Mode manuel - De 1 à 4 -compress.selectText.2=Niveau d'optimisation : -compress.selectText.3=4 (Terrible pour les images de texte) -compress.selectText.4=Mode automatique - Ajuste automatiquement la qualité pour obtenir le PDF à la taille exacte -compress.selectText.5=Taille PDF attendue (par exemple, 25 Mo, 10,8 Mo, 25 Ko) +compress.header=Compresser +compress.credit=Ce service utilise Ghostscript pour la compression et l\u2019optimisation des PDF. +compress.selectText.1=Mode manuel \u2013 de 1 à 4 +compress.selectText.2=Niveau d\u2019optimisation +compress.selectText.3=4 (terrible pour les images textuelles) +compress.selectText.4=Mode automatique \u2013 ajuste automatiquement la qualité pour obtenir le PDF à la taille exacte +compress.selectText.5=Taille PDF attendue (par exemple, 25\u202fMo, 10,8\u202fMo, 25\u202fKo) compress.submit=Compresser #Add image addImage.title=Ajouter une image -addImage.header=Ajouter une image au PDF -addImage.everyPage=Chaque page? -addImage.upload=Ajouter une image +addImage.header=Ajouter une image +addImage.everyPage=Toutes les pages\u00a0? +addImage.upload=Télécharger une image addImage.submit=Ajouter une image #merge merge.title=Fusionner -merge.header=Fusionner plusieurs PDF (2+) +merge.header=Fusionner plusieurs PDF +merge.sortByName=Trier par nom +merge.sortByDate=Trier par date merge.submit=Fusionner #pdfOrganiser -pdfOrganiser.title=Organisateur de pages -pdfOrganiser.header=Organisateur de pages PDF -pdfOrganiser.submit=Réorganiser les pages +pdfOrganiser.title=Organiser +pdfOrganiser.header=Organiser les pages +pdfOrganiser.submit=Organiser #multiTool -multiTool.title=Multi-outil PDF -multiTool.header=Outil multiple PDF +multiTool.title=Outil multifonction PDF +multiTool.header=Outil multifonction PDF #pageRemover -pageRemover.title=Suppresseur de pages -pageRemover.header=Outil de suppression de pages PDF -pageRemover.pagesToDelete=Pages à supprimer (Entrez une liste de numéros de page séparés par des virgules): -pageRemover.submit=Supprimer des pages +pageRemover.title=Supprimer des pages +pageRemover.header=Supprimer des pages +pageRemover.pagesToDelete=Pages à supprimer (entrez une liste de numéros de pages séparés par des virgules)\u00a0: +pageRemover.submit=Supprimer les pages #rotate -rotate.title=Faire pivoter le PDF -rotate.header=Faire pivoter le PDF -rotate.selectAngle=Sélectionner l'angle de rotation (en multiples de 90 degrés) : -rotate.submit=Rotation +rotate.title=Pivoter +rotate.header=Pivoter +rotate.selectAngle=Angle de rotation (par multiples de 90\u202fdegrés) +rotate.submit=Pivoter #merge -split.title=Fractionner le PDF -split.header=Diviser le PDF -split.desc.1=Les numéros que vous sélectionnez sont le numéro de page sur lequel vous souhaitez faire un fractionnement. -split.desc.2=Ainsi, la sélection de 1,3,7-8 diviserait un document de 10 pages en 6 PDF distincts avec : -split.desc.3=Document #1 : Page 1 -split.desc.4=Document #2 : Pages 2 et 3 -split.desc.5=Document #3 : Pages 4, 5 et 6 -split.desc.6=Document #4 : Page 7 -split.desc.7=Document #5 : Page 8 -split.desc.8=Document #6 : Pages 9 et 10 -split.splitPages=Entrez les pages sur lesquelles fractionner : +split.title=Diviser +split.header=Diviser +split.desc.1=Les numéros que vous sélectionnez sont le numéro de page sur lequel vous souhaitez faire une division +split.desc.2=Ainsi, la sélection de 1,3,7-8 diviserait un document de 10 pages en 6 PDF distincts avec\u00a0: +split.desc.3=Document #1: Page 1 +split.desc.4=Document #2: Page 2 et 3 +split.desc.5=Document #3: Page 4, 5 et 6 +split.desc.6=Document #4: Page 7 +split.desc.7=Document #5: Page 8 +split.desc.8=Document #6: Page 9 et 10 +split.splitPages=Pages sur lesquelles diviser split.submit=Diviser #merge -imageToPDF.title=Image au format PDF -imageToPDF.header=Image au format PDF +imageToPDF.title=Image en PDF +imageToPDF.header=Image en PDF imageToPDF.submit=Convertir -imageToPDF.selectText.1=Étirer pour s'adapter +imageToPDF.selectLabel=Options d\u2019ajustement de l\u2019image +imageToPDF.fillPage=Remplir la page +imageToPDF.fitDocumentToImage=Ajuster la page à l\u2019image +imageToPDF.maintainAspectRatio=Maintenir les proportions imageToPDF.selectText.2=Rotation automatique du PDF -imageToPDF.selectText.3=Logique de fichiers multiples (activé uniquement si vous travaillez avec plusieurs images) +imageToPDF.selectText.3=Logique multi-fichiers (uniquement activée si vous travaillez avec plusieurs images) imageToPDF.selectText.4=Fusionner en un seul PDF -imageToPDF.selectText.5=Convertir en PDFs distincts - - +imageToPDF.selectText.5=Convertir en PDF séparés + + #pdfToImage -pdfToImage.title=PDF vers image -pdfToImage.header=PDF vers image -pdfToImage.selectText=Format d'image -pdfToImage.singleOrMultiple=Type de résultat d'image +pdfToImage.title=Image en PDF +pdfToImage.header=Image en PDF +pdfToImage.selectText=Format d\u2019image +pdfToImage.singleOrMultiple=Type de résultat pdfToImage.single=Une seule grande image pdfToImage.multi=Plusieurs images -pdfToImage.colorType=Type de couleur +pdfToImage.colorType=Type d\u2019impression pdfToImage.color=Couleur pdfToImage.grey=Niveaux de gris -pdfToImage.blackwhite=Noir et Blanc (Peut perdre des données !) +pdfToImage.blackwhite=Noir et blanc (peut engendre une perde de données\u00a0!) pdfToImage.submit=Convertir #addPassword addPassword.title=Ajouter un mot de passe -addPassword.header=Ajouter un mot de passe (chiffrer) -addPassword.selectText.1=Sélectionnez le PDF à chiffrer -addPassword.selectText.2=Mot de passe +addPassword.header=Ajouter un mot de passe +addPassword.selectText.1=PDF à chiffrer +addPassword.selectText.2=Mot de passe de l\u2019utilisateur addPassword.selectText.3=Longueur de la clé de chiffrement -addPassword.selectText.4=Les valeurs supérieures sont plus fortes, mais les valeurs inférieures ont une meilleure compatibilité. -addPassword.selectText.5=Autorisations à définir -addPassword.selectText.6=Empêcher l'assemblage du document -addPassword.selectText.7=Empêcher l'extraction de contenu -addPassword.selectText.8=Empêcher l'extraction pour l'accessibilité -addPassword.selectText.9=Empêcher de remplir le formulaire +addPassword.selectText.4=Les valeurs plus élevées sont plus fortes, mais les valeurs plus faibles ont une meilleure compatibilité. +addPassword.selectText.5=Autorisations à définir (utilisation recommandée avec le mot de passe du propriétaire) +addPassword.selectText.6=Empêcher l\u2019assemblage du document +addPassword.selectText.7=Empêcher l\u2019extraction de contenu +addPassword.selectText.8=Empêcher l\u2019extraction pour l\u2019accessibilité +addPassword.selectText.9=Empêcher de remplir les formulaires addPassword.selectText.10=Empêcher la modification addPassword.selectText.11=Empêcher la modification des annotations -addPassword.selectText.12=Empêcher l'impression -addPassword.selectText.13=Empêcher l'impression de différents formats -addPassword.selectText.14=Owner Password -addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) -addPassword.selectText.16=Restricts the opening of the document itself -addPassword.submit=Crypter +addPassword.selectText.12=Empêcher l\u2019impression +addPassword.selectText.13=Empêcher l\u2019impression des différents formats +addPassword.selectText.14=Mot de passe du propriétaire +addPassword.selectText.15=Restreint ce qui peut être fait avec le document une fois qu\u2019il est ouvert (non pris en charge par tous les lecteurs). +addPassword.selectText.16=Restreint l\u2019ouverture du document lui-même. +addPassword.submit=Chiffrer #watermark watermark.title=Ajouter un filigrane watermark.header=Ajouter un filigrane -watermark.selectText.1=Sélectionnez le PDF auquel ajouter un filigrane : -watermark.selectText.2=Texte du filigrane : -watermark.selectText.3=Taille de la police : -watermark.selectText.4=Rotation (0-360) : -watermark.selectText.5=widthSpacer (Espace entre chaque filigrane horizontalement) : -watermark.selectText.6=heightSpacer (Espace entre chaque filigrane verticalement) : -watermark.selectText.7=Opacité (0 % - 100 %) : +watermark.selectText.1=PDF auquel ajouter un filigrane +watermark.selectText.2=Texte du filigrane +watermark.selectText.3=Taille de police +watermark.selectText.4=Rotation (de 0 à 360 degrés) +watermark.selectText.5=widthSpacer (espace entre chaque filigrane horizontalement) +watermark.selectText.6=heightSpacer (espace entre chaque filigrane verticalement) +watermark.selectText.7=Opacité (de 0% à 100%) +watermark.selectText.8=Type de filigrane +watermark.selectText.9=Image du filigrane watermark.submit=Ajouter un filigrane -#remove-watermark -remove-watermark.title=Supprimer le filigrane -remove-watermark.header=Supprimer le filigrane -remove-watermark.selectText.1=Sélectionnez le PDF pour supprimer le filigrane : -remove-watermark.selectText.2=Texte du filigrane : -remove-watermark.submit=Supprimer le filigrane - - #Change permissions -permissions.title=Modifier les autorisations -permissions.header=Modifier les autorisations -permissions.warning=Attention pour que ces permissions soient immuables il est recommandé de les définir avec un mot de passe via la page add-password. -permissions.selectText.1=Sélectionnez le PDF pour modifier les autorisations : -permissions.selectText.2=Autorisations à définir : -permissions.selectText.3=Employer l'assemblage du document -permissions.selectText.4=Employer l'extraction de contenu -permissions.selectText.5=Employer l'extraction pour l'accessibilité -permissions.selectText.6=Employer pour remplir le formulaire -permissions.selectText.7=Employer pour la modification -permissions.selectText.8=Employer pour la modification des annotations -permissions.selectText.9=Employer pour l'impression -permissions.selectText.10=Empêcher l'impression de différents formats -permissions.submit=Modificateur +permissions.title=Modifier les permissions +permissions.header=Modifier les permissions +permissions.warning=Attention, pour que ces permissions soient immuables il est recommandé de les paramétrer avec un mot de passe via la page Ajouter un mot de passe. +permissions.selectText.1=Sélectionnez le PDF +permissions.selectText.2=Permissions à définir +permissions.selectText.3=Empêcher l\u2019assemblage du document +permissions.selectText.4=Empêcher l\u2019extraction de contenu +permissions.selectText.5=Empêcher l\u2019extraction pour l\u2019accessibilité +permissions.selectText.6=Empêcher de remplir les formulaires +permissions.selectText.7=Empêcher la modification +permissions.selectText.8=Empêcher la modification des annotations +permissions.selectText.9=Empêcher l\u2019impression +permissions.selectText.10=Empêcher l\u2019impression des différents formats +permissions.submit=Modifier #remove password removePassword.title=Supprimer le mot de passe -removePassword.header=Supprimer le mot de passe (Déchiffrer) -removePassword.selectText.1=Sélectionnez le PDF à déchiffrer +removePassword.header=Supprimer le mot de passe +removePassword.selectText.1=Sélectionnez le PDF removePassword.selectText.2=Mot de passe removePassword.submit=Supprimer #changeMetadata -changeMetadata.title=Titre : +changeMetadata.title=Titre changeMetadata.header=Modifier les métadonnées changeMetadata.selectText.1=Veuillez modifier les variables que vous souhaitez modifier. -changeMetadata.selectText.2=Supprimer toutes les métadonnées. -changeMetadata.selectText.3=Afficher les métadonnées personnalisées : -changeMetadata.author=Auteur : -changeMetadata.creationDate=Date de création (aaaa/MM/jj HH:mm:ss) : -changeMetadata.creator=Créateur : -changeMetadata.keywords=Mots clés : -changeMetadata.modDate=Date de modification (aaaa/MM/jj HH:mm:ss) : -changeMetadata.producer=Producteur : -changeMetadata.subject=Objet : -changeMetadata.title=Titre : -changeMetadata.trapped=Piégé : -changeMetadata.selectText.4=Autres métadonnées : -changeMetadata.selectText.5=Ajouter une entrée de métadonnées personnalisées +changeMetadata.selectText.2=Supprimer toutes les métadonnées +changeMetadata.selectText.3=Afficher des métadonnées personnalisées +changeMetadata.author=Auteur +changeMetadata.creationDate=Date de création (yyyy/MM/dd HH:mm:ss) +changeMetadata.creator=Créateur +changeMetadata.keywords=Mots clés +changeMetadata.modDate=Date de modification (yyyy/MM/dd HH:mm:ss) +changeMetadata.producer=Producteur +changeMetadata.subject=Sujet +changeMetadata.title=Titre +changeMetadata.trapped=Défoncé (technique d’impression) +changeMetadata.selectText.4=Autres métadonnées +changeMetadata.selectText.5=Ajouter une entrée de métadonnées personnalisée changeMetadata.submit=Modifier -#xlsToPdf -xlsToPdf.title=Excel vers PDF -xlsToPdf.header=Excel en PDF -xlsToPdf.selectText.1=Sélectionnez une feuille Excel XLS ou XLSX à convertir. -xlsToPdf.convert=Convertir - - #pdfToPDFA -pdfToPDFA.title=PDF vers PDF/A -pdfToPDFA.header=PDF vers PDF/A -pdfToPDFA.credit=Ce service utilise OCRmyPDF pour la conversion PDF/A +pdfToPDFA.title=PDF en PDF/A +pdfToPDFA.header=PDF en PDF/A +pdfToPDFA.credit=Ce service utilise OCRmyPDF pour la conversion en PDF/A. pdfToPDFA.submit=Convertir #PDFToWord -PDFToWord.title=PDF vers Word -PDFToWord.header=PDF vers Word +PDFToWord.title=PDF en Word +PDFToWord.header=PDF en Word PDFToWord.selectText.1=Format du fichier de sortie PDFToWord.credit=Ce service utilise LibreOffice pour la conversion de fichiers. PDFToWord.submit=Convertir #PDFToPresentation -PDFToPresentation.title=PDF vers présentation -PDFToPresentation.header=PDF vers présentation +PDFToPresentation.title=PDF en formats de présentation +PDFToPresentation.header=PDF en formats de présentation PDFToPresentation.selectText.1=Format du fichier de sortie PDFToPresentation.credit=Ce service utilise LibreOffice pour la conversion de fichiers. PDFToPresentation.submit=Convertir #PDFToText -PDFToText.title=PDF vers Texte/RTF -PDFToText.header=PDF vers texte/RTF +PDFToText.title=PDF en RTF (texte) +PDFToText.header=PDF en RTF (texte) PDFToText.selectText.1=Format du fichier de sortie PDFToText.credit=Ce service utilise LibreOffice pour la conversion de fichiers. PDFToText.submit=Convertir #PDFToHTML -PDFToHTML.title=PDF vers HTML -PDFToHTML.header=PDF vers HTML +PDFToHTML.title=PDF en HTML +PDFToHTML.header=PDF en HTML PDFToHTML.credit=Ce service utilise LibreOffice pour la conversion de fichiers. PDFToHTML.submit=Convertir #PDFToXML -PDFToXML.title=PDF vers XML -PDFToXML.header=PDF vers XML +PDFToXML.title=PDF en XML +PDFToXML.header=PDF en XML PDFToXML.credit=Ce service utilise LibreOffice pour la conversion de fichiers. PDFToXML.submit=Convertir diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index 4e5cf304..08a34c63 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -26,11 +26,29 @@ text=Testo font=Font selectFillter=-- Seleziona -- pageNum=Numero pagina -sizes.small=Small -sizes.medium=Medium -sizes.large=Large -sizes.x-large=X-Large -error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +sizes.small=Piccolo +sizes.medium=Medio +sizes.large=Largo +sizes.x-large=Extra-Large +error.pdfPassword=Il documento PDF è protetto da password e la password non è stata fornita oppure non era corretta +delete=Elimina +username=Username +password=Password +welcome=Benvenuto +property=Proprietà +black=Nero +white=Bianco +red=Rosso +green=Verde +blue=Blu +custom=Personalizzato + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -54,6 +72,55 @@ settings.downloadOption.1=Apri in questa finestra settings.downloadOption.2=Apri in una nuova finestra settings.downloadOption.3=Scarica file settings.zipThreshold=Comprimi file in .zip quando il numero di download supera +settings.signOut=Logout +settings.accountSettings=Impostazioni Account + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Impostazioni Account +account.accountSettings=Impostazioni Account +account.adminSettings=Impostazioni Admin - Aggiungi e Vedi Utenti +account.userControlSettings=Impostazioni Utente +account.changeUsername=Cambia Username +account.changeUsername=Cambia Username +account.password=Conferma Password +account.oldPassword=Vecchia Password +account.newPassword=Nuova Password +account.changePassword=Cambia Password +account.confirmNewPassword=Conferma Nuova Password +account.signOut=Logout +account.yourApiKey=La tua API Key +account.syncTitle=Sincronizza le impostazioni del browser con l'account +account.settingsCompare=Confronto delle impostazioni: +account.property=Proprietà +account.webBrowserSettings=Impostazione del browser web +account.syncToBrowser=Sincronizza account -> Browser +account.syncToAccount=Sincronizza account <- Browser + + +adminUserSettings.title=Impostazioni di controllo utente +adminUserSettings.header=Impostazioni di controllo utente amministratore +adminUserSettings.admin=Amministratore +adminUserSettings.user=Utente +adminUserSettings.addUser=Aggiungi un nuovo Utente +adminUserSettings.roles=Ruoli +adminUserSettings.role=Ruolo +adminUserSettings.actions=Azioni +adminUserSettings.apiUser=Utente API limitato +adminUserSettings.webOnlyUser=Utente solo Web +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Salva utente ############# # HOME-PAGE # @@ -63,28 +130,28 @@ home.desc=La tua pagina self-hostata per gestire qualsiasi PDF. home.multiTool.title=Multifunzione PDF home.multiTool.desc=Unisci, Ruota, Riordina, e Rimuovi pagine -multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side +multiTool.tags=Strumento multiplo, operazione multipla, interfaccia utente, trascinamento clic, front-end, lato client home.merge.title=Unisci home.merge.desc=Unisci facilmente più PDF in uno. -merge.tags=merge,Page operations,Back end,server side +merge.tags=unione, operazioni sulla pagina, back end, lato server home.split.title=Dividi home.split.desc=Dividi un singolo PDF in più documenti. -split.tags=Page operations,divide,Multi Page,cut,server side +split.tags=Operazioni sulla pagina, divisione, multi pagina, taglio, lato server home.rotate.title=Ruota home.rotate.desc=Ruota un PDF. -rotate.tags=server side +rotate.tags=lato server home.imageToPdf.title=Da immagine a PDF home.imageToPdf.desc=Converti un'immagine (PNG, JPEG, GIF) in PDF. -imageToPdf.tags=conversion,img,jpg,picture,photo +imageToPdf.tags=conversione,img,jpg,immagine,foto home.pdfToImage.title=Da PDF a immagine home.pdfToImage.desc=Converti un PDF in un'immagine. (PNG, JPEG, GIF) -pdfToImage.tags=conversion,img,jpg,picture,photo +pdfToImage.tags=conversione,img,jpg,immagine,foto home.pdfOrganiser.title=Organizza home.pdfOrganiser.desc=Rimuovi/Riordina le pagine in qualsiasi ordine. @@ -187,80 +254,81 @@ home.compare.title=Compara home.compare.desc=Vedi e compara le differenze tra due PDF. compare.tags=differentiate,contrast,changes,analysis -home.certSign.title=Sign with Certificate -home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) +home.certSign.title=Firma con certificato +home.certSign.desc=Firma un PDF con un certificato/chiave (PEM/P12) certSign.tags=authenticate,PEM,P12,official,encrypt -home.pageLayout.title=Multi-Page Layout -home.pageLayout.desc=Merge multiple pages of a PDF document into a single page +home.pageLayout.title=Layout multipagina +home.pageLayout.desc=Unisci più pagine di un documento PDF in un'unica pagina pageLayout.tags=merge,composite,single-view,organize -home.scalePages.title=Adjust page size/scale -home.scalePages.desc=Change the size/scale of page and/or its contents. +home.scalePages.title=Regola le dimensioni/scala della pagina +home.scalePages.desc=Modificare le dimensioni/scala della pagina e/o dei suoi contenuti. scalePages.tags=resize,modify,dimension,adapt -home.pipeline.title=Pipeline (Advanced) -home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts +home.pipeline.title=Pipeline (avanzato) +home.pipeline.desc=Esegui più azioni sui PDF definendo script di pipeline pipeline.tags=automate,sequence,scripted,batch-process -home.add-page-numbers.title=Add Page Numbers -home.add-page-numbers.desc=Add Page numbers throughout a document in a set location +home.add-page-numbers.title=Aggiungi numeri di pagina +home.add-page-numbers.desc=Aggiungi numeri di pagina in tutto un documento in una posizione prestabilita add-page-numbers.tags=paginate,label,organize,index -home.auto-rename.title=Auto Rename PDF File -home.auto-rename.desc=Auto renames a PDF file based on its detected header +home.auto-rename.title=Rinomina automaticamente il file PDF +home.auto-rename.desc=Rinomina automaticamente un file PDF in base all'intestazione rilevata auto-rename.tags=auto-detect,header-based,organize,relabel -home.adjust-contrast.title=Adjust Colors/Contrast -home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF +home.adjust-contrast.title=Regola colori/contrasto +home.adjust-contrast.desc=Regola contrasto, saturazione e luminosità di un PDF adjust-contrast.tags=color-correction,tune,modify,enhance -home.crop.title=Crop PDF -home.crop.desc=Crop a PDF to reduce its size (maintains text!) +home.crop.title=Ritaglia PDF +home.crop.desc=Ritaglia un PDF per ridurne le dimensioni (mantiene il testo!) crop.tags=trim,shrink,edit,shape -home.autoSplitPDF.title=Auto Split Pages -home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code +home.autoSplitPDF.title=Pagine divise automaticamente +home.autoSplitPDF.desc=Dividi automaticamente il PDF scansionato con il codice QR dello divisore di pagina fisico scansionato autoSplitPDF.tags=QR-based,separate,scan-segment,organize -home.sanitizePdf.title=Sanitize -home.sanitizePdf.desc=Remove scripts and other elements from PDF files +home.sanitizePdf.title=Igienizzare +home.sanitizePdf.desc=Rimuovi script e altri elementi dai file PDF sanitizePdf.tags=clean,secure,safe,remove-threats -home.URLToPDF.title=URL/Website To PDF -home.URLToPDF.desc=Converts any http(s)URL to PDF +home.URLToPDF.title=URL/sito Web in PDF +home.URLToPDF.desc=Converte qualsiasi URL http(s) in PDF URLToPDF.tags=web-capture,save-page,web-to-doc,archive -home.HTMLToPDF.title=HTML to PDF -home.HTMLToPDF.desc=Converts any HTML file or zip to PDF +home.HTMLToPDF.title=Da HTML a PDF +home.HTMLToPDF.desc=Converte qualsiasi file HTML o zip in PDF HTMLToPDF.tags=markup,web-content,transformation,convert -home.MarkdownToPDF.title=Markdown to PDF -home.MarkdownToPDF.desc=Converts any Markdown file to PDF +home.MarkdownToPDF.title=Markdown in PDF +home.MarkdownToPDF.desc=Converte qualsiasi file Markdown in PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -home.getPdfInfo.title=Get ALL Info on PDF -home.getPdfInfo.desc=Grabs any and all information possible on PDFs +home.getPdfInfo.title=Ottieni TUTTE le informazioni in PDF +home.getPdfInfo.desc=Raccogli tutte le informazioni possibili sui PDF getPdfInfo.tags=infomation,data,stats,statistics -home.extractPage.title=Extract page(s) -home.extractPage.desc=Extracts select pages from PDF +home.extractPage.title=Estrai pagina/e +home.extractPage.desc=Estrae le pagine selezionate dal PDF extractPage.tags=extract -home.PdfToSinglePage.title=PDF to Single Large Page -home.PdfToSinglePage.desc=Merges all PDF pages into one large single page +home.PdfToSinglePage.title=PDF in un'unica pagina di grandi dimensioni +home.PdfToSinglePage.desc=Unisce tutte le pagine PDF in un'unica grande pagina PdfToSinglePage.tags=single page -########################## -### TODO: Translate ### -########################## -home.showJS.title=Show Javascript -home.showJS.desc=Searches and displays any JS injected into a PDF +home.showJS.title=Mostra Javascript +home.showJS.desc=Cerca e visualizza qualsiasi JS inserito in un PDF +showJS.tags=JS + +home.autoRedact.title=Redazione automatica +home.autoRedact.desc=Redige automaticamente (oscura) il testo in un PDF in base al testo immesso showJS.tags=JS ########################### @@ -268,116 +336,138 @@ showJS.tags=JS # WEB PAGES # # # ########################### +#login +login.title=Accedi +login.signin=Accedi +login.rememberme=Ricordami +login.invalid=Nome utente o password errati. +login.locked=Il tuo account è stato bloccato. +login.signinTitle=Per favore accedi + + +#auto-redact +autoRedact.title=Redazione automatica +autoRedact.header=Redazione automatica +autoRedact.colorLabel=Colore +autoRedact.textsToRedactLabel=Testo da oscurare (separato da righe) +autoRedact.textsToRedactPlaceholder=per esempio. \nConfidenziale \nTop-Secret +autoRedact.useRegexLabel=Usa Regex +autoRedact.wholeWordSearchLabel=Ricerca di parole intere +autoRedact.customPaddingLabel=Padding extra personalizzato +autoRedact.convertPDFToImageLabel=Converti PDF in immagine PDF (utilizzato per rimuovere il testo dietro la casella) +autoRedact.submitButton=Invia + + #showJS -########################## -### TODO: Translate ### -########################## -showJS.title=Show Javascript -showJS.header=Show Javascript -showJS.downloadJS=Download Javascript -showJS.submit=Show +showJS.title=Mostra Javascript +showJS.header=Mostra Javascript +showJS.downloadJS=Scarica Javascript +showJS.submit=Mostra #pdfToSinglePage -pdfToSinglePage.title=PDF To Single Page -pdfToSinglePage.header=PDF To Single Page -pdfToSinglePage.submit=Convert To Single Page +pdfToSinglePage.title=PDF a pagina singola +pdfToSinglePage.header=PDF a pagina singola +pdfToSinglePage.submit=Converti in pagina singola #pageExtracter -pageExtracter.title=Extract Pages -pageExtracter.header=Extract Pages -pageExtracter.submit=Extract +pageExtracter.title=Estrai pagine +pageExtracter.header=Estrai pagine +pageExtracter.submit=Estrai #getPdfInfo -getPdfInfo.title=Get Info on PDF -getPdfInfo.header=Get Info on PDF -getPdfInfo.submit=Get Info -getPdfInfo.downloadJson=Download JSON +getPdfInfo.title=Ottieni informazioni in PDF +getPdfInfo.header=Ottieni informazioni in PDF +getPdfInfo.submit=Ottieni informazioni +getPdfInfo.downloadJson=Scarica JSON #markdown-to-pdf -MarkdownToPDF.title=Markdown To PDF -MarkdownToPDF.header=Markdown To PDF -MarkdownToPDF.submit=Convert -MarkdownToPDF.help=Work in progress -MarkdownToPDF.credit=Uses WeasyPrint +MarkdownToPDF.title=Markdown in PDF +MarkdownToPDF.header=Markdown in PDF +MarkdownToPDF.submit=Converti +MarkdownToPDF.help=Conversione in corso +MarkdownToPDF.credit=Utilizza WeasyPrint #url-to-pdf -URLToPDF.title=URL To PDF -URLToPDF.header=URL To PDF -URLToPDF.submit=Convert -URLToPDF.credit=Uses WeasyPrint +URLToPDF.title=URL a PDF +URLToPDF.header=URL a PDF +URLToPDF.submit=Converti +URLToPDF.credit=Utilizza WeasyPrint #html-to-pdf -HTMLToPDF.title=HTML To PDF -HTMLToPDF.header=HTML To PDF -HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required -HTMLToPDF.submit=Convert -HTMLToPDF.credit=Uses WeasyPrint +HTMLToPDF.title=HTML a PDF +HTMLToPDF.header=HTML a PDF +HTMLToPDF.help=Accetta file HTML e ZIP contenenti html/css/immagini ecc. richiesti +HTMLToPDF.submit=Converti +HTMLToPDF.credit=Utilizza WeasyPrint #sanitizePDF -sanitizePDF.title=Sanitize PDF -sanitizePDF.header=Sanitize a PDF file -sanitizePDF.selectText.1=Remove JavaScript actions -sanitizePDF.selectText.2=Remove embedded files -sanitizePDF.selectText.3=Remove metadata -sanitizePDF.selectText.4=Remove links -sanitizePDF.selectText.5=Remove fonts -sanitizePDF.submit=Sanitize PDF +sanitizePDF.title=Disinfetta PDF +sanitizePDF.header=Disinfettare un file PDF +sanitizePDF.selectText.1=Rimuovi le azioni JavaScript +sanitizePDF.selectText.2=Rimuovi i file incorporati +sanitizePDF.selectText.3=Rimuovi i metadati +sanitizePDF.selectText.4=Rimuovi collegamenti +sanitizePDF.selectText.5=Rimuovi i fonts +sanitizePDF.submit=Disinfetta PDF #addPageNumbers -addPageNumbers.title=Add Page Numbers -addPageNumbers.header=Add Page Numbers -addPageNumbers.selectText.1=Select PDF file: -addPageNumbers.selectText.2=Margin Size -addPageNumbers.selectText.3=Position -addPageNumbers.selectText.4=Starting Number -addPageNumbers.selectText.5=Pages to Number -addPageNumbers.selectText.6=Custom Text -addPageNumbers.submit=Add Page Numbers +addPageNumbers.title=Aggiungi numeri di pagina +addPageNumbers.header=Aggiungi numeri di pagina +addPageNumbers.selectText.1=Seleziona il file PDF: +addPageNumbers.selectText.2=Dimensione margine +addPageNumbers.selectText.3=Posizione +addPageNumbers.selectText.4=Numero di partenza +addPageNumbers.selectText.5=Pagine da numerare +addPageNumbers.selectText.6=Testo personalizzato +addPageNumbers.customTextDesc=Testo personalizzato +addPageNumbers.numberPagesDesc=Quali pagine numerare, impostazione predefinita "tutte", accetta anche 1-5 o 2,5,9 ecc +addPageNumbers.customNumberDesc=Il valore predefinito è {n}, accetta anche 'Pagina {n} di {totale}', 'Testo-{n}', '{nomefile}-{n} +addPageNumbers.submit=Aggiungi numeri di pagina #auto-rename -auto-rename.title=Auto Rename -auto-rename.header=Auto Rename PDF -auto-rename.submit=Auto Rename +auto-rename.title=Rinomina automatica +auto-rename.header=Rinomina automatica PDF +auto-rename.submit=Rinomina automatica #adjustContrast -adjustContrast.title=Adjust Contrast -adjustContrast.header=Adjust Contrast -adjustContrast.contrast=Contrast: -adjustContrast.brightness=Brightness: -adjustContrast.saturation=Saturation: +adjustContrast.title=Regola il contrasto +adjustContrast.header=Regola il contrasto +adjustContrast.contrast=Contrasto: +adjustContrast.brightness=Luminosità: +adjustContrast.saturation=Saturazione: adjustContrast.download=Download #crop -crop.title=Crop -crop.header=Crop Image -crop.submit=Submit +crop.title=Ritaglia +crop.header=Ritaglia l'immagine +crop.submit=Invia #autoSplitPDF -autoSplitPDF.title=Auto Split PDF -autoSplitPDF.header=Auto Split PDF -autoSplitPDF.description=Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed. -autoSplitPDF.selectText.1=Print out some divider sheets from below (Black and white is fine). -autoSplitPDF.selectText.2=Scan all your documents at once by inserting the divider sheet between them. -autoSplitPDF.selectText.3=Upload the single large scanned PDF file and let Stirling PDF handle the rest. -autoSplitPDF.selectText.4=Divider pages are automatically detected and removed, guaranteeing a neat final document. -autoSplitPDF.formPrompt=Submit PDF containing Stirling-PDF Page dividers: -autoSplitPDF.duplexMode=Duplex Mode (Front and back scanning) -autoSplitPDF.dividerDownload1=Download 'Auto Splitter Divider (minimal).pdf' -autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf' -autoSplitPDF.submit=Submit +autoSplitPDF.title=PDF diviso automaticamente +autoSplitPDF.header=PDF diviso automaticamente +autoSplitPDF.description=Stampa, inserisci, scansiona, carica e lasciaci separare automaticamente i tuoi documenti. Non è necessario alcuno smistamento manuale. +autoSplitPDF.selectText.1=Stampa alcuni fogli divisori dal basso (il bianco e nero va bene). +autoSplitPDF.selectText.2=Scansiona tutti i tuoi documenti contemporaneamente inserendo il foglio divisorio tra di loro. +autoSplitPDF.selectText.3=Carica il singolo file PDF scansionato di grandi dimensioni e lascia che Stirling PDF gestisca il resto. +autoSplitPDF.selectText.4=Le pagine divisorie vengono rilevate e rimosse automaticamente, garantendo un documento finale ordinato. +autoSplitPDF.formPrompt=Invia PDF contenente divisori di pagina Stirling-PDF: +autoSplitPDF.duplexMode=Modalità duplex (scansione fronte e retro) +autoSplitPDF.dividerDownload1=Scarica 'Divisore automatico (minimo).pdf' +autoSplitPDF.dividerDownload2=Scarica 'Divisore automatico (con istruzioni).pdf' +autoSplitPDF.submit=Invia #pipeline @@ -385,18 +475,22 @@ pipeline.title=Pipeline #pageLayout -pageLayout.title=Multi Page Layout -pageLayout.header=Multi Page Layout -pageLayout.pagesPerSheet=Pages per sheet: -pageLayout.submit=Submit +pageLayout.title=Layout multipagina +pageLayout.header=Layout multipagina +pageLayout.pagesPerSheet=Pagine per foglio: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders +pageLayout.submit=Invia #scalePages -scalePages.title=Adjust page-scale -scalePages.header=Adjust page-scale -scalePages.pageSize=Size of a page of the document. -scalePages.scaleFactor=Zoom level (crop) of a page. -scalePages.submit=Submit +scalePages.title=Regola la scala della pagina +scalePages.header=Regola la scala della pagina +scalePages.pageSize=Dimensione di una pagina del documento. +scalePages.scaleFactor=Livello di zoom (ritaglio) di una pagina. +scalePages.submit=Invia #certSign @@ -526,6 +620,8 @@ addImage.submit=Aggiungi immagine #merge merge.title=Unisci merge.header=Unisci 2 o più PDF +merge.sortByName=Ordina per nome +merge.sortByDate=Ordina per data merge.submit=Unisci @@ -573,7 +669,10 @@ split.submit=Dividi imageToPDF.title=Immagine a PDF imageToPDF.header=Immagine a PDF imageToPDF.submit=Converti -imageToPDF.selectText.1=Allarga per riempire +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=Ruota automaticamente PDF imageToPDF.selectText.3=Logica multi-file (funziona solo se ci sono più immagini) imageToPDF.selectText.4=Unisci in un unico PDF @@ -610,9 +709,9 @@ addPassword.selectText.10=Previeni modifiche addPassword.selectText.11=Previeni annotazioni addPassword.selectText.12=Previeni stampa addPassword.selectText.13=Previeni stampa in diversi formati -addPassword.selectText.14=Owner Password -addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) -addPassword.selectText.16=Restricts the opening of the document itself +addPassword.selectText.14=Password del proprietario +addPassword.selectText.15=Limita le operazioni eseguibili con il documento una volta aperto (non supportato da tutti i lettori) +addPassword.selectText.16=Limita l'apertura del documento stesso addPassword.submit=Crittografa @@ -626,17 +725,11 @@ watermark.selectText.4=Rotazione (0-360): watermark.selectText.5=spazio orizzontale (tra ogni filigrana): watermark.selectText.6=spazio verticale (tra ogni filigrana): watermark.selectText.7=Opacità (0% - 100%): +watermark.selectText.8=Tipo di filigrana: +watermark.selectText.9=Immagine filigrana: watermark.submit=Aggiungi Filigrana -#remove-watermark -remove-watermark.title=Rimuovi Filigrana -remove-watermark.header=Rimuovi filigrana -remove-watermark.selectText.1=Seleziona PDF da cui rimuovere la filigrana: -remove-watermark.selectText.2=Testo: -remove-watermark.submit=Rimuovi Filigrana - - #Change permissions permissions.title=Cambia Permessi permissions.header=Cambia permessi @@ -682,13 +775,6 @@ changeMetadata.selectText.5=Aggiungi proprietà personalizzata: changeMetadata.submit=Cambia Proprietà -#xlsToPdf -xlsToPdf.title=Da Excel a PDF -xlsToPdf.header=Da Excel a PDF -xlsToPdf.selectText.1=Seleziona un foglio XLS o XLSX da convertire -xlsToPdf.convert=Converti - - #pdfToPDFA pdfToPDFA.title=Da PDF a PDF/A pdfToPDFA.header=Da PDF a PDF/A diff --git a/src/main/resources/messages_ja_JP.properties b/src/main/resources/messages_ja_JP.properties index 4e49c578..2251484f 100644 --- a/src/main/resources/messages_ja_JP.properties +++ b/src/main/resources/messages_ja_JP.properties @@ -26,11 +26,29 @@ text=テキスト font=フォント selectFillter=-- 選択 -- pageNum=ページ番号 -sizes.small=Small -sizes.medium=Medium -sizes.large=Large -sizes.x-large=X-Large +sizes.small=小 +sizes.medium=中 +sizes.large=大 +sizes.x-large=特大 error.pdfPassword=PDFにパスワードが設定されてますが、パスワードが入力されてないか間違ってます。 +delete=削除 +username=ユーザー名 +password=パスワード +welcome=ようこそ +property=プロパティ +black=黒 +white=白 +red=赤 +green=緑 +blue=青 +custom=カスタム... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -54,6 +72,55 @@ settings.downloadOption.1=同じウィンドウで開く settings.downloadOption.2=新しいウィンドウで開く settings.downloadOption.3=ファイルをダウンロード settings.zipThreshold=このファイル数を超えたときにファイルを圧縮する +settings.signOut=サインアウト +settings.accountSettings=アカウント設定 + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=アカウント設定 +account.accountSettings=アカウント設定 +account.adminSettings=管理者設定 - ユーザーの表示と追加 +account.userControlSettings=ユーザー制御設定 +account.changeUsername=ユーザー名を変更 +account.changeUsername=ユーザー名を変更 +account.password=確認用パスワード +account.oldPassword=旧パスワード +account.newPassword=新パスワード +account.changePassword=パスワードの変更 +account.confirmNewPassword=新パスワードの確認 +account.signOut=サインアウト +account.yourApiKey=あなたのAPIキー +account.syncTitle=ブラウザ設定をアカウントと同期する +account.settingsCompare=設定比較: +account.property=プロパティ +account.webBrowserSettings=Webブラウザ設定 +account.syncToBrowser=アカウントの同期 -> ブラウザ +account.syncToAccount=アカウントの同期 <- ブラウザ + + +adminUserSettings.title=ユーザー制御設定 +adminUserSettings.header=管理者ユーザー制御設定 +adminUserSettings.admin=管理者 +adminUserSettings.user=ユーザー +adminUserSettings.addUser=新しいユーザを追加 +adminUserSettings.roles=役割 +adminUserSettings.role=役割 +adminUserSettings.actions=アクション +adminUserSettings.apiUser=限定されたAPIユーザー +adminUserSettings.webOnlyUser=ウェブ専用ユーザー +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=ユーザーの保存 ############# # HOME-PAGE # @@ -199,68 +266,69 @@ home.scalePages.title=ページの縮尺の調整 home.scalePages.desc=ページやコンテンツの縮尺を変更します。 scalePages.tags=resize,modify,dimension,adapt -home.pipeline.title=Pipeline (Advanced) -home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts +home.pipeline.title=パイプライン (高度) +home.pipeline.desc=パイプラインスクリプトを定義してPDF上で複数のアクションを実行します。 pipeline.tags=automate,sequence,scripted,batch-process -home.add-page-numbers.title=Add Page Numbers -home.add-page-numbers.desc=Add Page numbers throughout a document in a set location +home.add-page-numbers.title=ページ番号の追加 +home.add-page-numbers.desc=ドキュメント全体の設定された場所にページ番号を追加します。 add-page-numbers.tags=paginate,label,organize,index -home.auto-rename.title=Auto Rename PDF File -home.auto-rename.desc=Auto renames a PDF file based on its detected header +home.auto-rename.title=PDFファイル名の自動変更 +home.auto-rename.desc=検出されたヘッダーに基づいてPDFファイルの名前を自動的に変更します。 auto-rename.tags=auto-detect,header-based,organize,relabel -home.adjust-contrast.title=Adjust Colors/Contrast -home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF +home.adjust-contrast.title=色/コントラストの調整 +home.adjust-contrast.desc=PDFのコントラスト、彩度、明るさを調整します。 adjust-contrast.tags=color-correction,tune,modify,enhance -home.crop.title=Crop PDF -home.crop.desc=Crop a PDF to reduce its size (maintains text!) +home.crop.title=PDFのトリミング +home.crop.desc=PDFをトリミングしてサイズを縮小します (テキストは維持します!)。 crop.tags=trim,shrink,edit,shape -home.autoSplitPDF.title=Auto Split Pages -home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code +home.autoSplitPDF.title=ページの自動分割 +home.autoSplitPDF.desc=ページ分割用QRコードを使用したスキャンしたPDFを自動分割します。 autoSplitPDF.tags=QR-based,separate,scan-segment,organize -home.sanitizePdf.title=Sanitize -home.sanitizePdf.desc=Remove scripts and other elements from PDF files +home.sanitizePdf.title=サニタイズ +home.sanitizePdf.desc=PDFファイルからスクリプトやその他の要素を削除します。 sanitizePdf.tags=clean,secure,safe,remove-threats -home.URLToPDF.title=URL/Website To PDF -home.URLToPDF.desc=Converts any http(s)URL to PDF +home.URLToPDF.title=URL/WebサイトをPDFに変換 +home.URLToPDF.desc=あらゆるhttp(s)URLをPDFに変換します。 URLToPDF.tags=web-capture,save-page,web-to-doc,archive -home.HTMLToPDF.title=HTML to PDF -home.HTMLToPDF.desc=Converts any HTML file or zip to PDF +home.HTMLToPDF.title=HTMLをPDFに変換 +home.HTMLToPDF.desc=HTMLファイルまたはzipをPDFに変換します。 HTMLToPDF.tags=markup,web-content,transformation,convert -home.MarkdownToPDF.title=Markdown to PDF -home.MarkdownToPDF.desc=Converts any Markdown file to PDF +home.MarkdownToPDF.title=MarkdownをPDFに変換 +home.MarkdownToPDF.desc=あらゆるMarkdownファイルをPDFに変換します。 MarkdownToPDF.tags=markup,web-content,transformation,convert -home.getPdfInfo.title=Get ALL Info on PDF -home.getPdfInfo.desc=Grabs any and all information possible on PDFs +home.getPdfInfo.title=PDFのすべての情報を入手 +home.getPdfInfo.desc=PDFのあらゆる情報を取得します。 getPdfInfo.tags=infomation,data,stats,statistics -home.extractPage.title=Extract page(s) -home.extractPage.desc=Extracts select pages from PDF +home.extractPage.title=ページの抽出 +home.extractPage.desc=PDFから選択したページを抽出します。 extractPage.tags=extract -home.PdfToSinglePage.title=PDF to Single Large Page -home.PdfToSinglePage.desc=Merges all PDF pages into one large single page +home.PdfToSinglePage.title=PDFを単一の大きなページに変換 +home.PdfToSinglePage.desc=PDFのすべてのページを1つの大きな単一ページに結合します PdfToSinglePage.tags=single page -########################## -### TODO: Translate ### -########################## -home.showJS.title=Show Javascript -home.showJS.desc=Searches and displays any JS injected into a PDF +home.showJS.title=JavaScriptを表示 +home.showJS.desc=PDFに挿入されたJavaScriptを検索して表示します。 +showJS.tags=JS + +home.autoRedact.title=自動塗りつぶし +home.autoRedact.desc=入力したテキストに基づいてPDF内のテキストを自動で塗りつぶし(黒塗り)します。 showJS.tags=JS ########################### @@ -268,126 +336,152 @@ showJS.tags=JS # WEB PAGES # # # ########################### +#login +login.title=サインイン +login.signin=サインイン +login.rememberme=サインイン状態を記憶する +login.invalid=ユーザー名かパスワードが無効です。 +login.locked=あなたのアカウントはロックされています。 +login.signinTitle=サインインしてください + + +#auto-redact +autoRedact.title=自動塗りつぶし +autoRedact.header=自動塗りつぶし +autoRedact.colorLabel=カラー +autoRedact.textsToRedactLabel=編集するテキスト (line-separated) +autoRedact.textsToRedactPlaceholder=例 \n機密 \n極秘 +autoRedact.useRegexLabel=正規表現を使用する +autoRedact.wholeWordSearchLabel=単語単位の検索 +autoRedact.customPaddingLabel=追加の余白 +autoRedact.convertPDFToImageLabel=PDFをPDF画像に変換 (塗りつぶしの後ろのテキストを削除するために使用) +autoRedact.submitButton=送信 + + #showJS -########################## -### TODO: Translate ### -########################## -showJS.title=Show Javascript -showJS.header=Show Javascript -showJS.downloadJS=Download Javascript -showJS.submit=Show +showJS.title=JavaScriptを表示 +showJS.header=JavaScriptを表示 +showJS.downloadJS=Javascriptをダウンロード +showJS.submit=表示 #pdfToSinglePage -pdfToSinglePage.title=PDF To Single Page -pdfToSinglePage.header=PDF To Single Page -pdfToSinglePage.submit=Convert To Single Page +pdfToSinglePage.title=PDFを単一ページに変換 +pdfToSinglePage.header=PDFを単一ページに変換 +pdfToSinglePage.submit=単一ページに変換 #pageExtracter -pageExtracter.title=Extract Pages -pageExtracter.header=Extract Pages -pageExtracter.submit=Extract +pageExtracter.title=ページの抽出 +pageExtracter.header=ページの抽出 +pageExtracter.submit=抽出 #getPdfInfo -getPdfInfo.title=Get Info on PDF -getPdfInfo.header=Get Info on PDF -getPdfInfo.submit=Get Info -getPdfInfo.downloadJson=Download JSON +getPdfInfo.title=PDFの情報を入手 +getPdfInfo.header=PDFの情報を入手 +getPdfInfo.submit=情報を入手 +getPdfInfo.downloadJson=JSONでダウンロード #markdown-to-pdf -MarkdownToPDF.title=Markdown To PDF -MarkdownToPDF.header=Markdown To PDF -MarkdownToPDF.submit=Convert -MarkdownToPDF.help=Work in progress -MarkdownToPDF.credit=Uses WeasyPrint +MarkdownToPDF.title=MarkdownをPDFに変換 +MarkdownToPDF.header=MarkdownをPDFに変換 +MarkdownToPDF.submit=変換 +MarkdownToPDF.help=処理中 +MarkdownToPDF.credit=WeasyPrintを使用 #url-to-pdf -URLToPDF.title=URL To PDF -URLToPDF.header=URL To PDF -URLToPDF.submit=Convert -URLToPDF.credit=Uses WeasyPrint +URLToPDF.title=URLをPDFに変換 +URLToPDF.header=URLをPDFに変換 +URLToPDF.submit=変換 +URLToPDF.credit=WeasyPrintを使用 #html-to-pdf -HTMLToPDF.title=HTML To PDF -HTMLToPDF.header=HTML To PDF -HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required -HTMLToPDF.submit=Convert -HTMLToPDF.credit=Uses WeasyPrint +HTMLToPDF.title=HTMLをPDFに変換 +HTMLToPDF.header=HTMLをPDFに変換 +HTMLToPDF.help=HTMLファイルと必要なhtml/css/画像などを含むZIPを受け入れます +HTMLToPDF.submit=変換 +HTMLToPDF.credit=WeasyPrintを使用 #sanitizePDF -sanitizePDF.title=Sanitize PDF -sanitizePDF.header=Sanitize a PDF file -sanitizePDF.selectText.1=Remove JavaScript actions -sanitizePDF.selectText.2=Remove embedded files -sanitizePDF.selectText.3=Remove metadata -sanitizePDF.selectText.4=Remove links -sanitizePDF.selectText.5=Remove fonts -sanitizePDF.submit=Sanitize PDF +sanitizePDF.title=PDFをサニタイズ +sanitizePDF.header=PDFファイルをサニタイズ +sanitizePDF.selectText.1=JavaScriptアクションを削除 +sanitizePDF.selectText.2=埋め込みファイルを削除 +sanitizePDF.selectText.3=メタデータを削除 +sanitizePDF.selectText.4=リンクを削除 +sanitizePDF.selectText.5=フォントを削除 +sanitizePDF.submit=PDFをサニタイズする #addPageNumbers -addPageNumbers.title=Add Page Numbers -addPageNumbers.header=Add Page Numbers -addPageNumbers.selectText.1=Select PDF file: -addPageNumbers.selectText.2=Margin Size -addPageNumbers.selectText.3=Position -addPageNumbers.selectText.4=Starting Number -addPageNumbers.selectText.5=Pages to Number -addPageNumbers.selectText.6=Custom Text -addPageNumbers.submit=Add Page Numbers +addPageNumbers.title=ページ番号の追加 +addPageNumbers.header=ページ番号の追加 +addPageNumbers.selectText.1=PDFファイルを選択: +addPageNumbers.selectText.2=余白サイズ +addPageNumbers.selectText.3=位置 +addPageNumbers.selectText.4=開始番号 +addPageNumbers.selectText.5=番号をつけるページ +addPageNumbers.selectText.6=カスタムテキスト +addPageNumbers.customTextDesc=カスタムテキスト +addPageNumbers.numberPagesDesc=番号をつけるページ、デフォルトは'all'、 1-5 や 2,5,9 など +addPageNumbers.customNumberDesc=デフォルトは{n}、'{n} / {total} ページ'、'テキスト-{n}'、'{filename}-{n}など +addPageNumbers.submit=ページ番号の追加 #auto-rename -auto-rename.title=Auto Rename -auto-rename.header=Auto Rename PDF -auto-rename.submit=Auto Rename +auto-rename.title=ファイル名の自動変更 +auto-rename.header=PDF名の自動変更 +auto-rename.submit=自動リネーム #adjustContrast -adjustContrast.title=Adjust Contrast -adjustContrast.header=Adjust Contrast -adjustContrast.contrast=Contrast: -adjustContrast.brightness=Brightness: -adjustContrast.saturation=Saturation: -adjustContrast.download=Download +adjustContrast.title=コントラストの調整 +adjustContrast.header=コントラストの調整 +adjustContrast.contrast=コントラスト: +adjustContrast.brightness=明度: +adjustContrast.saturation=彩度: +adjustContrast.download=ダウンロード #crop -crop.title=Crop -crop.header=Crop Image -crop.submit=Submit +crop.title=切り抜き +crop.header=画像の切り抜き +crop.submit=送信 #autoSplitPDF -autoSplitPDF.title=Auto Split PDF -autoSplitPDF.header=Auto Split PDF -autoSplitPDF.description=Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed. -autoSplitPDF.selectText.1=Print out some divider sheets from below (Black and white is fine). -autoSplitPDF.selectText.2=Scan all your documents at once by inserting the divider sheet between them. -autoSplitPDF.selectText.3=Upload the single large scanned PDF file and let Stirling PDF handle the rest. -autoSplitPDF.selectText.4=Divider pages are automatically detected and removed, guaranteeing a neat final document. -autoSplitPDF.formPrompt=Submit PDF containing Stirling-PDF Page dividers: -autoSplitPDF.duplexMode=Duplex Mode (Front and back scanning) -autoSplitPDF.dividerDownload1=Download 'Auto Splitter Divider (minimal).pdf' -autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf' -autoSplitPDF.submit=Submit +autoSplitPDF.title=PDFの自動分割 +autoSplitPDF.header=PDFの自動分割 +autoSplitPDF.description=印刷、挿入、スキャン、アップロード、およびドキュメントを自動分離します。手動での仕分けの必要ありません。 +autoSplitPDF.selectText.1=下から仕切り用紙を印刷します(白黒で問題ありません)。 +autoSplitPDF.selectText.2=原稿の間に仕切り用紙を挿入し、すべての原稿をまとめてスキャンします。 +autoSplitPDF.selectText.3=スキャンしたPDFファイルをアップロードしStirling PDFに任せます。 +autoSplitPDF.selectText.4=仕切りページは自動的に検出、削除されるので、最終的な文書はきれいに仕上がります。 +autoSplitPDF.formPrompt=Stirling-PDF仕切り用紙を含むPDFを送信: +autoSplitPDF.duplexMode=両面モード (表裏スキャン) +autoSplitPDF.dividerDownload1=ダウンロード '自動仕切り用紙 (最小).pdf' +autoSplitPDF.dividerDownload2=ダウンロード '自動仕切り用紙 (手順書付き).pdf' +autoSplitPDF.submit=送信 #pipeline -pipeline.title=Pipeline +pipeline.title=パイプライン #pageLayout pageLayout.title=マルチページレイアウト pageLayout.header=マルチページレイアウト pageLayout.pagesPerSheet=1枚あたりのページ数: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders pageLayout.submit=送信 @@ -526,6 +620,8 @@ addImage.submit=画像の追加 #merge merge.title=結合 merge.header=複数のPDFを結合 (2ファイル以上) +merge.sortByName=Sort by name +merge.sortByDate=Sort by date merge.submit=結合 @@ -573,7 +669,10 @@ split.submit=分割 imageToPDF.title=画像をPDFに変換 imageToPDF.header=画像をPDFに変換 imageToPDF.submit=変換 -imageToPDF.selectText.1=フィットするように引き伸ばす +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=PDFの自動回転 imageToPDF.selectText.3=マルチファイルの処理 (複数の画像を操作する場合に有効になります) imageToPDF.selectText.4=1つのPDFに結合 @@ -626,17 +725,11 @@ watermark.selectText.4=回転 (0-360): watermark.selectText.5=幅スペース (各透かし間の水平方向のスペース): watermark.selectText.6=高さスペース (各透かし間の垂直方向のスペース): watermark.selectText.7=不透明度 (0% - 100%): +watermark.selectText.8=Watermark Type: +watermark.selectText.9=Watermark Image: watermark.submit=透かしを追加 -#remove-watermark -remove-watermark.title=透かしの削除 -remove-watermark.header=透かしの削除 -remove-watermark.selectText.1=透かしを削除するPDFを選択: -remove-watermark.selectText.2=透かしのテキスト: -remove-watermark.submit=透かしを削除 - - #Change permissions permissions.title=権限の変更 permissions.header=権限の変更 @@ -682,13 +775,6 @@ changeMetadata.selectText.5=カスタムメタデータの追加 changeMetadata.submit=変更 -#xlsToPdf -xlsToPdf.title=ExcelをPDFに変換 -xlsToPdf.header=ExcelをPDFに変換 -xlsToPdf.selectText.1=変換するXLSまたはXLSX Execlシートを選択 -xlsToPdf.convert=変換 - - #pdfToPDFA pdfToPDFA.title=PDFをPDF/Aに変換 pdfToPDFA.header=PDFをPDF/Aに変換 diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index 4c5a1e8c..b4c2fa36 100644 --- a/src/main/resources/messages_ko_KR.properties +++ b/src/main/resources/messages_ko_KR.properties @@ -31,6 +31,24 @@ sizes.medium=Medium sizes.large=Large sizes.x-large=X-Large error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +delete=Delete +username=Username +password=Password +welcome=Welcome +property=Property +black=Black +white=White +red=Red +green=Green +blue=Blue +custom=Custom... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -54,6 +72,55 @@ settings.downloadOption.1=현재 창에서 열기 settings.downloadOption.2=새 창에서 열기 settings.downloadOption.3=다운로드 settings.zipThreshold=다운로드한 파일 수가 초과된 경우 파일 압축하기 +settings.signOut=Sign Out +settings.accountSettings=Account Settings + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Account Settings +account.accountSettings=Account Settings +account.adminSettings=Admin Settings - View and Add Users +account.userControlSettings=User Control Settings +account.changeUsername=Change Username +account.changeUsername=Change Username +account.password=Confirmation Password +account.oldPassword=Old password +account.newPassword=New Password +account.changePassword=Change Password +account.confirmNewPassword=Confirm New Password +account.signOut=Sign Out +account.yourApiKey=Your API Key +account.syncTitle=Sync browser settings with Account +account.settingsCompare=Settings Comparison: +account.property=Property +account.webBrowserSettings=Web Browser Setting +account.syncToBrowser=Sync Account -> Browser +account.syncToAccount=Sync Account <- Browser + + +adminUserSettings.title=User Control Settings +adminUserSettings.header=Admin User Control Settings +adminUserSettings.admin=Admin +adminUserSettings.user=User +adminUserSettings.addUser=Add New User +adminUserSettings.roles=Roles +adminUserSettings.role=Role +adminUserSettings.actions=Actions +adminUserSettings.apiUser=Limited API User +adminUserSettings.webOnlyUser=Web Only User +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Save User ############# # HOME-PAGE # @@ -256,22 +323,42 @@ home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page -########################## -### TODO: Translate ### -########################## home.showJS.title=Show Javascript home.showJS.desc=Searches and displays any JS injected into a PDF showJS.tags=JS +home.autoRedact.title=Auto Redact +home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### +#login +login.title=Sign in +login.signin=Sign in +login.rememberme=Remember me +login.invalid=Invalid username or password. +login.locked=Your account has been locked. +login.signinTitle=Please sign in + + +#auto-redact +autoRedact.title=Auto Redact +autoRedact.header=Auto Redact +autoRedact.colorLabel=Colour +autoRedact.textsToRedactLabel=Text to Redact (line-separated) +autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret +autoRedact.useRegexLabel=Use Regex +autoRedact.wholeWordSearchLabel=Whole Word Search +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) +autoRedact.submitButton=Submit + + #showJS -########################## -### TODO: Translate ### -########################## showJS.title=Show Javascript showJS.header=Show Javascript showJS.downloadJS=Download Javascript @@ -341,6 +428,9 @@ addPageNumbers.selectText.3=Position addPageNumbers.selectText.4=Starting Number addPageNumbers.selectText.5=Pages to Number addPageNumbers.selectText.6=Custom Text +addPageNumbers.customTextDesc=Custom Text +addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} addPageNumbers.submit=Add Page Numbers @@ -388,6 +478,10 @@ pipeline.title=Pipeline pageLayout.title=Multi Page Layout pageLayout.header=Multi Page Layout pageLayout.pagesPerSheet=Pages per sheet: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders pageLayout.submit=Submit @@ -526,6 +620,8 @@ addImage.submit=이미지 추가 #merge merge.title=병합 merge.header=여러 개의 PDF 병합 (2개 이상) +merge.sortByName=Sort by name +merge.sortByDate=Sort by date merge.submit=병합 @@ -573,7 +669,10 @@ split.submit=분할 imageToPDF.title=이미지를 PDF로 변환 imageToPDF.header=이미지를 PDF로 변환 imageToPDF.submit=변환하기 -imageToPDF.selectText.1=맞춤 크기로 늘리기 +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=PDF 자동 회전 imageToPDF.selectText.3=다중 파일 로직 (여러 이미지로 작업하는 경우에만 활성화됨) imageToPDF.selectText.4=단일 PDF로 병합 @@ -626,17 +725,11 @@ watermark.selectText.4=회전 각도 (0-360): watermark.selectText.5=가로 간격 (각 워터마크 사이의 가로 공간): watermark.selectText.6=세로 간격 (각 워터마크 사이의 세로 공간): watermark.selectText.7=투명도 (0% - 100%): +watermark.selectText.8=Watermark Type: +watermark.selectText.9=Watermark Image: watermark.submit=워터마크 추가 -#remove-watermark -remove-watermark.title=워터마크 제거 -remove-watermark.header=워터마크 제거 -remove-watermark.selectText.1=워터마크를 제거할 PDF 선택: -remove-watermark.selectText.2=워터마크 텍스트: -remove-watermark.submit=워터마크 제거 - - #Change permissions permissions.title=권한 변경 permissions.header=권한 변경 @@ -682,13 +775,6 @@ changeMetadata.selectText.5=사용자 정의 메타데이터 항목 추가 changeMetadata.submit=변경 -#xlsToPdf -xlsToPdf.title=Excel to PDF -xlsToPdf.header=Excel을 PDF로 변환 -xlsToPdf.selectText.1=변환할 XLS 또는 XLSX Excel 시트 선택 -xlsToPdf.convert=변환하기 - - #pdfToPDFA pdfToPDFA.title=PDF To PDF/A pdfToPDFA.header=PDF를 PDF/A로 변환 diff --git a/src/main/resources/messages_nl_NL.properties b/src/main/resources/messages_nl_NL.properties new file mode 100644 index 00000000..a13c9071 --- /dev/null +++ b/src/main/resources/messages_nl_NL.properties @@ -0,0 +1,820 @@ +########### +# Generic # +########### +# the direction that the language is written (ltr=left to right, rtl = right to left) +language.direction=ltr + +pdfPrompt=Selecteer PDF(s) +multiPdfPrompt=Selecteer PDFs (2+) +multiPdfDropPrompt=Selecteer (of sleep & zet neer) alle PDFs die je nodig hebt +imgPrompt=Selecteer afbeelding(en) +genericSubmit=Indienen +processTimeWarning=Waarschuwing: Dit proces kan tot een minuut duren afhankelijk van de bestandsgrootte +pageOrderPrompt=Aangepaste pagina volgorde (Voer een komma-gescheiden lijst van paginanummers of functies in, zoals 2n+1) : +goToPage=Ga +true=Waar +false=Onwaar +unknown=Onbekend +save=Opslaan +close=Sluiten +filesSelected=Bestanden geselecteerd +noFavourites=Geen favorieten toegevoegd +bored=Verveeld met wachten? +alphabet=Alfabet +downloadPdf=Download PDF +text=Tekst +font=Lettertype +selectFillter=-- Selecteer -- +pageNum=Paginanummer +sizes.small=Klein +sizes.medium=Medium +sizes.large=Groot +sizes.x-large=Extra Groot +error.pdfPassword=Het PDF document is beveiligd met een wachtwoord en het wachtwoord is niet ingevoerd of was onjuist +delete=Verwijderen +username=Gebruikersnaam +password=Wachtwoord +welcome=Welkom +property=Property +black=Black +white=White +red=Red +green=Green +blue=Blue +custom=Custom... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + + + +############# +# NAVBAR # +############# +navbar.convert=Converteren +navbar.security=Beveiliging +navbar.other=Overige +navbar.darkmode=Donkere modus +navbar.pageOps=Pagina operaties +navbar.settings=Instellingen + +############# +# SETTINGS # +############# +settings.title=Instellingen +settings.update=Update beschikbaar +settings.appVersion=App versie: +settings.downloadOption.title=Kies download optie (Voor enkelvoudige bestanddownloads zonder zip): +settings.downloadOption.1=Open in hetzelfde venster +settings.downloadOption.2=Open in nieuw venster +settings.downloadOption.3=Download bestand +settings.zipThreshold=Zip bestanden wanneer het aantal gedownloade bestanden overschrijdt +settings.signOut=Uitloggen +settings.accountSettings=Account instellingen + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Account instellingen +account.accountSettings=Account instellingen +account.adminSettings=Beheerdersinstellingen - Gebruikers bekijken en toevoegen +account.userControlSettings=Gebruikerscontrole instellingen +account.changeUsername=Wijzig gebruikersnaam +account.changeUsername=Wijzig gebruikersnaam +account.password=Bevestigingswachtwoord +account.oldPassword=Oud wachtwoord +account.newPassword=Nieuw wachtwoord +account.changePassword=Wijzig wachtwoord +account.confirmNewPassword=Bevestig nieuw wachtwoord +account.signOut=Uitloggen +account.yourApiKey=Jouw API sleutel +account.syncTitle=Synchroniseer browserinstellingen met account +account.settingsCompare=Instellingen vergelijking: +account.property=Eigenschap +account.webBrowserSettings=Web Browser instelling +account.syncToBrowser=Synchroniseer account -> browser +account.syncToAccount=Synchroniseer account <- browser + + +adminUserSettings.title=Gebruikerscontrole instellingen +adminUserSettings.header=Beheer Gebruikerscontrole instellingen +adminUserSettings.admin=Beheerder +adminUserSettings.user=Gebruiker +adminUserSettings.addUser=Voeg nieuwe gebruiker toe +adminUserSettings.roles=Rollen +adminUserSettings.role=Rol +adminUserSettings.actions=Acties +adminUserSettings.apiUser=Beperkte API gebruiker +adminUserSettings.webOnlyUser=Alleen web gebruiker +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Sla gebruiker op + +############# +# HOME-PAGE # +############# +home.desc=Jouw lokaal gehoste one-stop-shop voor al je PDF-behoeften. + + +home.multiTool.title=PDF Multitool +home.multiTool.desc=Samenvoegen, draaien, herschikken en pagina''s verwijderen +multiTool.tags=Multitool,Multi bewerking,UI,klik sleep,voorkant,clientzijde,interactief,beweegbaar,verplaats + +home.merge.title=Samenvoegen +home.merge.desc=Voeg eenvoudig meerdere PDF''s samen tot één. +merge.tags=samenvoegen,Pagina operaties,Serverkant + +home.split.title=Splitsen +home.split.desc=Splits PDF''s in meerdere documenten +split.tags=Pagina operaties,verdelen,meerdere pagina''s,knippen,serverzijde + +home.rotate.title=Roteren +home.rotate.desc=Roteer eenvoudig je PDF''s. +rotate.tags=serverzijde + + +home.imageToPdf.title=Afbeelding naar PDF +home.imageToPdf.desc=Converteer een afbeelding (PNG, JPEG, GIF) naar PDF. +imageToPdf.tags=conversie,img,jpg,foto + +home.pdfToImage.title=PDF naar Afbeelding +home.pdfToImage.desc=Converteer een PDF naar een afbeelding. (PNG, JPEG, GIF) +pdfToImage.tags=conversie,img,jpg,foto + +home.pdfOrganiser.title=Organiseren +home.pdfOrganiser.desc=Verwijder/Herschik pagina''s in een volgorde naar keus +pdfOrganiser.tags=duplex,even oneven,sorteren,verplaatsen + + +home.addImage.title=Afbeelding toevoegen +home.addImage.desc=Voegt een afbeelding toe op een specifieke locatie in de PDF +addImage.tags=img,jpg,foto + +home.watermark.title=Watermerk toevoegen +home.watermark.desc=Voeg een aangepast watermerk toe aan je PDF-document. +watermark.tags=Tekst,herhalend,label,eigen,copyright,handelsmerk,img,jpg,foto + +home.permissions.title=Permissies wijzigen +home.permissions.desc=Wijzig de permissies van je PDF-document +permissions.tags=lezen,schrijven,bewerken,printen + + +home.removePages.title=Verwijderen +home.removePages.desc=Verwijder ongewenste pagina''s uit je PDF-document. +removePages.tags=Pagina''s verwijderen + +home.addPassword.title=Wachtwoord toevoegen +home.addPassword.desc=Versleutel je PDF-document met een wachtwoord. +addPassword.tags=veilig,beveiliging + +home.removePassword.title=Wachtwoord verwijderen +home.removePassword.desc=Verwijder wachtwoordbeveiliging van je PDF-document. +removePassword.tags=veilig,Decrypteren,beveiliging,wachtwoord verwijderen + +home.compressPdfs.title=Comprimeren +home.compressPdfs.desc=Comprimeer PDFs om hun bestandsgrootte te verkleinen. +compressPdfs.tags=comprimeren,klein + + +home.changeMetadata.title=Metadata wijzigen +home.changeMetadata.desc=Wijzig/Verwijder/Voeg metadata toe van een PDF-document +changeMetadata.tags=Titel,auteur,datum,creatie,tijd,uitgever,producent,statistieken + +home.fileToPDF.title=Bestand naar PDF converteren +home.fileToPDF.desc=Converteer bijna ieder bestand naar PDF (DOCX, PNG, XLS, PPT, TXT en meer) +fileToPDF.tags=transformatie,formaat,document,foto,slide,tekst,conversie,kantoor,docs,word,excel,powerpoint + +home.ocr.title=OCR / Scans opruimen +home.ocr.desc=Ruim scans op, detecteert tekst van afbeeldingen in een PDF en voegt deze opnieuw toe als tekst. +ocr.tags=herkenning,tekst,afbeelding,scan,lezen,identificeren,detectie,bewerkbaar + + +home.extractImages.title=Afbeeldingen extraheren +home.extractImages.desc=Extraheert alle afbeeldingen uit een PDF en slaat ze op in een zip +extractImages.tags=foto,opslaan,archief,zip,vastleggen,plukken + +home.pdfToPDFA.title=PDF naar PDF/A +home.pdfToPDFA.desc=Converteer PDF naar PDF/A voor langdurige opslag +pdfToPDFA.tags=archief,langdurig,standaard,conversie,opslag,bewaring + +home.PDFToWord.title=PDF naar Word +home.PDFToWord.desc=Converteer PDF naar Word-formaten (DOC, DOCX en ODT) +PDFToWord.tags=doc,docx,odt,word,transformatie,formaat,conversie,kantoor,microsoft,docfile + +home.PDFToPresentation.title=PDF naar Presentatie +home.PDFToPresentation.desc=Converteer PDF naar Presentatie formaten (PPT, PPTX en ODP) +PDFToPresentation.tags=slides,show,kantoor,microsoft + +home.PDFToText.title=PDF naar RTF (Tekst) +home.PDFToText.desc=Converteer PDF naar Tekst of RTF formaat +PDFToText.tags=rijkformaat + +home.PDFToHTML.title=PDF naar HTML +home.PDFToHTML.desc=Converteer PDF naar HTML formaat +PDFToHTML.tags=webinhoud,browser vriendelijk + + +home.PDFToXML.title=PDF naar XML +home.PDFToXML.desc=Converteer PDF naar XML formaat +PDFToXML.tags=data-extractie,gestructureerd,code + +home.ScannerImageSplit.title=Detecteer/Split gescande foto''s +home.ScannerImageSplit.desc=Splits meerdere foto''s van binnen een foto/PDF +ScannerImageSplit.tags=scheiden,auto-detecteren,scans,meer-foto,organiseren + +home.sign.title=Ondertekenen +home.sign.desc=Voegt handtekening toe aan PDF via tekenen, tekst of afbeelding +sign.tags=autoriseren,initialen,getekende-handtekening,tekst-handtekening,afbeelding-handtekening + +home.flatten.title=Platdrukken +home.flatten.desc=Verwijder alle interactieve elementen en formulieren uit een PDF +flatten.tags=statisch,deactiveren,niet-interactief,stroomlijnen + +home.repair.title=Repareren +home.repair.desc=Probeert een corrupt/beschadigd PDF te herstellen +repair.tags=repareren,herstellen,correctie,terughalen + +home.removeBlanks.title=Verwijder lege pagina''s +home.removeBlanks.desc=Detecteert en verwijdert lege pagina''s uit een document +removeBlanks.tags=opruimen,stroomlijnen,geen-inhoud,organiseren + +home.compare.title=Vergelijken +home.compare.desc=Vergelijkt en toont de verschillen tussen 2 PDF-documenten +compare.tags=onderscheiden,contrasteren,veranderingen,analyse + +home.certSign.title=Ondertekenen met certificaat +home.certSign.desc=Ondertekent een PDF met een certificaat/sleutel (PEM/P12) +certSign.tags=authenticeren,PEM,P12,officieel,versleutelen + +home.pageLayout.title=Multi-pagina indeling +home.pageLayout.desc=Voeg meerdere pagina''s van een PDF-document samen op één pagina +pageLayout.tags=samenvoegen,composiet,enkel-zicht,organiseren + +home.scalePages.title=Aanpassen paginaformaat/schaal +home.scalePages.desc=Wijzig de grootte/schaal van een pagina en/of de inhoud ervan. +scalePages.tags=resize,aanpassen,dimensie,aanpassen + +home.pipeline.title=Pijplijn (Geavanceerd) +home.pipeline.desc=Voer meerdere acties uit op PDF''s door pipelinescripts te definiëren +pipeline.tags=automatiseren,volgorde,gescrript,batch-verwerking + +home.add-page-numbers.title=Paginanummers toevoegen +home.add-page-numbers.desc=Voeg paginanummers toe binnen het volledige document op een vastgestelde locatie +add-page-numbers.tags=pagineren,labelen,organiseren,indexeren + +home.auto-rename.title=Automatisch hernoemen PDF-bestand +home.auto-rename.desc=Hernoemt automatisch een PDF-bestand op basis van de gedetecteerde header +auto-rename.tags=auto-detecteren,op-header-gebaseerd,organiseren,herlabelen + +home.adjust-contrast.title=Kleuren/Contrast aanpassen +home.adjust-contrast.desc=Pas Contrast, Verzadiging en Helderheid van een PDF aan +adjust-contrast.tags=kleur-correctie,afstemmen,aanpassen,verbeteren + +home.crop.title=PDF bijsnijden +home.crop.desc=Snijd een PDF bij om de grootte te verkleinen (behoudt tekst!) +crop.tags=trimmen,verkleinen,bewerken,vorm + +home.autoSplitPDF.title=Automatisch splitsen pagina''s +home.autoSplitPDF.desc=Automatisch splitsen van gescande PDF met fysieke gescande paginasplitter QR-code +autoSplitPDF.tags=QR-gebaseerd,scheiden,scan-segment,organiseren + +home.sanitizePdf.title=Opschonen +home.sanitizePdf.desc=Verwijder scripts en andere elementen uit PDF-bestanden +sanitizePdf.tags=schoonmaken,veilig,veilig,bedreigingen verwijderen + +home.URLToPDF.title=URL/Website naar PDF +home.URLToPDF.desc=Zet http(s)URL om naar PDF +URLToPDF.tags=web-capture,pagina opslaan,web-naar-doc,archief + +home.HTMLToPDF.title=HTML naar PDF +home.HTMLToPDF.desc=Zet HTML-bestand of zip om naar PDF +HTMLToPDF.tags=markup,web-inhoud,transformatie,omzetten + + +home.MarkdownToPDF.title=Markdown naar PDF +home.MarkdownToPDF.desc=Zet Markdown-bestand om naar PDF +MarkdownToPDF.tags=markup,web-inhoud,transformatie,omzetten + + +home.getPdfInfo.title=Haal ALLE informatie op over PDF +home.getPdfInfo.desc=Haalt alle mogelijke informatie op van PDF''s +getPdfInfo.tags=informatie,data,statistieken + + +home.extractPage.title=Pagina(''s) extraheren +home.extractPage.desc=Extraheert geselecteerde pagina''s uit PDF +extractPage.tags=extraheren + + +home.PdfToSinglePage.title=PDF naar één grote pagina +home.PdfToSinglePage.desc=Voegt alle PDF-pagina''s samen tot één grote pagina +PdfToSinglePage.tags=één pagina + + +home.showJS.title=Toon Javascript +home.showJS.desc=Zoekt en toont ieder script dat in een PDF is geïnjecteerd +showJS.tags=JS + +home.autoRedact.title=Auto Redact +home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text +showJS.tags=JS + +########################### +# # +# WEB PAGES # +# # +########################### +#login +login.title=Sign in +login.signin=Sign in +login.rememberme=Remember me +login.invalid=Invalid username or password. +login.locked=Your account has been locked. +login.signinTitle=Please sign in + + +#auto-redact +autoRedact.title=Auto Redact +autoRedact.header=Auto Redact +autoRedact.colorLabel=Colour +autoRedact.textsToRedactLabel=Text to Redact (line-separated) +autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret +autoRedact.useRegexLabel=Use Regex +autoRedact.wholeWordSearchLabel=Whole Word Search +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) +autoRedact.submitButton=Submit + + +#showJS +showJS.title=Toon Javascript +showJS.header=Toon Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Toon + + +#pdfToSinglePage +pdfToSinglePage.title=PDF naar enkele pagina +pdfToSinglePage.header=PDF naar enkele pagina +pdfToSinglePage.submit=Converteren naar enkele pagina + + +#pageExtracter +pageExtracter.title=Pagina''s extraheren +pageExtracter.header=Pagina''s extraheren +pageExtracter.submit=Extraheren + + +#getPdfInfo +getPdfInfo.title=Informatie over PDF ophalen +getPdfInfo.header=Informatie over PDF ophalen +getPdfInfo.submit=Haal informatie op +getPdfInfo.downloadJson=Download JSON + + +#markdown-to-pdf +MarkdownToPDF.title=Markdown naar PDF +MarkdownToPDF.header=Markdown naar PDF +MarkdownToPDF.submit=Converteren +MarkdownToPDF.help=in ontwikkeling +MarkdownToPDF.credit=Gebruikt WeasyPrint + + + +#url-to-pdf +URLToPDF.title=URL naar PDF +URLToPDF.header=URL naar PDF +URLToPDF.submit=Converteren +URLToPDF.credit=Gebruikt WeasyPrint + + +#html-to-pdf +HTMLToPDF.title=HTML naar PDF +HTMLToPDF.header=HTML naar PDF +HTMLToPDF.help=Accepteert HTML-bestanden en ZIP''s die html/css/afbeeldingen etc. bevatten +HTMLToPDF.submit=Converteren +HTMLToPDF.credit=Gebruikt WeasyPrint + + +#sanitizePDF +sanitizePDF.title=PDF opschonen +sanitizePDF.header=Een PDF-bestand opschonen +sanitizePDF.selectText.1=Verwijder Javascript-acties +sanitizePDF.selectText.2=Verwijder ingebedde bestanden +sanitizePDF.selectText.3=Verwijder metadata +sanitizePDF.selectText.4=Verwijder links +sanitizePDF.selectText.5=Verwijder lettertypen +sanitizePDF.submit=PDF opschonen + + +#addPageNumbers +addPageNumbers.title=Paginanummers toevoegen +addPageNumbers.header=Paginanummers toevoegen +addPageNumbers.selectText.1=Selecteer PDF-bestand: +addPageNumbers.selectText.2=Margegrootte +addPageNumbers.selectText.3=Positie +addPageNumbers.selectText.4=Startnummer +addPageNumbers.selectText.5=Pagina''s om te nummeren +addPageNumbers.selectText.6=Aangepaste tekst +addPageNumbers.customTextDesc=Custom Text +addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} +addPageNumbers.submit=Paginanummers toevoegen + + +#auto-rename +auto-rename.title=Automatisch hernoemen +auto-rename.header=PDF automatisch hernoemen +auto-rename.submit=Automatisch hernoemen + + +#adjustContrast +adjustContrast.title=Contrast aanpassen +adjustContrast.header=Contrast aanpassen +adjustContrast.contrast=Contrast: +adjustContrast.brightness=Helderheid: +adjustContrast.saturation=Verzadiging: +adjustContrast.download=Downloaden + + +#crop +crop.title=Bijwerken +crop.header=Afbeelding bijwerken +crop.submit=Indienen + + +#autoSplitPDF +autoSplitPDF.title=PDF automatisch splitsen +autoSplitPDF.header=PDF automatisch splitsen +autoSplitPDF.description=Print, Voeg in, Scan, upload, en laat ons je documenten automatisch scheiden. Geen handmatig sorteerwerk nodig. +autoSplitPDF.selectText.1=Print enkele scheidingsbladen van hieronder (Zwart-wit is prima). +autoSplitPDF.selectText.2=Scan al je documenten tegelijk door het scheidingsblad ertussen te plaatsen. +autoSplitPDF.selectText.3=Upload het enkele grote gescande PDF-bestand en laat Stirling PDF de rest afhandelen. +autoSplitPDF.selectText.4=Scheidingspagina''s worden automatisch gedetecteerd en verwijderd, wat een net einddocument garandeert. +autoSplitPDF.formPrompt=Dien PDF in met Stirling-PDF Pagina-scheiders: +autoSplitPDF.duplexMode=Duplex Modus (voor- en achterkant scannen) +autoSplitPDF.dividerDownload1=Download 'Auto Splitter Divider (minimal).pdf' +autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf' +autoSplitPDF.submit=Indienen + + +#pipeline +pipeline.title=Pijplijn + + +#pageLayout +pageLayout.title=Meerdere pagina indeling +pageLayout.header=Meerdere pagina indeling +pageLayout.pagesPerSheet=Pagina''s per vel: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders +pageLayout.submit=Indienen + + +#scalePages +scalePages.title=Pagina-schaal aanpassen +scalePages.header=Pagina-schaal aanpassen +scalePages.pageSize=Grootte van een pagina van het document. +scalePages.scaleFactor=Zoomniveau (uitsnede) van een pagina. +scalePages.submit=Indienen + + +#certSign +certSign.title=Certificaat ondertekening +certSign.header=Onderteken een PDF met je certificaat (in ontwikkeling) +certSign.selectPDF=Selecteer een PDF-bestand voor ondertekening: +certSign.selectKey=Selecteer je privésleutelbestand (PKCS#8 formaat, kan .pem of .der zijn): +certSign.selectCert=Selecteer je certificaatbestand (X.509 formaat, kan .pem of .der zijn): +certSign.selectP12=Selecteer je PKCS#12 Sleutelopslagbestand (.p12 of .pfx) (Optioneel, indien verstrekt, moet het je privésleutel en certificaat bevatten): +certSign.certType=Certificaattype +certSign.password=Voer je sleutelopslag of privésleutel wachtwoord in (indien van toepassing): +certSign.showSig=Toon handtekening +certSign.reason=Reden +certSign.location=Locatie +certSign.name=Naam +certSign.submit=PDF ondertekenen + + +#removeBlanks +removeBlanks.title=Verwijder blanco''s +removeBlanks.header=Verwijder lege pagina''s +removeBlanks.threshold=Pixel witheid drempel: +removeBlanks.thresholdDesc=Drempel voor het bepalen hoe wit een witte pixel moet zijn om als ''Wit'' te worden geclassificeerd. 0 = Zwart, 255 zuiver wit. +removeBlanks.whitePercent=Wit percentage (%): +removeBlanks.whitePercentDesc=Percentage van de pagina dat ''witte'' pixels moet zijn om verwijderd te worden +removeBlanks.submit=Blanco''s verwijderen + + +#compare +compare.title=Vergelijken +compare.header=PDF''s vergelijken +compare.document.1=Document 1 +compare.document.2=Document 2 +compare.submit=Vergelijken + + +#sign +sign.title=Ondertekenen +sign.header=PDF''s ondertekenen +sign.upload=Upload afbeelding +sign.draw=Handtekening tekenen +sign.text=Tekstinvoer +sign.clear=Wissen +sign.add=Toevoegen + + +#repair +repair.title=Repareren +repair.header=PDF''s repareren +repair.submit=Repareren + + +#flatten +flatten.title=Afvlakken +flatten.header=PDF''s afvlakken +flatten.submit=Afvlakken + + +#ScannerImageSplit +ScannerImageSplit.selectText.1=Hoek drempel: +ScannerImageSplit.selectText.2=Stelt de minimale absolute hoek in die nodig is om de afbeelding te roteren (standaard: 10). +ScannerImageSplit.selectText.3=Tolerantie: +ScannerImageSplit.selectText.4=Bepaalt het bereik van kleurvariatie rond de geschatte achtergrondkleur (standaard: 30). +ScannerImageSplit.selectText.5=Minimum oppervlakte: +ScannerImageSplit.selectText.6=Stelt de minimale oppervlakte drempel in voor een foto (standaard: 10000). +ScannerImageSplit.selectText.7=Minimum contour oppervlakte: +ScannerImageSplit.selectText.8=Stelt de minimale contour oppervlakte drempel in voor een foto +ScannerImageSplit.selectText.9=Randgrootte: +ScannerImageSplit.selectText.10=Stelt de grootte van de toegevoegde en verwijderde rand in om witte randen in de uitvoer te voorkomen (standaard: 1). + + +#OCR +ocr.title=OCR / Scan opruimen +ocr.header=Scans opruimen / OCR (Optical Character Recognition) +ocr.selectText.1=Selecteer talen die binnen de PDF gedetecteerd moeten worden (De vermelde zijn de momenteel gedetecteerde): +ocr.selectText.2=Produceer tekstbestand met OCR-tekst naast de OCR''d PDF +ocr.selectText.3=Corrigeer pagina''s die onder een scheve hoek zijn gescand door ze terug te draaien +ocr.selectText.4=Maak de pagina schoon, zodat het minder waarschijnlijk is dat OCR tekst in achtergrondruis vindt. (Geen uitvoerverandering) +ocr.selectText.5=Maak de pagina schoon zodat OCR waarschijnlijk geen tekst in achtergrondruis vindt, behoudt opruiming in uitvoer. +ocr.selectText.6=Negeert pagina''s met interactieve tekst, OCR''s alleen pagina''s die afbeeldingen zijn +ocr.selectText.7=Forceer OCR, zal elke pagina OCR''en en alle originele tekstelementen verwijderen +ocr.selectText.8=Normaal (Zal een fout geven als de PDF tekst bevat) +ocr.selectText.9=Aanvullende instellingen +ocr.selectText.10=OCR-modus +ocr.selectText.11=Verwijder afbeeldingen na OCR (Verwijdert ALLE afbeeldingen, alleen nuttig als onderdeel van conversiestap) +ocr.selectText.12=Render Type (Geavanceerd) +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.submit=Verwerk PDF met OCR + + +#extractImages +extractImages.title=Afbeeldingen extraheren +extractImages.header=Afbeeldingen extraheren +extractImages.selectText=Selecteer het beeldformaat voor geëxtraheerde afbeeldingen +extractImages.submit=Extraheer + + +#File to PDF +fileToPDF.title=Bestand naar PDF +fileToPDF.header=Zet elk bestand om naar PDF +fileToPDF.credit=Deze service gebruikt LibreOffice en Unoconv voor bestandsconversie. +fileToPDF.supportedFileTypes=Ondersteunde bestandstypen zijn hieronder opgenomen, maar raadpleeg voor een volledige lijst met ondersteunde formaten de LibreOffice-documentatie +fileToPDF.submit=Omzetten naar PDF + + +#compress +compress.title=Comprimeren +compress.header=PDF comprimeren +compress.credit=Deze functie gebruikt Ghostscript voor PDF Compressie/Optimalisatie. +compress.selectText.1=Handmatige modus - Van 1 tot 4 +compress.selectText.2=Optimalisatieniveau: +compress.selectText.3=4 (Verschrikkelijk voor tekstafbeeldingen) +compress.selectText.4=Automatische modus - Past kwaliteit automatisch aan om PDF naar exacte grootte te krijgen +compress.selectText.5=Verwachte PDF-grootte (bijv. 25MB, 10.8MB, 25KB) +compress.submit=Comprimeren + + +#Add image +addImage.title=Afbeelding toevoegen +addImage.header=Afbeelding aan PDF toevoegen +addImage.everyPage=Elke pagina? +addImage.upload=Afbeelding toevoegen +addImage.submit=Afbeelding toevoegen + + +#merge +merge.title=Samenvoegen +merge.header=Meerdere PDF''s samenvoegen (2+) +merge.sortByName=Sorteer op naam +merge.sortByDate=Sorteer op datum +merge.submit=Samenvoegen + + +#pdfOrganiser +pdfOrganiser.title=Pagina organisator +pdfOrganiser.header=PDF pagina organisator +pdfOrganiser.submit=Pagina''s herschikken + + +#multiTool +multiTool.title=PDF Multitool +multiTool.header=PDF Multitool + + +#pageRemover +pageRemover.title=Pagina verwijderaar +pageRemover.header=PDF pagina verwijderaar +pageRemover.pagesToDelete=Te verwijderen pagina''s (Voer een door komma''s gescheiden lijst met paginanummers in): +pageRemover.submit=Pagina''s verwijderen + + +#rotate +rotate.title=PDF roteren +rotate.header=PDF roteren +rotate.selectAngle=Selecteer rotatiehoek (in veelvouden van 90 graden): +rotate.submit=Roteren + + +#merge +split.title=PDF splitsen +split.header=PDF splitsen +split.desc.1=De nummers die je kiest zijn de paginanummers waarop je een splitsing wilt uitvoeren +split.desc.2=Als zodanig selecteren van 1,3,7-8 zou een 10 pagina''s tellend document splitsen in 6 aparte PDF''s met: +split.desc.3=Document #1: Pagina 1 +split.desc.4=Document #2: Pagina 2 en 3 +split.desc.5=Document #3: Pagina 4, 5 en 6 +split.desc.6=Document #4: Pagina 7 +split.desc.7=Document #5: Pagina 8 +split.desc.8=Document #6: Pagina 9 en 10 +split.splitPages=Voer pagina''s in om op te splitsen: +split.submit=Splitsen + + +#merge +imageToPDF.title=Afbeelding naar PDF +imageToPDF.header=Afbeelding naar PDF +imageToPDF.submit=Omzetten +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios +imageToPDF.selectText.2=PDF automatisch draaien +imageToPDF.selectText.3=Meervoudige bestandslogica (Alleen ingeschakeld bij werken met meerdere afbeeldingen) +imageToPDF.selectText.4=Voeg samen in één PDF +imageToPDF.selectText.5=Zet om naar afzonderlijke PDF''s + + +#pdfToImage +pdfToImage.title=PDF naar afbeelding +pdfToImage.header=PDF naar afbeelding +pdfToImage.selectText=Afbeeldingsformaat +pdfToImage.singleOrMultiple=Resultaattype van pagina naar afbeelding +pdfToImage.single=Eén grote afbeelding die alle pagina''s combineert +pdfToImage.multi=Meerdere afbeeldingen, één afbeelding per pagina +pdfToImage.colorType=Kleurtype +pdfToImage.color=Kleur +pdfToImage.grey=Grijstinten +pdfToImage.blackwhite=Zwart en wit (kan data verliezen!) +pdfToImage.submit=Omzetten + + +#addPassword +addPassword.title=Wachtwoord toevoegen +addPassword.header=Wachtwoord toevoegen (Versleutelen) +addPassword.selectText.1=Selecteer PDF om te versleutelen +addPassword.selectText.2=Gebruikerswachtwoord +addPassword.selectText.3=Versleutelingssleutellengte +addPassword.selectText.4=Hogere waarden zijn sterker, maar lagere waarden hebben een betere compatibiliteit. +addPassword.selectText.5=In te stellen rechten (Aanbevolen om te gebruiken samen met eigenaarswachtwoord) +addPassword.selectText.6=Voorkomen van documentassemblage +addPassword.selectText.7=Voorkomen van inhoudsextractie +addPassword.selectText.8=Voorkomen van extractie voor toegankelijkheid +addPassword.selectText.9=Voorkomen van invullen van formulier +addPassword.selectText.10=Voorkomen van wijziging +addPassword.selectText.11=Voorkomen van annotatiewijziging +addPassword.selectText.12=Voorkomen van afdrukken +addPassword.selectText.13=Voorkomen van afdrukken in verschillende formaten +addPassword.selectText.14=Eigenaarswachtwoord +addPassword.selectText.15=Beperkt wat gedaan kan worden met het document nadat het is geopend (Niet ondersteund door alle lezers) +addPassword.selectText.16=Beperkt het openen van het document zelf +addPassword.submit=Versleutelen + + +#watermark +watermark.title=Watermerk toevoegen +watermark.header=Watermerk toevoegen +watermark.selectText.1=Selecteer PDF om watermerk toe te voegen: +watermark.selectText.2=Watermerk tekst: +watermark.selectText.3=Tekengrootte: +watermark.selectText.4=Rotatie (0-360): +watermark.selectText.5=breedteSpacer (Ruimte tussen elk watermerk horizontaal): +watermark.selectText.6=hoogteSpacer (Ruimte tussen elk watermerk verticaal): +watermark.selectText.7=Transparantie (0% - 100%): +watermark.selectText.8=Type watermerk: +watermark.selectText.9=Watermerk afbeelding: +watermark.submit=Watermerk toevoegen + + +#Change permissions +permissions.title=Rechten wijzigen +permissions.header=Rechten wijzigen +permissions.warning=Let op: om deze rechten onveranderlijk te maken, wordt aanbevolen om ze met een wachtwoord in te stellen via de add-password pagina. +permissions.selectText.1=Selecteer PDF om rechten te wijzigen +permissions.selectText.2=In te stellen rechten +permissions.selectText.3=Voorkom samenvoegen van document +permissions.selectText.4=Voorkom inhoudsextractie +permissions.selectText.5=Voorkom extractie voor toegankelijkheid +permissions.selectText.6=Voorkom invullen van formulier +permissions.selectText.7=Voorkom wijziging +permissions.selectText.8=Voorkom annotatie wijziging +permissions.selectText.9=Voorkom afdrukken +permissions.selectText.10=Voorkom afdrukken in verschillende formaten +permissions.submit=Wijzigen + + +#remove password +removePassword.title=Wachtwoord verwijderen +removePassword.header=Wachtwoord verwijderen (Decrypteren) +removePassword.selectText.1=Selecteer PDF om te decrypteren +removePassword.selectText.2=Wachtwoord +removePassword.submit=Verwijderen + + +#changeMetadata +changeMetadata.title=Titel: +changeMetadata.header=Metadata wijzigen +changeMetadata.selectText.1=Pas de variabelen aan die je wilt wijzigen +changeMetadata.selectText.2=Verwijder alle metadata +changeMetadata.selectText.3=Toon aangepaste metadata: +changeMetadata.author=Auteur: +changeMetadata.creationDate=Aanmaakdatum (yyyy/MM/dd HH:mm:ss): +changeMetadata.creator=Maker: +changeMetadata.keywords=Trefwoorden: +changeMetadata.modDate=Wijzigingsdatum (yyyy/MM/dd HH:mm:ss): +changeMetadata.producer=Producent: +changeMetadata.subject=Onderwerp: +changeMetadata.title=Titel: +changeMetadata.trapped=Vastgezet: +changeMetadata.selectText.4=Overige metadata: +changeMetadata.selectText.5=Voeg aangepaste metadata-invoer toe +changeMetadata.submit=Wijzigen + + +#pdfToPDFA +pdfToPDFA.title=PDF naar PDF/A +pdfToPDFA.header=PDF naar PDF/A +pdfToPDFA.credit=Deze service gebruikt OCRmyPDF voor PDF/A-conversie +pdfToPDFA.submit=Converteren + + +#PDFToWord +PDFToWord.title=PDF naar Word +PDFToWord.header=PDF naar Word +PDFToWord.selectText.1=Uitvoerbestandsformaat +PDFToWord.credit=Deze service gebruikt LibreOffice voor bestandsconversie. +PDFToWord.submit=Converteren + + +#PDFToPresentation +PDFToPresentation.title=PDF naar Presentatie +PDFToPresentation.header=PDF naar Presentatie +PDFToPresentation.selectText.1=Uitvoerbestandsformaat +PDFToPresentation.credit=Deze service gebruikt LibreOffice voor bestandsconversie. +PDFToPresentation.submit=Converteren + + +#PDFToText +PDFToText.title=PDF naar RTF (Tekst) +PDFToText.header=PDF naar RTF (Tekst) +PDFToText.selectText.1=Uitvoerbestandsformaat +PDFToText.credit=Deze service gebruikt LibreOffice voor bestandsconversie. +PDFToText.submit=Converteren + + +#PDFToHTML +PDFToHTML.title=PDF naar HTML +PDFToHTML.header=PDF naar HTML +PDFToHTML.credit=Deze service gebruikt LibreOffice voor bestandsconversie. +PDFToHTML.submit=Converteren + + +#PDFToXML +PDFToXML.title=PDF naar XML +PDFToXML.header=PDF naar XML +PDFToXML.credit=Deze service gebruikt LibreOffice voor bestandsconversie. +PDFToXML.submit=Converteren diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index 2e04e80a..a2c06fc1 100644 --- a/src/main/resources/messages_pl_PL.properties +++ b/src/main/resources/messages_pl_PL.properties @@ -31,6 +31,24 @@ sizes.medium=Medium sizes.large=Large sizes.x-large=X-Large error.pdfPassword=Dokument PDF jest zabezpieczony hasłem, musisz podać prawidłowe hasło. +delete=Delete +username=Username +password=Password +welcome=Welcome +property=Property +black=Black +white=White +red=Red +green=Green +blue=Blue +custom=Custom... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -54,6 +72,55 @@ settings.downloadOption.1=Otwórz w tym samym oknie settings.downloadOption.2=Otwórz w nowym oknie settings.downloadOption.3=Pobierz plik settings.zipThreshold=Spakuj pliki, gdy liczba pobranych plików przekroczy +settings.signOut=Sign Out +settings.accountSettings=Account Settings + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Account Settings +account.accountSettings=Account Settings +account.adminSettings=Admin Settings - View and Add Users +account.userControlSettings=User Control Settings +account.changeUsername=Change Username +account.changeUsername=Change Username +account.password=Confirmation Password +account.oldPassword=Old password +account.newPassword=New Password +account.changePassword=Change Password +account.confirmNewPassword=Confirm New Password +account.signOut=Sign Out +account.yourApiKey=Your API Key +account.syncTitle=Sync browser settings with Account +account.settingsCompare=Settings Comparison: +account.property=Property +account.webBrowserSettings=Web Browser Setting +account.syncToBrowser=Sync Account -> Browser +account.syncToAccount=Sync Account <- Browser + + +adminUserSettings.title=User Control Settings +adminUserSettings.header=Admin User Control Settings +adminUserSettings.admin=Admin +adminUserSettings.user=User +adminUserSettings.addUser=Add New User +adminUserSettings.roles=Roles +adminUserSettings.role=Role +adminUserSettings.actions=Actions +adminUserSettings.apiUser=Limited API User +adminUserSettings.webOnlyUser=Web Only User +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Save User ############# # HOME-PAGE # @@ -256,22 +323,42 @@ home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page -########################## -### TODO: Translate ### -########################## home.showJS.title=Show Javascript home.showJS.desc=Searches and displays any JS injected into a PDF showJS.tags=JS +home.autoRedact.title=Auto Redact +home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### +#login +login.title=Sign in +login.signin=Sign in +login.rememberme=Remember me +login.invalid=Invalid username or password. +login.locked=Your account has been locked. +login.signinTitle=Please sign in + + +#auto-redact +autoRedact.title=Auto Redact +autoRedact.header=Auto Redact +autoRedact.colorLabel=Colour +autoRedact.textsToRedactLabel=Text to Redact (line-separated) +autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret +autoRedact.useRegexLabel=Use Regex +autoRedact.wholeWordSearchLabel=Whole Word Search +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) +autoRedact.submitButton=Submit + + #showJS -########################## -### TODO: Translate ### -########################## showJS.title=Show Javascript showJS.header=Show Javascript showJS.downloadJS=Download Javascript @@ -341,6 +428,9 @@ addPageNumbers.selectText.3=Position addPageNumbers.selectText.4=Starting Number addPageNumbers.selectText.5=Pages to Number addPageNumbers.selectText.6=Custom Text +addPageNumbers.customTextDesc=Custom Text +addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} addPageNumbers.submit=Add Page Numbers @@ -388,6 +478,10 @@ pipeline.title=Pipeline pageLayout.title=Układ wielu stron pageLayout.header=Układ wielu stron pageLayout.pagesPerSheet=Stron na jednym arkuszu: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders pageLayout.submit=Wykonaj @@ -526,6 +620,8 @@ addImage.submit=Dodaj obraz #merge merge.title=Połącz merge.header=Połącz wiele dokumentów PDF (2+) +merge.sortByName=Sort by name +merge.sortByDate=Sort by date merge.submit=Połącz @@ -573,7 +669,10 @@ split.submit=Podziel imageToPDF.title=Obraz na PDF imageToPDF.header=Obraz na PDF imageToPDF.submit=Konwertuj -imageToPDF.selectText.1=Rozciągnij, aby dopasować +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=Automatyczne obracanie PDF imageToPDF.selectText.3=Logika wielu plików (dostępna tylko w przypadku pracy z wieloma obrazami) imageToPDF.selectText.4=Połącz w jeden dokument PDF @@ -626,17 +725,11 @@ watermark.selectText.4=Obrót (0-360): watermark.selectText.5=Odstęp w poziomie (odstęp między każdym znakiem wodnym w poziomie): watermark.selectText.6=Odstęp w pionie (odstęp między każdym znakiem wodnym w pionie): watermark.selectText.7=Nieprzezroczystość (0% - 100%): +watermark.selectText.8=Watermark Type: +watermark.selectText.9=Watermark Image: watermark.submit=Dodaj znak wodny -#remove-watermark -remove-watermark.title=Usuń znak wodny -remove-watermark.header=Usuń znak wodny -remove-watermark.selectText.1=Wybierz dokument PDF, aby usunąć znak wodny z: -remove-watermark.selectText.2=Treść zanku wodnego: -remove-watermark.submit=Usuń znak wodny - - #Change permissions permissions.title=Zmień uprawnienia permissions.header=Zmień uprawnienia @@ -682,13 +775,6 @@ changeMetadata.selectText.5=Dodaj niestandardowy wpis w metadanych changeMetadata.submit=Zmień -#xlsToPdf -xlsToPdf.title=Excel na PDF -xlsToPdf.header=Excel na PDF -xlsToPdf.selectText.1=Wybierz arkusz Microsoft Excel XLS lub XLSX do konwersji -xlsToPdf.convert=Konwertuj - - #pdfToPDFA pdfToPDFA.title=PDF na PDF/A pdfToPDFA.header=PDF na PDF/A diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index 7112ddf1..879f3715 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -31,6 +31,24 @@ sizes.medium=Médio sizes.large=Grande sizes.x-large=Muito grande error.pdfPassword=O documento PDF está protegido por senha e a senha não foi fornecida ou está incorreta +delete=Delete +username=Username +password=Password +welcome=Welcome +property=Property +black=Black +white=White +red=Red +green=Green +blue=Blue +custom=Custom... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -54,12 +72,62 @@ settings.downloadOption.1=Abrir na mesma janela settings.downloadOption.2=Abrir em nova janela settings.downloadOption.3=⇬ Fazer download do arquivo settings.zipThreshold=Compactar arquivos quando o número de arquivos baixados exceder +settings.signOut=Sign Out +settings.accountSettings=Account Settings + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Account Settings +account.accountSettings=Account Settings +account.adminSettings=Admin Settings - View and Add Users +account.userControlSettings=User Control Settings +account.changeUsername=Change Username +account.changeUsername=Change Username +account.password=Confirmation Password +account.oldPassword=Old password +account.newPassword=New Password +account.changePassword=Change Password +account.confirmNewPassword=Confirm New Password +account.signOut=Sign Out +account.yourApiKey=Your API Key +account.syncTitle=Sync browser settings with Account +account.settingsCompare=Settings Comparison: +account.property=Property +account.webBrowserSettings=Web Browser Setting +account.syncToBrowser=Sync Account -> Browser +account.syncToAccount=Sync Account <- Browser + + +adminUserSettings.title=User Control Settings +adminUserSettings.header=Admin User Control Settings +adminUserSettings.admin=Admin +adminUserSettings.user=User +adminUserSettings.addUser=Add New User +adminUserSettings.roles=Roles +adminUserSettings.role=Role +adminUserSettings.actions=Actions +adminUserSettings.apiUser=Limited API User +adminUserSettings.webOnlyUser=Web Only User +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Save User ############# # HOME-PAGE # ############# home.desc=Seu melhor utilitário para suas necessidades de PDF. + home.multiTool.title=Multiferramenta de PDF home.multiTool.desc=Mesclar, girar, reorganizar e remover páginas multiTool.tags=Multi Ferramenta, Operação Múltipla, Interface do Usuário, Clique e Arraste, Front-end, Lado do Cliente @@ -76,6 +144,7 @@ home.rotate.title=Girar home.rotate.desc=Girar facilmente seus PDFs. rotate.tags=Lado do Servidor + home.imageToPdf.title=Imagem para PDF home.imageToPdf.desc=Converter uma imagem (PNG, JPEG, GIF) em PDF. imageToPdf.tags=conversão, img, jpg, imagem, foto @@ -88,6 +157,7 @@ home.pdfOrganiser.title=Organizar home.pdfOrganiser.desc=Remover/reorganizar as páginas em qualquer ordem. pdfOrganiser.tags=duplex, par, ímpar, ordenar, mover + home.addImage.title=Adicionar Imagem home.addImage.desc=Adicionar uma imagem em um local definido no PDF (trabalho em andamento) addImage.tags=img, jpg, imagem, foto @@ -100,6 +170,7 @@ home.permissions.title=Alterar Permissões home.permissions.desc=Alterar as permissões do seu documento PDF. permissions.tags=leitura, escrita, edição, impressão + home.removePages.title=Remover home.removePages.desc=Excluir as páginas indesejadas do seu documento PDF. removePages.tags=Remover páginas, excluir páginas @@ -116,6 +187,7 @@ home.compressPdfs.title=Comprimir home.compressPdfs.desc=Comprimir PDFs para reduzir o tamanho do arquivo. compressPdfs.tags=compactar, pequeno, mínimo + home.changeMetadata.title=Alterar Metadados home.changeMetadata.desc=Alterar/remover/adicionar metadados de um documento PDF. changeMetadata.tags=Título, autor, data, criação, hora, editor, produtor, estatísticas @@ -128,6 +200,7 @@ home.ocr.title=OCR / Limpeza de Digitalizações home.ocr.desc=A limpeza verifica e detecta texto em imagens de um PDF e o adiciona novamente como texto. ocr.tags=reconhecimento, texto, imagem, digitalização, leitura, identificação, detecção, editável + home.extractImages.title=Extrair Imagens home.extractImages.desc=Extrair todas as imagens de um PDF e salvá-las em um arquivo zip. extractImages.tags=imagem, foto, salvar, arquivo, zip, captura, coleta @@ -152,6 +225,7 @@ home.PDFToHTML.title=PDF para HTML home.PDFToHTML.desc=Converter PDF para o formato HTML PDFToHTML.tags=conteúdo web, compatível com navegador + home.PDFToXML.title=PDF para XML home.PDFToXML.desc=Converter PDF para o formato XML PDFToXML.tags=extração-de-dados,conteúdo-estruturado,interoperabilidade,transformação,converter @@ -220,68 +294,96 @@ home.sanitizePdf.title=Sanitizar home.sanitizePdf.desc=Remover scripts e outros elementos de arquivos PDF sanitizePdf.tags=limpar,seguro,protegido,remover-ameaças -home.URLToPDF.title=URL/Site para PDF -home.URLToPDF.desc=Converte qualquer URL http(s) para PDF +home.URLToPDF.title=Converter Site para PDF +home.URLToPDF.desc=Converte qualquer página da internet para um arquivo PDF URLToPDF.tags=captura-de-web,salvar-página,web-para-doc,arquivar home.HTMLToPDF.title=HTML para PDF home.HTMLToPDF.desc=Converte qualquer arquivo HTML ou zip para PDF HTMLToPDF.tags=marcação,conteúdo-web,transformação,converter + home.MarkdownToPDF.title=Markdown para PDF home.MarkdownToPDF.desc=Converte qualquer arquivo Markdown para PDF MarkdownToPDF.tags=marcação,conteúdo-web,transformação,converter + home.getPdfInfo.title=Obter TODAS as Informações de um PDF home.getPdfInfo.desc=Obtém todas as informações possíveis de um PDF getPdfInfo.tags=informações,dados,estatísticas + home.extractPage.title=Extrair Página(s) home.extractPage.desc=Extrai páginas selecionadas de um PDF extractPage.tags=extrair + home.PdfToSinglePage.title=PDF para Página Única Grande home.PdfToSinglePage.desc=Combina todas as páginas de um PDF em uma única página grande PdfToSinglePage.tags=página única -########################## -### TODO: Translate ### -########################## home.showJS.title=Mostrar Javascript home.showJS.desc=Procura e exibe qualquer JavaScript injetado em um PDF showJS.tags=JavaScript +home.autoRedact.title=Auto Redact +home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text +showJS.tags=JavaScript + ########################### # # # WEB PAGES # # # ########################### +#login +login.title=Sign in +login.signin=Sign in +login.rememberme=Remember me +login.invalid=Invalid username or password. +login.locked=Your account has been locked. +login.signinTitle=Please sign in + + +#auto-redact +autoRedact.title=Auto Redact +autoRedact.header=Auto Redact +autoRedact.colorLabel=Colour +autoRedact.textsToRedactLabel=Text to Redact (line-separated) +autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret +autoRedact.useRegexLabel=Use Regex +autoRedact.wholeWordSearchLabel=Whole Word Search +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) +autoRedact.submitButton=Submit + + #showJS -########################## -### TODO: Translate ### -########################## showJS.title=Exibir JavaScript showJS.header=Exibir JavaScript showJS.downloadJS=Download do JavaScript showJS.submit=Exibir + #pdfToSinglePage pdfToSinglePage.title=PDF para Página Única pdfToSinglePage.header=PDF para Página Única pdfToSinglePage.submit=Converter para Página Única + #pageExtracter pageExtracter.title=Extrair Páginas pageExtracter.header=Extrair Páginas pageExtracter.submit=Extrair + #getPdfInfo getPdfInfo.title=Obter Informações do PDF getPdfInfo.header=Obter Informações do PDF getPdfInfo.submit=Obter Informações getPdfInfo.downloadJson=Download JSON + #markdown-to-pdf MarkdownToPDF.title=Markdown para PDF MarkdownToPDF.header=Markdown para PDF @@ -289,12 +391,15 @@ MarkdownToPDF.submit=Converter MarkdownToPDF.help=Trabalho em andamento MarkdownToPDF.credit=Usa o WeasyPrint + + #url-to-pdf URLToPDF.title=URL para PDF URLToPDF.header=URL para PDF URLToPDF.submit=Converter URLToPDF.credit=Usa o WeasyPrint + #html-to-pdf HTMLToPDF.title=HTML para PDF HTMLToPDF.header=HTML para PDF @@ -302,6 +407,7 @@ HTMLToPDF.help=Aceita arquivos HTML e ZIPs contendo html/css/imagens etc necess HTMLToPDF.submit=Converter HTMLToPDF.credit=Usa o WeasyPrint + #sanitizePDF sanitizePDF.title=Sanitizar PDF sanitizePDF.header=Sanitizar um arquivo PDF @@ -312,6 +418,7 @@ sanitizePDF.selectText.4=Remover links sanitizePDF.selectText.5=Remover fontes sanitizePDF.submit=Sanitizar PDF + #addPageNumbers addPageNumbers.title=Adicionar Números de Página addPageNumbers.header=Adicionar Números de Página @@ -321,13 +428,18 @@ addPageNumbers.selectText.3=Posição addPageNumbers.selectText.4=Número Inicial addPageNumbers.selectText.5=Páginas a Numerar addPageNumbers.selectText.6=Texto Personalizado +addPageNumbers.customTextDesc=Custom Text +addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} addPageNumbers.submit=Adicionar Números de Página + #auto-rename auto-rename.title=Rename Automático auto-rename.header=Rename Automático de PDF auto-rename.submit=Rename Automático + #adjustContrast adjustContrast.title=Ajustar Contraste adjustContrast.header=Ajustar Contraste @@ -336,11 +448,13 @@ adjustContrast.brightness=Brilho: adjustContrast.saturation=Saturação: adjustContrast.download=Download + #crop crop.title=Cortar crop.header=Cortar Imagem crop.submit=Enviar + #autoSplitPDF autoSplitPDF.title=Divisão Automática de PDF autoSplitPDF.header=Divisão Automática de PDF @@ -355,6 +469,7 @@ autoSplitPDF.dividerDownload1=Download 'Folha Divisória Automática (mínima).p autoSplitPDF.dividerDownload2=Download 'Folha Divisória Automática (com instruções).pdf' autoSplitPDF.submit=Enviar + #pipeline pipeline.title=Pipeline @@ -363,8 +478,13 @@ pipeline.title=Pipeline pageLayout.title=Layout de Múltiplas Páginas pageLayout.header=Layout de Múltiplas Páginas pageLayout.pagesPerSheet=Páginas por folha: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders pageLayout.submit=Enviar + #scalePages scalePages.title=Ajustar Tamanho/Escala da Página scalePages.header=Ajustar Tamanho/Escala da Página @@ -372,6 +492,7 @@ scalePages.pageSize=Tamanho de uma página do documento. scalePages.scaleFactor=Fator de zoom (corte) de uma página. scalePages.submit=Enviar + #certSign certSign.title=Assinatura com Certificado certSign.header=Assine um PDF com o seu certificado (Em desenvolvimento) @@ -387,6 +508,7 @@ certSign.location=Localização certSign.name=Nome certSign.submit=Assinar PDF + #removeBlanks removeBlanks.title=Remover Páginas em Branco removeBlanks.header=Remover Páginas em Branco @@ -396,6 +518,7 @@ removeBlanks.whitePercent=Porcentagem de Branco (%): removeBlanks.whitePercentDesc=Porcentagem da página que deve ser branca para ser removida removeBlanks.submit=Remover Páginas em Branco + #compare compare.title=Comparar compare.header=Comparar PDFs @@ -403,6 +526,7 @@ compare.document.1=Documento 1 compare.document.2=Documento 2 compare.submit=Comparar + #sign sign.title=Assinar sign.header=Assinar PDFs @@ -412,16 +536,19 @@ sign.text=Inserir Texto sign.clear=Limpar sign.add=Adicionar + #repair repair.title=Reparar repair.header=Reparar PDFs repair.submit=Reparar + #flatten flatten.title=Achatar flatten.header=Achatar PDFs flatten.submit=Achatar + #ScannerImageSplit ScannerImageSplit.selectText.1=Limite de Ângulo: ScannerImageSplit.selectText.2=Define o ângulo absoluto mínimo necessário para que a imagem seja girada (padrão: 10). @@ -449,16 +576,19 @@ ocr.selectText.8=Normal (gerará um erro se o PDF já contiver texto) ocr.selectText.9=Configurações adicionais 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.12=Render Type (Advanced) 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.submit=Processar PDF com OCR + #extractImages extractImages.title=Extrair Imagens extractImages.header=Extrair Imagens extractImages.selectText=Selecione o formato de imagem para converter as imagens extraídas extractImages.submit=Extrair + #File to PDF fileToPDF.title=Arquivo para PDF fileToPDF.header=Converter Qualquer Arquivo para PDF @@ -466,6 +596,7 @@ fileToPDF.credit=Este serviço usa o LibreOffice e o Unoconv para conversão de fileToPDF.supportedFileTypes=Os tipos de arquivo suportados devem incluir os listados abaixo. No entanto, para obter uma lista atualizada completa dos formatos suportados, consulte a documentação do LibreOffice. fileToPDF.submit=Converter para PDF + #compress compress.title=Comprimir compress.header=Comprimir PDF @@ -477,6 +608,7 @@ compress.selectText.4=Modo Automático - Ajusta automaticamente a qualidade para compress.selectText.5=Tamanho Esperado do PDF (por exemplo, 25 MB, 10,8 MB, 25 KB) compress.submit=Comprimir + #Add image addImage.title=Adicionar Imagem addImage.header=Adicionar Imagem ao PDF @@ -484,56 +616,69 @@ addImage.everyPage=Para cada página? addImage.upload=Enviar Imagem addImage.submit=Adicionar Imagem + #merge merge.title=Mesclar merge.header=Mesclar Vários PDFs (2+) +merge.sortByName=Sort by name +merge.sortByDate=Sort by date merge.submit=Mesclar + #pdfOrganiser pdfOrganiser.title=Organizador de Páginas pdfOrganiser.header=Organizador de Páginas PDF pdfOrganiser.submit=Reorganizar Páginas + #multiTool multiTool.title=Multiferramenta de PDF multiTool.header=Multiferramenta de PDF + #pageRemover pageRemover.title=Remover Página pageRemover.header=Remover Páginas do PDF pageRemover.pagesToDelete=Páginas a serem excluídas (insira uma lista separada por vírgulas de números de página): pageRemover.submit=Excluir Páginas + #rotate rotate.title=Girar PDF rotate.header=Girar PDF rotate.selectAngle=Selecione o ângulo de rotação (múltiplos de 90 graus): rotate.submit=Girar + #merge split.title=Dividir PDF split.header=Dividir PDF split.desc.1=Os números selecionados correspondem às páginas onde você deseja fazer a divisão. -split.desc.2,3,4,5,6,7,8=Por exemplo, selecionar 1,3,7-8 dividirá um documento de 10 páginas em 6 PDFs separados da seguinte forma: -split.desc.2=Documento 1: Página 1 -split.desc.3=Documento 2: Páginas 2 e 3 -split.desc.4=Documento 3: Páginas 4, 5 e 6 -split.desc.5=Documento 4: Página 7 -split.desc.6=Documento 5: Página 8 -split.desc.7=Documento 6: Páginas 9 e 10 +split.desc.2=Por exemplo, selecionar 1,3,7-8 dividirá um documento de 10 páginas em 6 PDFs separados da seguinte forma: +split.desc.3=Documento Nº1: Página 1 +split.desc.4=Documento Nº2: Páginas 2 e 3 +split.desc.5=Documento Nº3: Páginas 4, 5 e 6 +split.desc.6=Documento Nº4: Página 7 +split.desc.7=Documento Nº5: Página 8 +split.desc.8=Documento Nº6: Páginas 9 e 10 split.splitPages=Digite as páginas para a divisão: split.submit=Dividir + #merge imageToPDF.title=Imagem para PDF imageToPDF.header=Converter Imagem para PDF imageToPDF.submit=Converter -imageToPDF.selectText.1=Esticar para Ajustar +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=Girar Automaticamente imageToPDF.selectText.3=Lógica de Vários Arquivos (Ativada apenas ao trabalhar com várias imagens) imageToPDF.selectText.4=Mesclar em um Único PDF imageToPDF.selectText.5=Converter em PDFs Separados - + + #pdfToImage pdfToImage.title=PDF para Imagem pdfToImage.header=Converter PDF para Imagem @@ -569,6 +714,7 @@ addPassword.selectText.15=Restringe o que pode ser feito com o documento após a addPassword.selectText.16=Restringe a abertura do próprio documento addPassword.submit=Criptografar + #watermark watermark.title=Adicionar Marca d'Água watermark.header=Adicionar Marca d'Água @@ -579,14 +725,10 @@ watermark.selectText.4=Rotação (0-360) watermark.selectText.5=Espaçamento Horizontal (widthSpacer) watermark.selectText.6=Espaçamento Vertical (heightSpacer) watermark.selectText.7=Opacidade (0% - 100%) +watermark.selectText.8=Tipo de Marca d'Água +watermark.selectText.9=Imagem da Marca d'Água watermark.submit=Adicionar Marca d'Água -#remove-watermark -remove-watermark.title=Remover Marca d'Água -remove-watermark.header=Remover Marca d'Água -remove-watermark.selectText.1=Selecione o PDF para Remover a Marca d'Água -remove-watermark.selectText.2=Texto da Marca d'Água -remove-watermark.submit=Remover Marca d'Água #Change permissions permissions.title=Alterar Permissões @@ -604,6 +746,7 @@ permissions.selectText.9=Impedir Impressão permissions.selectText.10=Impedir Impressão de Formatos Diferentes permissions.submit=Mudar + #remove password removePassword.title=Remover Senha removePassword.header=Remover Senha (Descriptografar) @@ -613,7 +756,7 @@ removePassword.submit=Remover #changeMetadata -changeMetadata.title=Alterar Metadados +changeMetadata.title=Título: changeMetadata.header=Alterar Metadados changeMetadata.selectText.1=Edite as Variáveis que Deseja Alterar changeMetadata.selectText.2=Excluir Todos os Metadados @@ -631,11 +774,6 @@ changeMetadata.selectText.4=Outros Metadados changeMetadata.selectText.5=Adicionar Entrada de Metadados Personalizados changeMetadata.submit=Mudar -#xlsToPdf -xlsToPdf.title=Excel para PDF -xlsToPdf.header=Excel para PDF -xlsToPdf.selectText.1=Selecione a Planilha Excel XLS ou XLSX para Converter -xlsToPdf.convert=Converter #pdfToPDFA pdfToPDFA.title=PDF para PDF/A @@ -643,6 +781,7 @@ pdfToPDFA.header=PDF para PDF/A pdfToPDFA.credit=Este serviço usa OCRmyPDF para Conversão de PDF/A pdfToPDFA.submit=Converter + #PDFToWord PDFToWord.title=PDF para Word PDFToWord.header=PDF para Word @@ -650,6 +789,7 @@ PDFToWord.selectText.1=Formato do Arquivo de Saída PDFToWord.credit=Este serviço usa o LibreOffice para Conversão de Arquivos. PDFToWord.submit=Converter + #PDFToPresentation PDFToPresentation.title=PDF para Apresentação PDFToPresentation.header=PDF para Apresentação @@ -657,6 +797,7 @@ PDFToPresentation.selectText.1=Formato do Arquivo de Saída PDFToPresentation.credit=Este serviço usa o LibreOffice para Conversão de Arquivos. PDFToPresentation.submit=Converter + #PDFToText PDFToText.title=PDF para Texto/RTF PDFToText.header=PDF para Texto/RTF @@ -664,12 +805,14 @@ PDFToText.selectText.1=Formato do Arquivo de Saída PDFToText.credit=Este serviço usa o LibreOffice para Conversão de Arquivos. PDFToText.submit=Converter + #PDFToHTML PDFToHTML.title=PDF para HTML PDFToHTML.header=PDF para HTML PDFToHTML.credit=Este serviço usa o LibreOffice para Conversão de Arquivos. PDFToHTML.submit=Converter + #PDFToXML PDFToXML.title=PDF para XML PDFToXML.header=PDF para XML diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties index 11a5c090..46dad1ba 100644 --- a/src/main/resources/messages_ro_RO.properties +++ b/src/main/resources/messages_ro_RO.properties @@ -31,6 +31,24 @@ sizes.medium=Medium sizes.large=Large sizes.x-large=X-Large error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +delete=Delete +username=Username +password=Password +welcome=Welcome +property=Property +black=Black +white=White +red=Red +green=Green +blue=Blue +custom=Custom... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -54,6 +72,55 @@ settings.downloadOption.1=Deschide în aceeași fereastră settings.downloadOption.2=Deschide într-o fereastră nouă settings.downloadOption.3=Descarcă fișierul settings.zipThreshold=Împachetează fișierele când numărul de fișiere descărcate depășește +settings.signOut=Sign Out +settings.accountSettings=Account Settings + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Account Settings +account.accountSettings=Account Settings +account.adminSettings=Admin Settings - View and Add Users +account.userControlSettings=User Control Settings +account.changeUsername=Change Username +account.changeUsername=Change Username +account.password=Confirmation Password +account.oldPassword=Old password +account.newPassword=New Password +account.changePassword=Change Password +account.confirmNewPassword=Confirm New Password +account.signOut=Sign Out +account.yourApiKey=Your API Key +account.syncTitle=Sync browser settings with Account +account.settingsCompare=Settings Comparison: +account.property=Property +account.webBrowserSettings=Web Browser Setting +account.syncToBrowser=Sync Account -> Browser +account.syncToAccount=Sync Account <- Browser + + +adminUserSettings.title=User Control Settings +adminUserSettings.header=Admin User Control Settings +adminUserSettings.admin=Admin +adminUserSettings.user=User +adminUserSettings.addUser=Add New User +adminUserSettings.roles=Roles +adminUserSettings.role=Role +adminUserSettings.actions=Actions +adminUserSettings.apiUser=Limited API User +adminUserSettings.webOnlyUser=Web Only User +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Save User ############# # HOME-PAGE # @@ -256,22 +323,42 @@ home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page -########################## -### TODO: Translate ### -########################## home.showJS.title=Show Javascript home.showJS.desc=Searches and displays any JS injected into a PDF showJS.tags=JS +home.autoRedact.title=Auto Redact +home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### +#login +login.title=Sign in +login.signin=Sign in +login.rememberme=Remember me +login.invalid=Invalid username or password. +login.locked=Your account has been locked. +login.signinTitle=Please sign in + + +#auto-redact +autoRedact.title=Auto Redact +autoRedact.header=Auto Redact +autoRedact.colorLabel=Colour +autoRedact.textsToRedactLabel=Text to Redact (line-separated) +autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret +autoRedact.useRegexLabel=Use Regex +autoRedact.wholeWordSearchLabel=Whole Word Search +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) +autoRedact.submitButton=Submit + + #showJS -########################## -### TODO: Translate ### -########################## showJS.title=Show Javascript showJS.header=Show Javascript showJS.downloadJS=Download Javascript @@ -341,6 +428,9 @@ addPageNumbers.selectText.3=Position addPageNumbers.selectText.4=Starting Number addPageNumbers.selectText.5=Pages to Number addPageNumbers.selectText.6=Custom Text +addPageNumbers.customTextDesc=Custom Text +addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} addPageNumbers.submit=Add Page Numbers @@ -388,6 +478,10 @@ pipeline.title=Pipeline pageLayout.title=Multi Page Layout pageLayout.header=Multi Page Layout pageLayout.pagesPerSheet=Pages per sheet: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders pageLayout.submit=Submit @@ -526,6 +620,8 @@ addImage.submit=Adăugare imagine #merge merge.title=Unire merge.header=Unirea mai multor PDF-uri (2+) +merge.sortByName=Sort by name +merge.sortByDate=Sort by date merge.submit=Unire @@ -573,7 +669,10 @@ split.submit=Împarte imageToPDF.title=Imagine în PDF imageToPDF.header=Imagine în PDF imageToPDF.submit=Convertă -imageToPDF.selectText.1=Redimensionare pentru a se potrivi +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=Rotire automată a PDF-ului imageToPDF.selectText.3=Logica pentru mai multe fișiere (activată numai dacă se lucrează cu mai multe imagini) imageToPDF.selectText.4=Unifică într-un singur PDF @@ -626,17 +725,11 @@ watermark.selectText.4=Rotire (0-360): watermark.selectText.5=widthSpacer (Spațiu între fiecare filigran pe orizontală): watermark.selectText.6=heightSpacer (Spațiu între fiecare filigran pe verticală): watermark.selectText.7=Opacitate (0% - 100%): +watermark.selectText.8=Watermark Type: +watermark.selectText.9=Watermark Image: watermark.submit=Adăugați Filigran -#remove-watermark -remove-watermark.title=Eliminați Filigran -remove-watermark.header=Eliminați Filigran -remove-watermark.selectText.1=Selectați PDF-ul de la care să eliminați filigranul: -remove-watermark.selectText.2=Textul Filigranului: -remove-watermark.submit=Eliminați Filigran - - #Change permissions permissions.title=Schimbați Permisiunile permissions.header=Schimbați Permisiunile @@ -682,13 +775,6 @@ changeMetadata.selectText.5=Adăugați Intrare Metadate Personalizate changeMetadata.submit=Schimbare -#xlsToPdf -xlsToPdf.title=Excel to PDF -xlsToPdf.header=Excel to PDF -xlsToPdf.selectText.1=Selectați fișierul Excel XLS sau XLSX pentru a converti -xlsToPdf.convert=convert - - #pdfToPDFA pdfToPDFA.title=PDF către PDF/A pdfToPDFA.header=PDF către PDF/A diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index e416988a..272afccf 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -31,6 +31,24 @@ sizes.medium=Medium sizes.large=Large sizes.x-large=X-Large error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +delete=Delete +username=Username +password=Password +welcome=Welcome +property=Property +black=Black +white=White +red=Red +green=Green +blue=Blue +custom=Custom... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -54,6 +72,55 @@ settings.downloadOption.1=Открыть в том же окне settings.downloadOption.2=Открыть в новом окне settings.downloadOption.3=Загрузить файл settings.zipThreshold=Zip-файлы, когда количество загруженных файлов превышает +settings.signOut=Sign Out +settings.accountSettings=Account Settings + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Account Settings +account.accountSettings=Account Settings +account.adminSettings=Admin Settings - View and Add Users +account.userControlSettings=User Control Settings +account.changeUsername=Change Username +account.changeUsername=Change Username +account.password=Confirmation Password +account.oldPassword=Old password +account.newPassword=New Password +account.changePassword=Change Password +account.confirmNewPassword=Confirm New Password +account.signOut=Sign Out +account.yourApiKey=Your API Key +account.syncTitle=Sync browser settings with Account +account.settingsCompare=Settings Comparison: +account.property=Property +account.webBrowserSettings=Web Browser Setting +account.syncToBrowser=Sync Account -> Browser +account.syncToAccount=Sync Account <- Browser + + +adminUserSettings.title=User Control Settings +adminUserSettings.header=Admin User Control Settings +adminUserSettings.admin=Admin +adminUserSettings.user=User +adminUserSettings.addUser=Add New User +adminUserSettings.roles=Roles +adminUserSettings.role=Role +adminUserSettings.actions=Actions +adminUserSettings.apiUser=Limited API User +adminUserSettings.webOnlyUser=Web Only User +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Save User ############# # HOME-PAGE # @@ -256,28 +323,49 @@ home.PdfToSinglePage.desc=Объединяет все страницы PDF в о PdfToSinglePage.tags=single page -########################## -### TODO: Translate ### -########################## home.showJS.title=Показать Javascript home.showJS.desc=Ищет и отображает любой JS, внедренный в PDF-файл. showJS.tags=JS +home.autoRedact.title=Auto Redact +home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### +#login +login.title=Sign in +login.signin=Sign in +login.rememberme=Remember me +login.invalid=Invalid username or password. +login.locked=Your account has been locked. +login.signinTitle=Please sign in + + +#auto-redact +autoRedact.title=Auto Redact +autoRedact.header=Auto Redact +autoRedact.colorLabel=Colour +autoRedact.textsToRedactLabel=Text to Redact (line-separated) +autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret +autoRedact.useRegexLabel=Use Regex +autoRedact.wholeWordSearchLabel=Whole Word Search +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) +autoRedact.submitButton=Submit + + #showJS -########################## -### TODO: Translate ### -########################## showJS.title=Показать Javascript showJS.header=Показать Javascript showJS.downloadJS=Скачать Javascript showJS.submit=Показать + #pdfToSinglePage pdfToSinglePage.title=PDF на одну страницу pdfToSinglePage.header=PDF на одну страницу @@ -341,6 +429,9 @@ addPageNumbers.selectText.3=Позиция addPageNumbers.selectText.4=Стартовый номер addPageNumbers.selectText.5=Страницы для нумерации addPageNumbers.selectText.6=Свой текст +addPageNumbers.customTextDesc=Custom Text +addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} addPageNumbers.submit=Добавить номера страниц @@ -388,9 +479,12 @@ pipeline.title=Pipeline pageLayout.title=Многостраничный макет pageLayout.header=Многостраничный макет pageLayout.pagesPerSheet=Страниц на одном листе: +pageLayout.addBorder=Add Borders +pageLayout.submit=Submit pageLayout.submit=Отправить + #scalePages scalePages.title=Отрегулировать масштаб страницы scalePages.header=Отрегулировать масштаб страницы @@ -526,6 +620,8 @@ addImage.submit=Добавить изображение #merge merge.title=Объединить merge.header=Объединение нескольких PDF-файлов (2+) +merge.sortByName=Sort by name +merge.sortByDate=Sort by date merge.submit=Объединить @@ -573,7 +669,10 @@ split.submit=Разделить imageToPDF.title=Изображение в PDF imageToPDF.header=Изображение в PDF imageToPDF.submit=Конвертировать -imageToPDF.selectText.1=Растянуть, чтобы соответствовать +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=Автоматический поворот PDF imageToPDF.selectText.3=Многофайловая логика (включена только при работе с несколькими изображениями) imageToPDF.selectText.4=Объединить в один PDF @@ -626,17 +725,11 @@ watermark.selectText.4=Поворот (0-360): watermark.selectText.5=widthSpacer (пробел между каждым водяным знаком по горизонтали): watermark.selectText.6=heightSpacer (пробел между каждым водяным знаком по вертикали): watermark.selectText.7=Непрозрачность (0% - 100%): +watermark.selectText.8=Watermark Type: +watermark.selectText.9=Watermark Image: watermark.submit=Добавить водяной знак -#remove-watermark -remove-watermark.title=Удалить водяной знак -remove-watermark.header=Удалить водяной знак -remove-watermark.selectText.1=Выберите PDF, чтобы удалить водяной знак из: -remove-watermark.selectText.2=Текст водяного знака: -remove-watermark.submit=Удалить водяной знак - - #Change permissions permissions.title=Изменить разрешения permissions.header=Изменить разрешения @@ -682,13 +775,6 @@ changeMetadata.selectText.5=Добавить пользовательскую з changeMetadata.submit=Изменить -#xlsToPdf -xlsToPdf.title=Excel в PDF -xlsToPdf.header=Excel в PDF -xlsToPdf.selectText.1=Выберите книгу Excel XLS или XLSX для преобразования -xlsToPdf.convert=Конвертировать - - #pdfToPDFA pdfToPDFA.title=PDF в PDF/A pdfToPDFA.header=PDF в PDF/A diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index 06637e86..e2fc2eb8 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -31,6 +31,24 @@ sizes.medium=Medium sizes.large=Large sizes.x-large=X-Large error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +delete=Delete +username=Username +password=Password +welcome=Welcome +property=Property +black=Black +white=White +red=Red +green=Green +blue=Blue +custom=Custom... + +changedCredsMessage=Credentials changed! +notAuthenticatedMessage=User not authenticated. +userNotFoundMessage=User not found. +incorrectPasswordMessage=Current password is incorrect. +usernameExistsMessage=New Username already exists. + ############# @@ -54,6 +72,55 @@ settings.downloadOption.1=Öppnas i samma fönster settings.downloadOption.2=Öppna i nytt fönster settings.downloadOption.3=Ladda ner fil settings.zipThreshold=Zip-filer när antalet nedladdade filer överskrider +settings.signOut=Sign Out +settings.accountSettings=Account Settings + + + +changeCreds.title=Change Credentials +changeCreds.header=Update Your Account Details +changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) +changeCreds.newUsername=New Username +changeCreds.oldPassword=Current Password +changeCreds.newPassword=New Password +changeCreds.confirmNewPassword=Confirm New Password +changeCreds.submit=Submit Changes + + + +account.title=Account Settings +account.accountSettings=Account Settings +account.adminSettings=Admin Settings - View and Add Users +account.userControlSettings=User Control Settings +account.changeUsername=Change Username +account.changeUsername=Change Username +account.password=Confirmation Password +account.oldPassword=Old password +account.newPassword=New Password +account.changePassword=Change Password +account.confirmNewPassword=Confirm New Password +account.signOut=Sign Out +account.yourApiKey=Your API Key +account.syncTitle=Sync browser settings with Account +account.settingsCompare=Settings Comparison: +account.property=Property +account.webBrowserSettings=Web Browser Setting +account.syncToBrowser=Sync Account -> Browser +account.syncToAccount=Sync Account <- Browser + + +adminUserSettings.title=User Control Settings +adminUserSettings.header=Admin User Control Settings +adminUserSettings.admin=Admin +adminUserSettings.user=User +adminUserSettings.addUser=Add New User +adminUserSettings.roles=Roles +adminUserSettings.role=Role +adminUserSettings.actions=Actions +adminUserSettings.apiUser=Limited API User +adminUserSettings.webOnlyUser=Web Only User +adminUserSettings.forceChange=Force user to change username/password on login +adminUserSettings.submit=Save User ############# # HOME-PAGE # @@ -256,22 +323,42 @@ home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page -########################## -### TODO: Translate ### -########################## home.showJS.title=Show Javascript home.showJS.desc=Searches and displays any JS injected into a PDF showJS.tags=JS +home.autoRedact.title=Auto Redact +home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### +#login +login.title=Sign in +login.signin=Sign in +login.rememberme=Remember me +login.invalid=Invalid username or password. +login.locked=Your account has been locked. +login.signinTitle=Please sign in + + +#auto-redact +autoRedact.title=Auto Redact +autoRedact.header=Auto Redact +autoRedact.colorLabel=Colour +autoRedact.textsToRedactLabel=Text to Redact (line-separated) +autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret +autoRedact.useRegexLabel=Use Regex +autoRedact.wholeWordSearchLabel=Whole Word Search +autoRedact.customPaddingLabel=Custom Extra Padding +autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) +autoRedact.submitButton=Submit + + #showJS -########################## -### TODO: Translate ### -########################## showJS.title=Show Javascript showJS.header=Show Javascript showJS.downloadJS=Download Javascript @@ -341,6 +428,9 @@ addPageNumbers.selectText.3=Position addPageNumbers.selectText.4=Starting Number addPageNumbers.selectText.5=Pages to Number addPageNumbers.selectText.6=Custom Text +addPageNumbers.customTextDesc=Custom Text +addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} addPageNumbers.submit=Add Page Numbers @@ -388,6 +478,10 @@ pipeline.title=Pipeline pageLayout.title=Multi Page Layout pageLayout.header=Multi Page Layout pageLayout.pagesPerSheet=Pages per sheet: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=Add Borders pageLayout.submit=Submit @@ -526,6 +620,8 @@ addImage.submit=Lägg till bild #merge merge.title=Sammanfoga merge.header=Slå samman flera PDF-filer (2+) +merge.sortByName=Sort by name +merge.sortByDate=Sort by date merge.submit=Slå samman @@ -573,7 +669,10 @@ split.submit=Dela imageToPDF.title=Bild till PDF imageToPDF.header=Bild till PDF imageToPDF.submit=Konvertera -imageToPDF.selectText.1=Sträck för att passa +imageToPDF.selectLabel=Image Fit Options +imageToPDF.fillPage=Fill Page +imageToPDF.fitDocumentToImage=Fit Page to Image +imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.selectText.2=Rotera PDF automatiskt imageToPDF.selectText.3=Multifillogik (Endast aktiverad om man arbetar med flera bilder) imageToPDF.selectText.4=Slå samman till en enda PDF @@ -626,17 +725,11 @@ watermark.selectText.4=Rotation (0-360): watermark.selectText.5=widthSpacer (mellanrum mellan varje vattenstämpel horisontellt): watermark.selectText.6=heightSpacer (mellanrum mellan varje vattenstämpel vertikalt): watermark.selectText.7=Opacitet (0% - 100%): +watermark.selectText.8=Watermark Type: +watermark.selectText.9=Watermark Image: watermark.submit=Lägg till vattenstämpel -#remove-watermark -remove-watermark.title=Ta bort vattenstämpel -remove-watermark.header=Ta bort vattenstämpel -remove-watermark.selectText.1=Välj PDF för att ta bort vattenstämpel från: -remove-watermark.selectText.2=Vattenstämpeltext: -remove-watermark.submit=Ta bort vattenstämpel - - #Change permissions permissions.title=Ändra behörigheter permissions.header=Ändra behörigheter @@ -682,13 +775,6 @@ changeMetadata.selectText.5=Lägg till anpassad metadatapost changeMetadata.submit=Ändra -#xlsToPdf -xlsToPdf.title=Excel till PDF -xlsToPdf.header=Excel till PDF -xlsToPdf.selectText.1=Välj XLS eller XLSX Excel-ark att konvertera -xlsToPdf.convert=konvertera - - #pdfToPDFA pdfToPDFA.title=PDF till PDF/A pdfToPDFA.header=PDF till PDF/A diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index 7ece8479..8a3554a9 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -12,25 +12,43 @@ genericSubmit=提交 processTimeWarning=警告:此过程可能需要多达一分钟,具体时间取决于文件大小 pageOrderPrompt=页面顺序(输入逗号分隔的页码列表): goToPage=到 -true=True -false=False +true=对 +false=错 unknown=未知 save=保存 close=关闭 -filesSelected=\u9009\u62E9\u7684\u6587\u4EF6 -noFavourites=\u6CA1\u6709\u6DFB\u52A0\u6536\u85CF\u5939 -bored=\u65E0\u804A\u7B49\u5F85\uFF1F -alphabet=\u5B57\u6BCD\u8868 -downloadPdf=\u4E0B\u8F7DPDF -text=\u6587\u672C -font=\u5B57\u4F53 +filesSelected=选中的文件 +noFavourites=没有添加收藏夹 +bored=无聊等待吗? +alphabet=字母表 +downloadPdf=下载PDF +text=文本 +font=字体 selectFillter=-- 选择-- pageNum=页码 -sizes.small=Small -sizes.medium=Medium -sizes.large=Large -sizes.x-large=X-Large -error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect +sizes.small=小型尺寸 +sizes.medium=中型尺寸 +sizes.large=大型尺寸 +sizes.x-large=稍大型尺寸 +error.pdfPassword=PDF 文档有密码,未提供密码或密码不正确 +delete=删除 +username=用户名 +password=密码 +welcome=欢迎 +property=资产 +black=Black +white=White +red=Red +green=Green +blue=Blue +custom=Custom... + +changedCredsMessage=凭证已更改! +notAuthenticatedMessage=用户未经过身份验证。 +userNotFoundMessage=未找到用户。 +incorrectPasswordMessage=当前密码不正确。 +usernameExistsMessage=新用户名已存在。 + ############# @@ -54,349 +72,415 @@ settings.downloadOption.1=在同一窗口打开 settings.downloadOption.2=在新窗口中打开 settings.downloadOption.3=下载文件 settings.zipThreshold=当下载的文件数量超过限制时,将文件压缩。 +settings.signOut=登出 +settings.accountSettings=帐号设定 + + + +changeCreds.title=更改凭证 +changeCreds.header=更新您的账户详情 +changeCreds.changeUserAndPassword=您正在使用默认登录凭据。请输入新密码(如果需要,还可以输入新用户名) +changeCreds.newUsername=新用户名 +changeCreds.oldPassword=当前密码 +changeCreds.newPassword=新密码 +changeCreds.confirmNewPassword=确认新密码 +changeCreds.submit=提交更改 + + + +account.title=帐号设定 +account.accountSettings=帐号设定 +account.adminSettings=管理员设置 - 查看和添加用户 +account.userControlSettings=用户控制设置 +account.changeUsername=更改用户名 +account.password=确认密码 +account.oldPassword=旧密码 +account.newPassword=新密码 +account.changePassword=更改密码 +account.confirmNewPassword=确认新密码 +account.signOut=退出登录 +account.yourApiKey=您的 API 密钥 +account.syncTitle=将浏览器设置与账户同步 +account.settingsCompare=设置比较: +account.property=属性 +account.webBrowserSettings=Web 浏览器设置 +account.syncToBrowser=同步账户 -> 浏览器 +account.syncToAccount=同步账户 <- 浏览器 + + +adminUserSettings.title=用户控制设置 +adminUserSettings.header=管理员用户控制设置 +adminUserSettings.admin=管理员 +adminUserSettings.user=用户 +adminUserSettings.addUser=添加新用户 +adminUserSettings.roles=角色 +adminUserSettings.role=角色 +adminUserSettings.actions=操作 +adminUserSettings.apiUser=有限 API 用户 +adminUserSettings.webOnlyUser=仅限 Web 用户 +adminUserSettings.forceChange=强制用户在登录时更改用户名/密码 +adminUserSettings.submit=保存用户 ############# # HOME-PAGE # ############# -home.desc=您的本地托管一站式服务,满足您的所有PDF需求。 +home.desc=CZL一站式服务,满足您的所有PDF需求。 home.multiTool.title=PDF多功能工具 home.multiTool.desc=合并、旋转、重新排列和删除PDF页面 -multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side +multiTool.tags=多工具,多操作,用户界面,点击拖动,前端,客户端 home.merge.title=合并 home.merge.desc=轻松合并多个PDF为一个。 -merge.tags=merge,Page operations,Back end,server side +merge.tags=合并,页面操作,后端,服务器端 home.split.title=拆分 home.split.desc=将 PDF 拆分为多个文档。 -split.tags=Page operations,divide,Multi Page,cut,server side +split.tags=页面操作,划分,多页面,剪切,服务器端 home.rotate.title=旋转 home.rotate.desc=旋转PDF。 -rotate.tags=server side +rotate.tags=服务器端 home.imageToPdf.title=转换图像到PDF -home.imageToPdf.desc=转换图像(PNG, JPEG, GIF)到 PDF。 -imageToPdf.tags=conversion,img,jpg,picture,photo +home.imageToPdf.desc=将图像(PNG、JPEG、GIF)转换为PDF。 +imageToPdf.tags=转换、图像、JPG、图片、照片 home.pdfToImage.title=转换PDF到图像 -home.pdfToImage.desc=转换PDF到图像(PNG, JPEG, GIF) -pdfToImage.tags=conversion,img,jpg,picture,photo +home.pdfToImage.desc=将PDF转换为图像(PNG、JPEG、GIF)。 +pdfToImage.tags=转换、图像、JPG、图片、照片 home.pdfOrganiser.title=整理 -home.pdfOrganiser.desc=按任何顺序删除/重新排列页面。 -pdfOrganiser.tags=duplex,even,odd,sort,move - +home.pdfOrganiser.desc=按任意顺序删除/重新排列页面。 +pdfOrganiser.tags=双面、偶数、奇数、排序、移动 home.addImage.title=在PDF中添加图片 -home.addImage.desc=将图像添加到PDF的设定位置上 -addImage.tags=img,jpg,picture,photo +home.addImage.desc=将图像添加到PDF的指定位置。 +addImage.tags=图像、JPG、图片、照片 home.watermark.title=添加水印 -home.watermark.desc=在PDF中添加一个自定义的水印。 -watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo +home.watermark.desc=在PDF中添加自定义水印。 +watermark.tags=文本、重复、标签、自定义、版权、商标、图像、JPG、图片、照片 home.permissions.title=更改权限 -home.permissions.desc=改变你的PDF文档的权限。 -permissions.tags=read,write,edit,print - +home.permissions.desc=更改PDF文档的权限。 +permissions.tags=阅读、写入、编辑、打印 home.removePages.title=删除 -home.removePages.desc=从你的PDF文档中删除不需要的页面。 -removePages.tags=Remove pages,delete pages +home.removePages.desc=从PDF文档中删除不需要的页面。 +removePages.tags=删除页面、删除 home.addPassword.title=添加密码 -home.addPassword.desc=用密码来加密你的PDF文档。 -addPassword.tags=secure,security +home.addPassword.desc=使用密码对PDF文档进行加密。 +addPassword.tags=安全、密码、加密 home.removePassword.title=删除密码 -home.removePassword.desc=从你的PDF文档中移除密码保护。 -removePassword.tags=secure,Decrypt,security,unpassword,delete password +home.removePassword.desc=从PDF文档中移除密码保护。 +removePassword.tags=安全、解密、密码、安全性、删除密码 home.compressPdfs.title=压缩 -home.compressPdfs.desc=压缩PDF文件以减少其文件大小。 -compressPdfs.tags=squish,small,tiny - +home.compressPdfs.desc=压缩PDF文件以减小文件大小。 +compressPdfs.tags=压缩、小、微小 home.changeMetadata.title=更改元数据 home.changeMetadata.desc=更改/删除/添加PDF文档的元数据。 -changeMetadata.tags==Title,author,date,creation,time,publisher,producer,stats +changeMetadata.tags=标题、作者、日期、创建、时间、发布者、制作人、统计数据 home.fileToPDF.title=将文件转换为PDF文件 -home.fileToPDF.desc=将几乎所有文件转换为PDF(DOCX、PNG、XLS、PPT、TXT等) -fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint +home.fileToPDF.desc=将几乎所有文件转换为PDF(DOCX、PNG、XLS、PPT、TXT等)。 +fileToPDF.tags=转换、格式、文档、图片、幻灯片、文本、转换、办公室、文档、Word、Excel、PowerPoint home.ocr.title=运行OCR/清理扫描 -home.ocr.desc=清理和检测PDF中的文本图像,并将其重新添加为文本。 -ocr.tags=recognition,text,image,scan,read,identify,detection,editable +home.ocr.desc=清理和识别PDF中的图像文本,并将其转换为可编辑文本。 +ocr.tags=识别、文本、图像、扫描、阅读、识别、检测、可编辑 home.extractImages.title=提取图像 -home.extractImages.desc=从PDF中提取所有的图像并将其保存到压缩包中。 -extractImages.tags=picture,photo,save,archive,zip,capture,grab +home.extractImages.desc=从PDF中提取所有图像并保存到压缩包中。 +extractImages.tags=图片、照片、保存、归档、压缩包、截取、抓取 home.pdfToPDFA.title=PDF To PDF/A -home.pdfToPDFA.desc=将PDF转换为PDF/A以便长期保存 -pdfToPDFA.tags=archive,long-term,standard,conversion,storage,preservation +home.pdfToPDFA.desc=将PDF转换为PDF/A以进行长期保存。 +pdfToPDFA.tags=归档、长期、标准、转换、存储、保存 -home.PDFToWord.title=PDF to Word +home.PDFToWord.title=PDF转Word home.PDFToWord.desc=将PDF转换为Word格式(DOC、DOCX和ODT)。 -PDFToWord.tags=doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile +PDFToWord.tags=doc、docx、odt、word、转换、格式、办公、Microsoft、文档 -home.PDFToPresentation.title=PDF To Presentation -home.PDFToPresentation.desc=将PDF转换成演示文稿格式(PPT、PPTX和ODP)。 -PDFToPresentation.tags=slides,show,office,microsoft +home.PDFToPresentation.title=PDF转演示文稿 +home.PDFToPresentation.desc=将PDF转换为演示文稿格式(PPT、PPTX和ODP)。 +PDFToPresentation.tags=幻灯片、展示、办公、Microsoft -home.PDFToText.title=PDF to RTF (Text) -home.PDFToText.desc=将PDF转换为文本或RTF格式 -PDFToText.tags=richformat,richtextformat,rich text format +home.PDFToText.title=PDF转RTF(文本) +home.PDFToText.desc=将PDF转换为文本或RTF格式。 +PDFToText.tags=富文本格式、RTF、富文本格式 -home.PDFToHTML.title=PDF To HTML -home.PDFToHTML.desc=将PDF转换为HTML格式 -PDFToHTML.tags=web content,browser friendly +home.PDFToHTML.title=PDF转HTML +home.PDFToHTML.desc=将PDF转换为HTML格式。 +PDFToHTML.tags=网页内容、浏览器友好 +home.PDFToXML.title=PDF转XML +home.PDFToXML.desc=将PDF转换为XML格式。 +PDFToXML.tags=数据提取、结构化内容、互操作、转换 -home.PDFToXML.title=PDF To XML -home.PDFToXML.desc=将PDF转换为XML格式 -PDFToXML.tags=data-extraction,structured-content,interop,transformation,convert +home.ScannerImageSplit.title=检测/分割扫描图像 +home.ScannerImageSplit.desc=从一张照片或PDF中分割出多张照片。 +ScannerImageSplit.tags=分离、自动检测、扫描、多张照片、整理 -home.ScannerImageSplit.title=检测/分割扫描的照片 -home.ScannerImageSplit.desc=从一张照片/PDF中分割出多张照片 -ScannerImageSplit.tags=separate,auto-detect,scans,multi-photo,organize +home.sign.title=标志 +home.sign.desc=通过绘图、文字或图像向PDF添加签名 +sign.tags=授权、缩写、手绘签名、文本签名、图像签名 -home.sign.title=\u6807\u5FD7 -home.sign.desc=\u901A\u8FC7\u7ED8\u56FE\u3001\u6587\u672C\u6216\u56FE\u50CF\u5411 PDF \u6DFB\u52A0\u7B7E\u540D -sign.tags=authorize,initials,drawn-signature,text-sign,image-signature +home.flatten.title=展平 +home.flatten.desc=从PDF中删除所有互动元素和表单 +flatten.tags=静态、停用、非交互、简化 -home.flatten.title=\u5C55\u5E73 -home.flatten.desc=\u4ECE PDF \u4E2D\u5220\u9664\u6240\u6709\u4EA4\u4E92\u5143\u7D20\u548C\u8868\u5355 -flatten.tags=static,deactivate,non-interactive,streamline +home.repair.title=修复 +home.repair.desc=尝试修复损坏/损坏的PDF +repair.tags=修复、恢复、纠正、恢复 -home.repair.title=\u4FEE\u590D -home.repair.desc=\u5C1D\u8BD5\u4FEE\u590D\u635F\u574F/\u635F\u574F\u7684 PDF -repair.tags=fix,restore,correction,recover +home.removeBlanks.title=删除空白页 +home.removeBlanks.desc=检测并删除文档中的空白页 +removeBlanks.tags=清理、简化、非内容、整理 -home.removeBlanks.title=\u5220\u9664\u7A7A\u767D\u9875 -home.removeBlanks.desc=\u68C0\u6D4B\u5E76\u5220\u9664\u6587\u6863\u4E2D\u7684\u7A7A\u767D\u9875 -removeBlanks.tags=cleanup,streamline,non-content,organize +home.compare.title=比较 +home.compare.desc=比较并显示两个PDF文档之间的差异 +compare.tags=区分、对比、更改、分析 -home.compare.title=\u6BD4\u8F83 -home.compare.desc=\u6BD4\u8F83\u5E76\u663E\u793A 2 \u4E2A PDF \u6587\u6863\u4E4B\u95F4\u7684\u5DEE\u5F02 -compare.tags=differentiate,contrast,changes,analysis +home.certSign.title=使用证书签署 +home.certSign.desc=使用证书/密钥(PEM/P12)对PDF进行签署 +certSign.tags=身份验证、PEM、P12、官方、加密 -home.certSign.title=Sign with Certificate -home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) -certSign.tags=authenticate,PEM,P12,official,encrypt +home.pageLayout.title=多页布局 +home.pageLayout.desc=将PDF文档的多个页面合并成一页 +pageLayout.tags=合并、组合、单视图、整理 -home.pageLayout.title=Multi-Page Layout -home.pageLayout.desc=Merge multiple pages of a PDF document into a single page -pageLayout.tags=merge,composite,single-view,organize +home.scalePages.title=调整页面尺寸/缩放 +home.scalePages.desc=调整页面及/或其内容的尺寸/缩放 +scalePages.tags=调整大小、修改、尺寸、适应 -home.scalePages.title=Adjust page size/scale -home.scalePages.desc=Change the size/scale of page and/or its contents. -scalePages.tags=resize,modify,dimension,adapt +home.pipeline.title=管道(高级版) +home.pipeline.desc=通过定义管道脚本在PDF上运行多个操作 +pipeline.tags=自动化、顺序、脚本化、批处理 -home.pipeline.title=Pipeline (Advanced) -home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts -pipeline.tags=automate,sequence,scripted,batch-process +home.add-page-numbers.title=添加页码 +home.add-page-numbers.desc=在文档的指定位置添加页码 +add-page-numbers.tags=分页、标签、整理、索引 -home.add-page-numbers.title=Add Page Numbers -home.add-page-numbers.desc=Add Page numbers throughout a document in a set location -add-page-numbers.tags=paginate,label,organize,index +home.auto-rename.title=自动重命名PDF文件 +home.auto-rename.desc=根据检测到的标题自动对PDF文件进行重命名 +auto-rename.tags=自动检测、基于标题、整理、重新标记 -home.auto-rename.title=Auto Rename PDF File -home.auto-rename.desc=Auto renames a PDF file based on its detected header -auto-rename.tags=auto-detect,header-based,organize,relabel +home.adjust-contrast.title=调整颜色/对比度 +home.adjust-contrast.desc=调整PDF的对比度、饱和度和亮度 +adjust-contrast.tags=颜色校正、调节、修改、增强 -home.adjust-contrast.title=Adjust Colors/Contrast -home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF -adjust-contrast.tags=color-correction,tune,modify,enhance +home.crop.title=裁剪PDF +home.crop.desc=裁剪PDF以减小其文件大小(保留文本!) +crop.tags=修剪、缩小、编辑、形状 -home.crop.title=Crop PDF -home.crop.desc=Crop a PDF to reduce its size (maintains text!) -crop.tags=trim,shrink,edit,shape +home.autoSplitPDF.title=自动拆分页面 +home.autoSplitPDF.desc=使用物理扫描页面分割器QR代码自动拆分扫描的PDF +autoSplitPDF.tags=基于QR码、分离、扫描分割、整理 -home.autoSplitPDF.title=Auto Split Pages -home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code -autoSplitPDF.tags=QR-based,separate,scan-segment,organize +home.sanitizePdf.title=清理 +home.sanitizePdf.desc=从PDF文件中删除脚本和其他元素 +sanitizePdf.tags=清理、安全、安全、删除威胁 -home.sanitizePdf.title=Sanitize -home.sanitizePdf.desc=Remove scripts and other elements from PDF files -sanitizePdf.tags=clean,secure,safe,remove-threats +home.URLToPDF.title=URL/网站转PDF +home.URLToPDF.desc=将任何http(s)URL转换为PDF +URLToPDF.tags=网页捕获、保存网页、网页转文档、归档 -home.URLToPDF.title=URL/Website To PDF -home.URLToPDF.desc=Converts any http(s)URL to PDF -URLToPDF.tags=web-capture,save-page,web-to-doc,archive +home.HTMLToPDF.title=HTML转PDF +home.HTMLToPDF.desc=将任何HTML文件或zip文件转换为PDF +HTMLToPDF.tags=标记、网页内容、转换、转换 -home.HTMLToPDF.title=HTML to PDF -home.HTMLToPDF.desc=Converts any HTML file or zip to PDF -HTMLToPDF.tags=markup,web-content,transformation,convert +home.MarkdownToPDF.title=Markdown转PDF +home.MarkdownToPDF.desc=将任何Markdown文件转换为PDF +MarkdownToPDF.tags=标记、网页内容、转换、转换 +home.getPdfInfo.title=获取PDF的所有信息 +home.getPdfInfo.desc=获取PDF的所有可能的信息 +getPdfInfo.tags=信息、数据、统计、统计数据 -home.MarkdownToPDF.title=Markdown to PDF -home.MarkdownToPDF.desc=Converts any Markdown file to PDF -MarkdownToPDF.tags=markup,web-content,transformation,convert +home.extractPage.title=提取页面 +home.extractPage.desc=从PDF中提取选定的页面 +extractPage.tags=提取 +home.PdfToSinglePage.title=PDF转单一大页 +home.PdfToSinglePage.desc=将所有PDF页面合并为一个大的单页 +PdfToSinglePage.tags=单页 -home.getPdfInfo.title=Get ALL Info on PDF -home.getPdfInfo.desc=Grabs any and all information possible on PDFs -getPdfInfo.tags=infomation,data,stats,statistics +home.showJS.title=显示JavaScript +home.showJS.desc=搜索并显示嵌入到PDF中的任何JavaScript代码 +showJS.tags=JavaScript - -home.extractPage.title=Extract page(s) -home.extractPage.desc=Extracts select pages from PDF -extractPage.tags=extract - - -home.PdfToSinglePage.title=PDF to Single Large Page -home.PdfToSinglePage.desc=Merges all PDF pages into one large single page -PdfToSinglePage.tags=single page - - -########################## -### TODO: Translate ### -########################## -home.showJS.title=Show Javascript -home.showJS.desc=Searches and displays any JS injected into a PDF -showJS.tags=JS +home.autoRedact.title=自动删除 +home.autoRedact.desc=根据输入文本自动删除(覆盖)PDF中的文本 +showJS.tags=JavaScript ########################### # # # WEB PAGES # # # ########################### +#login +login.title=登录 +login.signin=登录 +login.rememberme=记住我 +login.invalid=用户名或密码无效。 +login.locked=您的账户已被锁定。 +login.signinTitle=请登录 + + +#auto-redact +autoRedact.title=自动删除 +autoRedact.header=自动删除 +autoRedact.colorLabel=颜色 +autoRedact.textsToRedactLabel=要删除的文本(每行一个) +autoRedact.textsToRedactPlaceholder=例如:\n保密\n绝密 +autoRedact.useRegexLabel=使用正则表达式 +autoRedact.wholeWordSearchLabel=全字匹配 +autoRedact.customPaddingLabel=自定义额外间距 +autoRedact.convertPDFToImageLabel=将PDF转换为PDF-Image(用于删除方框后面的文本) +autoRedact.submitButton=提交 + + #showJS -########################## -### TODO: Translate ### -########################## -showJS.title=Show Javascript -showJS.header=Show Javascript -showJS.downloadJS=Download Javascript -showJS.submit=Show +showJS.title=显示 JavaScript +showJS.header=显示 JavaScript +showJS.downloadJS=下载 JavaScript +showJS.submit=显示 #pdfToSinglePage -pdfToSinglePage.title=PDF To Single Page -pdfToSinglePage.header=PDF To Single Page -pdfToSinglePage.submit=Convert To Single Page +pdfToSinglePage.title=PDF转为单页 +pdfToSinglePage.header=PDF转为单页 +pdfToSinglePage.submit=转为单页 #pageExtracter -pageExtracter.title=Extract Pages -pageExtracter.header=Extract Pages -pageExtracter.submit=Extract +pageExtracter.title=提取页面 +pageExtracter.header=提取页面 +pageExtracter.submit=提取 #getPdfInfo -getPdfInfo.title=Get Info on PDF -getPdfInfo.header=Get Info on PDF -getPdfInfo.submit=Get Info -getPdfInfo.downloadJson=Download JSON +getPdfInfo.title=获取PDF信息 +getPdfInfo.header=获取PDF信息 +getPdfInfo.submit=获取信息 +getPdfInfo.downloadJson=下载JSON #markdown-to-pdf -MarkdownToPDF.title=Markdown To PDF -MarkdownToPDF.header=Markdown To PDF -MarkdownToPDF.submit=Convert -MarkdownToPDF.help=Work in progress -MarkdownToPDF.credit=Uses WeasyPrint +MarkdownToPDF.title=Markdown转PDF +MarkdownToPDF.header=Markdown转PDF +MarkdownToPDF.submit=转换 +MarkdownToPDF.help=正在努力中 +MarkdownToPDF.credit=使用WeasyPrint #url-to-pdf -URLToPDF.title=URL To PDF -URLToPDF.header=URL To PDF -URLToPDF.submit=Convert -URLToPDF.credit=Uses WeasyPrint +URLToPDF.title=URL转PDF +URLToPDF.header=URL转PDF +URLToPDF.submit=转换 +URLToPDF.credit=使用WeasyPrint #html-to-pdf -HTMLToPDF.title=HTML To PDF -HTMLToPDF.header=HTML To PDF -HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required -HTMLToPDF.submit=Convert -HTMLToPDF.credit=Uses WeasyPrint +HTMLToPDF.title=HTML转PDF +HTMLToPDF.header=HTML转PDF +HTMLToPDF.help=接受HTML文件和包含所需的html/css/images等的ZIP文件 +HTMLToPDF.submit=转换 +HTMLToPDF.credit=使用WeasyPrint #sanitizePDF -sanitizePDF.title=Sanitize PDF -sanitizePDF.header=Sanitize a PDF file -sanitizePDF.selectText.1=Remove JavaScript actions -sanitizePDF.selectText.2=Remove embedded files -sanitizePDF.selectText.3=Remove metadata -sanitizePDF.selectText.4=Remove links -sanitizePDF.selectText.5=Remove fonts -sanitizePDF.submit=Sanitize PDF +sanitizePDF.title=清理PDF +sanitizePDF.header=清理PDF文件 +sanitizePDF.selectText.1=移除JavaScript操作 +sanitizePDF.selectText.2=移除嵌入的文件 +sanitizePDF.selectText.3=移除元数据 +sanitizePDF.selectText.4=移除链接 +sanitizePDF.selectText.5=移除字体 +sanitizePDF.submit=清理PDF #addPageNumbers -addPageNumbers.title=Add Page Numbers -addPageNumbers.header=Add Page Numbers -addPageNumbers.selectText.1=Select PDF file: -addPageNumbers.selectText.2=Margin Size -addPageNumbers.selectText.3=Position -addPageNumbers.selectText.4=Starting Number -addPageNumbers.selectText.5=Pages to Number -addPageNumbers.selectText.6=Custom Text -addPageNumbers.submit=Add Page Numbers +addPageNumbers.title=添加页码 +addPageNumbers.header=添加页码 +addPageNumbers.selectText.1=选择PDF文件: +addPageNumbers.selectText.2=边距大小 +addPageNumbers.selectText.3=位置 +addPageNumbers.selectText.4=起始页码 +addPageNumbers.selectText.5=添加页码的页数 +addPageNumbers.selectText.6=自定义文本 +addPageNumbers.customTextDesc=自定义文本 +addPageNumbers.numberPagesDesc=要添加页码的页数,默认为“所有”,也可以接受1-5或2,5,9等 +addPageNumbers.customNumberDesc=默认为{n},也可以接受“第{n}页/共{total}页”,“文本-{n}”,“{filename}-{n}” +addPageNumbers.submit=添加页码 #auto-rename -auto-rename.title=Auto Rename -auto-rename.header=Auto Rename PDF -auto-rename.submit=Auto Rename +auto-rename.title=自动重命名 +auto-rename.header=自动重命名PDF +auto-rename.submit=自动重命名 #adjustContrast -adjustContrast.title=Adjust Contrast -adjustContrast.header=Adjust Contrast -adjustContrast.contrast=Contrast: -adjustContrast.brightness=Brightness: -adjustContrast.saturation=Saturation: -adjustContrast.download=Download +adjustContrast.title=调整对比度 +adjustContrast.header=调整对比度 +adjustContrast.contrast=对比度: +adjustContrast.brightness=亮度: +adjustContrast.saturation=饱和度: +adjustContrast.download=下载 #crop -crop.title=Crop -crop.header=Crop Image -crop.submit=Submit +crop.title=裁剪 +crop.header=裁剪图像 +crop.submit=提交 #autoSplitPDF -autoSplitPDF.title=Auto Split PDF -autoSplitPDF.header=Auto Split PDF -autoSplitPDF.description=Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed. -autoSplitPDF.selectText.1=Print out some divider sheets from below (Black and white is fine). -autoSplitPDF.selectText.2=Scan all your documents at once by inserting the divider sheet between them. -autoSplitPDF.selectText.3=Upload the single large scanned PDF file and let Stirling PDF handle the rest. -autoSplitPDF.selectText.4=Divider pages are automatically detected and removed, guaranteeing a neat final document. -autoSplitPDF.formPrompt=Submit PDF containing Stirling-PDF Page dividers: -autoSplitPDF.duplexMode=Duplex Mode (Front and back scanning) -autoSplitPDF.dividerDownload1=Download 'Auto Splitter Divider (minimal).pdf' -autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf' -autoSplitPDF.submit=Submit +autoSplitPDF.title=自动拆分PDF +autoSplitPDF.header=自动拆分PDF +autoSplitPDF.description=打印、插入、扫描、上传,让我们自动分离您的文档。无需手动排序。 +autoSplitPDF.selectText.1=从下面打印一些分隔页(黑白打印即可)。 +autoSplitPDF.selectText.2=在文档之间插入分隔页,一次性扫描所有文档。 +autoSplitPDF.selectText.3=上传单个大型扫描的PDF文件,让Stirling PDF处理剩下的事情。 +autoSplitPDF.selectText.4=分隔页会自动检测和删除,确保最终文档整洁。 +autoSplitPDF.formPrompt=提交包含Stirling-PDF分隔页的PDF: +autoSplitPDF.duplexMode=双面模式(正反面扫描) +autoSplitPDF.dividerDownload1=下载“自动拆分分隔页(最小化).pdf” +autoSplitPDF.dividerDownload2=下载“自动拆分分隔页(带指导说明).pdf” +autoSplitPDF.submit=提交 #pipeline -pipeline.title=Pipeline +pipeline.title=流水线 #pageLayout -pageLayout.title=Multi Page Layout -pageLayout.header=Multi Page Layout -pageLayout.pagesPerSheet=Pages per sheet: -pageLayout.submit=Submit +pageLayout.title=多页布局 +pageLayout.header=多页布局 +pageLayout.pagesPerSheet=每页的页面数: +########################## +### TODO: Translate ### +########################## +pageLayout.addBorder=添加边框 +pageLayout.submit=提交 #scalePages -scalePages.title=Adjust page-scale -scalePages.header=Adjust page-scale -scalePages.pageSize=Size of a page of the document. -scalePages.scaleFactor=Zoom level (crop) of a page. -scalePages.submit=Submit +scalePages.title=调整页面缩放比例 +scalePages.header=调整页面缩放比例 +scalePages.pageSize=文档页面的尺寸。 +scalePages.scaleFactor=页面的缩放级别(裁剪)。 +scalePages.submit=提交 #certSign @@ -416,43 +500,43 @@ certSign.submit=签署 PDF #removeBlanks -removeBlanks.title=\u5220\u9664\u7A7A\u767D -removeBlanks.header=\u5220\u9664\u7A7A\u767D\u9875 -removeBlanks.threshold=\u9608\u503C\uFF1A -removeBlanks.thresholdDesc=\u786E\u5B9A\u767D\u8272\u50CF\u7D20\u5FC5\u987B\u6709\u591A\u767D\u7684\u9608\u503C -removeBlanks.whitePercent=\u767D\u8272\u767E\u5206\u6BD4\uFF08%\uFF09\uFF1A -removeBlanks.whitePercentDesc=\u5FC5\u987B\u4E3A\u767D\u8272\u624D\u80FD\u5220\u9664\u7684\u9875\u9762\u767E\u5206\u6BD4 -removeBlanks.submit=\u5220\u9664\u7A7A\u767D +removeBlanks.title=删除空白 +removeBlanks.header=删除空白页 +removeBlanks.threshold=阈值: +removeBlanks.thresholdDesc=确定白色像素必须有多白的阈值 +removeBlanks.whitePercent=白色百分比(%): +removeBlanks.whitePercentDesc=必须为白色才能删除的页面百分比 +removeBlanks.submit=删除空白 #compare -compare.title=\u6BD4\u8F83 -compare.header=\u6BD4\u8F83 PDF -compare.document.1=\u6587\u6863 1 -compare.document.2=\u6587\u6863 2 -compare.submit=\u6BD4\u8F83 +compare.title=比较 +compare.header=比较 PDF +compare.document.1=文档 1 +compare.document.2=文档 2 +compare.submit=比较 #sign -sign.title=\u7B7E\u540D -sign.header=\u7B7E\u7F72 PDF -sign.upload=\u4E0A\u4F20\u56FE\u7247 -sign.draw=\u7ED8\u5236\u7B7E\u540D -sign.text=\u6587\u672C\u8F93\u5165 -sign.clear=\u6E05\u9664 -sign.add=\u6DFB\u52A0 +sign.title=签名 +sign.header=签署 PDF +sign.upload=上传图片 +sign.draw=绘制签名 +sign.text=文本输入 +sign.clear=清除 +sign.add=添加 #repair -repair.title=\u4FEE\u590D -repair.header=\u4FEE\u590D PDF -repair.submit=\u4FEE\u590D +repair.title=修复 +repair.header=修复 PDF +repair.submit=修复 #flatten -flatten.title=\u5C55\u5E73 -flatten.header=\u5C55\u5E73 PDF -flatten.submit=\u5C55\u5E73 +flatten.title=展平 +flatten.header=展平 PDF +flatten.submit=展平 #ScannerImageSplit @@ -517,7 +601,7 @@ compress.submit=压缩 #Add image addImage.title=添加图像 -addImage.header=添加图片到PDF (Work in progress) +addImage.header=添加图片到PDF(正在进行中) addImage.everyPage=每一页? addImage.upload=添加图片 addImage.submit=添加图片 @@ -526,6 +610,8 @@ addImage.submit=添加图片 #merge merge.title=合并 merge.header=合并多个PDF(2个以上)。 +merge.sortByName=按名称排序 +merge.sortByDate=按日期排序 merge.submit=合并 @@ -559,12 +645,12 @@ split.title=拆分PDF split.header=拆分PDF split.desc.1=选择希望进行分割的页数 split.desc.2=如选择1,3,7-8将把一个10页的文件分割成6个独立的PDF: -split.desc.3=Document #1: Page 1 -split.desc.4=Document #2: Page 2 and 3 -split.desc.5=Document #3: Page 4, 5 and 6 -split.desc.6=Document #4: Page 7 -split.desc.7=Document #5: Page 8 -split.desc.8=Document #6: Page 9 and 10 +split.desc.3=文档 #1:第1页 +split.desc.4=文档 #2:第2页和第3页 +split.desc.5=文档 #3:第4页、第5页和第6页 +split.desc.6=文档 #4:第7页 +split.desc.7=文档 #5:第8页 +split.desc.8=文档 #6:第9页和第10页 split.splitPages=输入要分割的页面: split.submit=拆分 @@ -573,7 +659,10 @@ split.submit=拆分 imageToPDF.title=图片转PDF imageToPDF.header=图像转为PDF imageToPDF.submit=转换 -imageToPDF.selectText.1=拉伸至适合的尺寸 +imageToPDF.selectLabel=图片适应选项 +imageToPDF.fillPage=填充页面 +imageToPDF.fitDocumentToImage=适应图片大小 +imageToPDF.maintainAspectRatio=保持纵横比例 imageToPDF.selectText.2=自动旋转PDF imageToPDF.selectText.3=多文件逻辑(仅在处理多个图像时启用) imageToPDF.selectText.4=合并成一个PDF文件 @@ -609,10 +698,10 @@ addPassword.selectText.9=防止填写表格 addPassword.selectText.10=防止修改 addPassword.selectText.11=防止修改注释 addPassword.selectText.12=防止打印 -addPassword.selectText.13=防止打印不同的格� -addPassword.selectText.14=Owner Password -addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) -addPassword.selectText.16=Restricts the opening of the document itself� +addPassword.selectText.13=防止打印不同的格式 +addPassword.selectText.14=所有者密码 +addPassword.selectText.15=限制打开后对文档的操作(不被所有阅读器支持) +addPassword.selectText.16=限制打开文档本身 addPassword.submit=加密 @@ -623,20 +712,14 @@ watermark.selectText.1=选择要添加水印的PDF: watermark.selectText.2=水印文本: watermark.selectText.3=字体大小: watermark.selectText.4=旋转(0-360): -watermark.selectText.5=widthSpacer(水平方向上每个水印之间的空间): -watermark.selectText.6=heightSpacer(每个水印之间的垂直空间): +watermark.selectText.5=水平间距(每个水印之间的水平距离): +watermark.selectText.6=垂直间距(每个水印之间的垂直距离): watermark.selectText.7=透明度(0% - 100%): +watermark.selectText.8=水印类型: +watermark.selectText.9=水印图片: watermark.submit=添加水印 -#remove-watermark -remove-watermark.title=去除水印 -remove-watermark.header=去除水印 -remove-watermark.selectText.1=选择要去除水印的PDF: -remove-watermark.selectText.2=水印文本: -remove-watermark.submit=移除水印 - - #Change permissions permissions.title=更改权限 permissions.header=改变权限 @@ -682,16 +765,9 @@ changeMetadata.selectText.5=添加自定义元数据条目 changeMetadata.submit=更改 -#xlsToPdf -xlsToPdf.title=Excel转PDF -xlsToPdf.header=Excel转PDF -xlsToPdf.selectText.1=选择要转换的XLS或XLSX Excel表格 -xlsToPdf.convert=转换 - - #pdfToPDFA -pdfToPDFA.title=PDF To PDF/A -pdfToPDFA.header=PDF to PDF/A +pdfToPDFA.title=将PDF转换为PDF/A +pdfToPDFA.header=PDF转换为PDF/A pdfToPDFA.credit=此服务使用OCRmyPDF进行PDF/A转换 pdfToPDFA.submit=转换 @@ -705,7 +781,7 @@ PDFToWord.submit=转换 #PDFToPresentation -PDFToPresentation.title=PDF To Presentation +PDFToPresentation.title=PDF转换为演示文稿 PDFToPresentation.header=将PDF转为演示文稿 PDFToPresentation.selectText.1=输出文件格式 PDFToPresentation.credit=该服务使用LibreOffice进行文件转换。 diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template new file mode 100644 index 00000000..0f229ba4 --- /dev/null +++ b/src/main/resources/settings.yml.template @@ -0,0 +1,23 @@ +# Welcome to settings file +# Remove comment marker # if on start of line to enable the configuration +# If you want to override with environment parameter follow parameter naming SECURITY_INITIALLOGIN_USERNAME + +security: + enableLogin: false # set to 'true' to enable login + csrfDisabled: true + +system: + defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc) + googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow + +#ui: +# appName: exampleAppName # Application's visible name +# homeDescription: I am a description # Short description or tagline shown on homepage. +# appNameNavbar: navbarName # Name displayed on the navigation bar + +endpoints: + toRemove: [] # List endpoints to disable (e.g. ['img-to-pdf', 'remove-pages']) + groupsToRemove: [] # List groups to disable (e.g. ['LibreOffice']) + +metrics: + enabled: true # 'true' to enable Info APIs (`/api/*`) endpoints, 'false' to disable diff --git a/src/main/resources/static/css/bootstrap.min.css b/src/main/resources/static/css/bootstrap.min.css index dd9f38e6..edfbbb03 100644 --- a/src/main/resources/static/css/bootstrap.min.css +++ b/src/main/resources/static/css/bootstrap.min.css @@ -1,6 +1,7 @@ -/*! - * Bootstrap v4.5.2 (https://getbootstrap.com/) - * Copyright 2011-2020 The Bootstrap Authors - * Copyright 2011-2020 Twitter, Inc. +@charset "UTF-8";/*! + * Bootstrap v5.0.2 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;z-index:1;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item,.nav-fill>.nav-link{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item{display:-ms-flexbox;display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{-ms-flex-preferred-size:350px;flex-basis:350px;max-width:350px;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;-ms-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} \ No newline at end of file + */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0))}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-font-sans-serif);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + (.5rem + 2px));padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + (1rem + 2px));padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + (.75rem + 2px))}textarea.form-control-sm{min-height:calc(1.5em + (.5rem + 2px))}textarea.form-control-lg{min-height:calc(1.5em + (1rem + 2px))}.form-control-color{max-width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast:not(.showing):not(.show){opacity:0}.toast.hide{display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1060;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1050;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{color:#0d6efd!important}.text-secondary{color:#6c757d!important}.text-success{color:#198754!important}.text-info{color:#0dcaf0!important}.text-warning{color:#ffc107!important}.text-danger{color:#dc3545!important}.text-light{color:#f8f9fa!important}.text-dark{color:#212529!important}.text-white{color:#fff!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-reset{color:inherit!important}.bg-primary{background-color:#0d6efd!important}.bg-secondary{background-color:#6c757d!important}.bg-success{background-color:#198754!important}.bg-info{background-color:#0dcaf0!important}.bg-warning{background-color:#ffc107!important}.bg-danger{background-color:#dc3545!important}.bg-light{background-color:#f8f9fa!important}.bg-dark{background-color:#212529!important}.bg-body{background-color:#fff!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/main/resources/static/css/dark-mode.css b/src/main/resources/static/css/dark-mode.css index 0e78e4ae..cf2e028d 100644 --- a/src/main/resources/static/css/dark-mode.css +++ b/src/main/resources/static/css/dark-mode.css @@ -1,5 +1,5 @@ /* Dark Mode Styles */ -body { +body, select, textarea { --body-background-color: 51, 51, 51; --base-font-color: 255, 255, 255; background-color: rgb(var(--body-background-color)) !important; @@ -41,4 +41,53 @@ body { .favorite-icon img { filter: brightness(0) invert(1) !important; -} \ No newline at end of file +} +table thead { + background-color: #333 !important; + border: 1px solid #444; +} +table th, table td { + border: 1px solid #444 !important; + color: white; +} +.btn { + background-color: #444 !important; + border: none; + color: #fff !important; +} +.btn-primary { + background-color: #007bff !important; + border: none; + color: #fff !important; +} +.btn-secondary { + background-color: #6c757d !important; + border: none; + color: #fff !important; +} +.btn-info { + background-color: #17a2b8 !important; + border: none; + color: #fff !important; +} +.btn-danger { + background-color: #dc3545 !important; + border: none; + color: #fff !important; +} +.btn-outline-secondary { + color: #fff !important; + border-color: #fff; +} +.btn-outline-secondary:hover { + background-color: #444 !important; + color: #007bff !important; + border-color: #007bff; +} +.blackwhite-icon { + filter: brightness(0) invert(1); +} +hr { + border-color: rgba(255, 255, 255, 0.6); /* semi-transparent white */ + background-color: rgba(255, 255, 255, 0.6); /* for some browsers that might use background instead of border for
*/ +} diff --git a/src/main/resources/static/css/home.css b/src/main/resources/static/css/home.css index 998278e1..9bd1ce70 100644 --- a/src/main/resources/static/css/home.css +++ b/src/main/resources/static/css/home.css @@ -14,7 +14,7 @@ .features-container { display: grid; - grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr)); + grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr)); gap: 25px 30px; } @@ -27,6 +27,8 @@ align-items: flex-start; background: rgba(13, 110, 253, 0.05); transition: transform 0.3s, border 0.3s; + transform-origin: center center; + outline: 2px solid transparent; } .feature-card a { @@ -43,7 +45,7 @@ } .feature-card:hover { - border: 1px solid rgba(0, 0, 0, .5); + outline: 1px solid rgba(0, 0, 0, .5); cursor: pointer; transform: scale(1.1); } diff --git a/src/main/resources/static/images/clipboard.svg b/src/main/resources/static/images/clipboard.svg new file mode 100644 index 00000000..360e0894 --- /dev/null +++ b/src/main/resources/static/images/clipboard.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/static/images/eraser-fill.svg b/src/main/resources/static/images/eraser-fill.svg new file mode 100644 index 00000000..10959b3d --- /dev/null +++ b/src/main/resources/static/images/eraser-fill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/static/images/eye-slash.svg b/src/main/resources/static/images/eye-slash.svg new file mode 100644 index 00000000..c5208375 --- /dev/null +++ b/src/main/resources/static/images/eye-slash.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/static/images/eye.svg b/src/main/resources/static/images/eye.svg new file mode 100644 index 00000000..412ff692 --- /dev/null +++ b/src/main/resources/static/images/eye.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/static/images/flags/bg.svg b/src/main/resources/static/images/flags/bg.svg new file mode 100644 index 00000000..b100dd0d --- /dev/null +++ b/src/main/resources/static/images/flags/bg.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/static/images/flags/gr.svg b/src/main/resources/static/images/flags/gr.svg new file mode 100644 index 00000000..599741ee --- /dev/null +++ b/src/main/resources/static/images/flags/gr.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/static/images/flags/nl.svg b/src/main/resources/static/images/flags/nl.svg new file mode 100644 index 00000000..4e809744 --- /dev/null +++ b/src/main/resources/static/images/flags/nl.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/static/js/darkmode.js b/src/main/resources/static/js/darkmode.js index c91bfa68..ebf9c683 100644 --- a/src/main/resources/static/js/darkmode.js +++ b/src/main/resources/static/js/darkmode.js @@ -1,6 +1,55 @@ var toggleCount = 0; var lastToggleTime = Date.now(); +var elements = { + lightModeStyles: null, + darkModeStyles: null, + rainbowModeStyles: null, + darkModeIcon: null +}; + +function getElements() { + elements.lightModeStyles = document.getElementById("light-mode-styles"); + elements.darkModeStyles = document.getElementById("dark-mode-styles"); + elements.rainbowModeStyles = document.getElementById("rainbow-mode-styles"); + elements.darkModeIcon = document.getElementById("dark-mode-icon"); +} + +function setMode(mode) { + var event = new CustomEvent('modeChanged', { detail: mode }); + document.dispatchEvent(event); + elements.lightModeStyles.disabled = mode !== "off"; + elements.darkModeStyles.disabled = mode !== "on"; + elements.rainbowModeStyles.disabled = mode !== "rainbow"; + var jumbotron = document.getElementById('jumbotron'); + if (mode === "on") { + elements.darkModeIcon.src = "moon.svg"; + // Add the table-dark class to tables for dark mode + var tables = document.querySelectorAll('.table'); + tables.forEach(table => { + table.classList.add('table-dark'); + }); + if(jumbotron) { + jumbotron.classList.add('bg-dark'); + jumbotron.classList.remove('bg-light'); + } + } else if (mode === "off") { + elements.darkModeIcon.src = "sun.svg"; + // Remove the table-dark class for light mode + var tables = document.querySelectorAll('.table-dark'); + tables.forEach(table => { + table.classList.remove('table-dark'); + }); + if(jumbotron){ + console.log(mode) + jumbotron.classList.remove('bg-dark'); + jumbotron.classList.add('bg-light'); + } + } else if (mode === "rainbow") { + elements.darkModeIcon.src = "rainbow.svg"; + } +} + function toggleDarkMode() { var currentTime = Date.now(); if (currentTime - lastToggleTime < 1000) { @@ -10,67 +59,32 @@ function toggleDarkMode() { } lastToggleTime = currentTime; - var lightModeStyles = document.getElementById("light-mode-styles"); - var darkModeStyles = document.getElementById("dark-mode-styles"); - var rainbowModeStyles = document.getElementById("rainbow-mode-styles"); - var darkModeIcon = document.getElementById("dark-mode-icon"); - if (toggleCount >= 18) { localStorage.setItem("dark-mode", "rainbow"); - lightModeStyles.disabled = true; - darkModeStyles.disabled = true; - rainbowModeStyles.disabled = false; - darkModeIcon.src = "rainbow.svg"; + setMode("rainbow"); } else if (localStorage.getItem("dark-mode") == "on") { localStorage.setItem("dark-mode", "off"); - lightModeStyles.disabled = false; - darkModeStyles.disabled = true; - rainbowModeStyles.disabled = true; - darkModeIcon.src = "sun.svg"; + setMode("off"); } else { localStorage.setItem("dark-mode", "on"); - lightModeStyles.disabled = true; - darkModeStyles.disabled = false; - rainbowModeStyles.disabled = true; - darkModeIcon.src = "moon.svg"; + setMode("on"); } } document.addEventListener("DOMContentLoaded", function() { - var lightModeStyles = document.getElementById("light-mode-styles"); - var darkModeStyles = document.getElementById("dark-mode-styles"); - var rainbowModeStyles = document.getElementById("rainbow-mode-styles"); - var darkModeIcon = document.getElementById("dark-mode-icon"); + getElements(); - if (localStorage.getItem("dark-mode") == "on") { - lightModeStyles.disabled = true; - darkModeStyles.disabled = false; - rainbowModeStyles.disabled = true; - darkModeIcon.src = "moon.svg"; - } else if (localStorage.getItem("dark-mode") == "off") { - lightModeStyles.disabled = false; - darkModeStyles.disabled = true; - rainbowModeStyles.disabled = true; - darkModeIcon.src = "sun.svg"; - } else if (localStorage.getItem("dark-mode") == "rainbow") { - lightModeStyles.disabled = true; - darkModeStyles.disabled = true; - rainbowModeStyles.disabled = false; - darkModeIcon.src = "rainbow.svg"; + var currentMode = localStorage.getItem("dark-mode"); + if (currentMode === "on" || currentMode === "off" || currentMode === "rainbow") { + setMode(currentMode); + } else if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { + setMode("on"); } else { - if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { - darkModeStyles.disabled = false; - rainbowModeStyles.disabled = true; - darkModeIcon.src = "moon.svg"; - } else { - darkModeStyles.disabled = true; - rainbowModeStyles.disabled = true; - darkModeIcon.src = "sun.svg"; - } + setMode("off"); } - + document.getElementById("dark-mode-toggle").addEventListener("click", function(event) { event.preventDefault(); toggleDarkMode(); }); -}); \ No newline at end of file +}); diff --git a/src/main/resources/static/js/draggable-utils.js b/src/main/resources/static/js/draggable-utils.js index f90a80f1..4dadf920 100644 --- a/src/main/resources/static/js/draggable-utils.js +++ b/src/main/resources/static/js/draggable-utils.js @@ -13,12 +13,12 @@ const DraggableUtils = { listeners: { move: (event) => { const target = event.target; - const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx; - const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy; + const x = (parseFloat(target.getAttribute('data-bs-x')) || 0) + event.dx; + const y = (parseFloat(target.getAttribute('data-bs-y')) || 0) + event.dy; target.style.transform = `translate(${x}px, ${y}px)`; - target.setAttribute('data-x', x); - target.setAttribute('data-y', y); + target.setAttribute('data-bs-x', x); + target.setAttribute('data-bs-y', y); this.onInteraction(target); }, @@ -29,8 +29,8 @@ const DraggableUtils = { listeners: { move: (event) => { var target = event.target - var x = (parseFloat(target.getAttribute('data-x')) || 0) - var y = (parseFloat(target.getAttribute('data-y')) || 0) + var x = (parseFloat(target.getAttribute('data-bs-x')) || 0) + var y = (parseFloat(target.getAttribute('data-bs-y')) || 0) // check if control key is pressed if (event.ctrlKey) { @@ -58,8 +58,8 @@ const DraggableUtils = { target.style.transform = 'translate(' + x + 'px,' + y + 'px)' - target.setAttribute('data-x', x) - target.setAttribute('data-y', y) + target.setAttribute('data-bs-x', x) + target.setAttribute('data-bs-y', y) target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height) this.onInteraction(target); @@ -86,8 +86,8 @@ const DraggableUtils = { const x = 0; const y = 20; createdCanvas.style.transform = `translate(${x}px, ${y}px)`; - createdCanvas.setAttribute('data-x', x); - createdCanvas.setAttribute('data-y', y); + createdCanvas.setAttribute('data-bs-x', x); + createdCanvas.setAttribute('data-bs-y', y); createdCanvas.onclick = e => this.onInteraction(e.target); diff --git a/src/main/resources/static/js/favourites.js b/src/main/resources/static/js/favourites.js index 7372ad30..11cdbf04 100644 --- a/src/main/resources/static/js/favourites.js +++ b/src/main/resources/static/js/favourites.js @@ -1,37 +1,45 @@ function updateFavoritesDropdown() { - var dropdown = document.querySelector('#favoritesDropdown'); - dropdown.innerHTML = ''; // Clear the current favorites + var dropdown = document.querySelector('#favoritesDropdown'); + + // Check if dropdown exists + if (!dropdown) { + console.error('Dropdown element with ID "favoritesDropdown" not found!'); + return; // Exit the function + } + dropdown.innerHTML = ''; // Clear the current favorites + var hasFavorites = false; + for (var i = 0; i < localStorage.length; i++) { + var key = localStorage.key(i); + if (localStorage.getItem(key) === 'favorite') { + // Find the corresponding navbar entry + var navbarEntry = document.querySelector(`a[href='${key}']`); + if (navbarEntry) { + // Create a new dropdown entry + var dropdownItem = document.createElement('a'); + dropdownItem.className = 'dropdown-item'; + dropdownItem.href = navbarEntry.href; + dropdownItem.innerHTML = navbarEntry.innerHTML; + dropdown.appendChild(dropdownItem); + hasFavorites = true; + } else { + console.warn(`Navbar entry not found for key: ${key}`); + } + } + } - var hasFavorites = false; - - for (var i = 0; i < localStorage.length; i++) { - var key = localStorage.key(i); - if (localStorage.getItem(key) === 'favorite') { - // Find the corresponding navbar entry - var navbarEntry = document.querySelector(`a[href='${key}']`); - if (navbarEntry) { - // Create a new dropdown entry - var dropdownItem = document.createElement('a'); - dropdownItem.className = 'dropdown-item'; - dropdownItem.href = navbarEntry.href; - dropdownItem.innerHTML = navbarEntry.innerHTML; - dropdown.appendChild(dropdownItem); - hasFavorites = true; - } - } - } - - // Show or hide the default item based on whether there are any favorites - if (!hasFavorites) { - var defaultItem = document.createElement('a'); - defaultItem.className = 'dropdown-item'; - defaultItem.textContent = noFavourites; - dropdown.appendChild(defaultItem); - } + // Show or hide the default item based on whether there are any favorites + if (!hasFavorites) { + var defaultItem = document.createElement('a'); + defaultItem.className = 'dropdown-item'; + defaultItem.textContent = noFavourites; + dropdown.appendChild(defaultItem); + } } -document.addEventListener('DOMContentLoaded', function() { - updateFavoritesDropdown(); -}); \ No newline at end of file +// Ensure that the DOM content has been fully loaded before calling the function +document.addEventListener('DOMContentLoaded', function() { + console.log('DOMContentLoaded event fired'); + updateFavoritesDropdown(); +}); diff --git a/src/main/resources/static/js/fileInput.js b/src/main/resources/static/js/fileInput.js index 7825af8b..12d50b3f 100644 --- a/src/main/resources/static/js/fileInput.js +++ b/src/main/resources/static/js/fileInput.js @@ -3,9 +3,9 @@ document.addEventListener('DOMContentLoaded', function() { }); function setupFileInput(chooser) { - const elementId = chooser.getAttribute('data-element-id'); - const filesSelected = chooser.getAttribute('data-files-selected'); - const pdfPrompt = chooser.getAttribute('data-pdf-prompt'); + const elementId = chooser.getAttribute('data-bs-element-id'); + const filesSelected = chooser.getAttribute('data-bs-files-selected'); + const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt'); let allFiles = []; let overlay; diff --git a/src/main/resources/static/js/homecard.js b/src/main/resources/static/js/homecard.js index 72997a02..d0d56185 100644 --- a/src/main/resources/static/js/homecard.js +++ b/src/main/resources/static/js/homecard.js @@ -10,7 +10,7 @@ function filterCards() { // Get the navbar tags associated with the card var navbarItem = document.querySelector(`a.dropdown-item[href="${card.id}"]`); - var navbarTags = navbarItem ? navbarItem.getAttribute('data-tags') : ''; + var navbarTags = navbarItem ? navbarItem.getAttribute('data-bs-tags') : ''; var content = title + ' ' + text + ' ' + navbarTags; diff --git a/src/main/resources/static/js/languageSelection.js b/src/main/resources/static/js/languageSelection.js index e9d141f5..c554c2a7 100644 --- a/src/main/resources/static/js/languageSelection.js +++ b/src/main/resources/static/js/languageSelection.js @@ -1,4 +1,5 @@ document.addEventListener('DOMContentLoaded', function() { + setLanguageForDropdown('.lang_dropdown-item'); const defaultLocale = document.documentElement.lang || 'en_GB'; const storedLocale = localStorage.getItem('languageCode') || defaultLocale; const dropdownItems = document.querySelectorAll('.lang_dropdown-item'); @@ -13,34 +14,56 @@ document.addEventListener('DOMContentLoaded', function() { } }); -function handleDropdownItemClick(event) { - event.preventDefault(); - const languageCode = this.dataset.languageCode; - localStorage.setItem('languageCode', languageCode); +function setLanguageForDropdown(dropdownClass) { + const defaultLocale = document.documentElement.lang || 'en_GB'; + const storedLocale = localStorage.getItem('languageCode') || defaultLocale; + const dropdownItems = document.querySelectorAll(dropdownClass); - const currentUrl = window.location.href; - if (currentUrl.indexOf('?lang=') === -1) { - window.location.href = currentUrl + '?lang=' + languageCode; - } else { - window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode); - } + for (let i = 0; i < dropdownItems.length; i++) { + const item = dropdownItems[i]; + item.classList.remove('active'); + if (item.dataset.languageCode === storedLocale) { + item.classList.add('active'); + } + item.addEventListener('click', handleDropdownItemClick); + } } -$(document).ready(function() { - $(".nav-item.dropdown").each(function() { - var $dropdownMenu = $(this).find(".dropdown-menu"); - if ($dropdownMenu.children().length <= 2 && $dropdownMenu.children("hr.dropdown-divider").length === $dropdownMenu.children().length) { - $(this).prev('.nav-item.nav-item-separator').remove(); - $(this).remove(); - } - }); +function handleDropdownItemClick(event) { + event.preventDefault(); + const languageCode = event.currentTarget.dataset.bsLanguageCode; // change this to event.currentTarget + if (languageCode) { + localStorage.setItem('languageCode', languageCode); + const currentUrl = window.location.href; + if (currentUrl.indexOf('?lang=') === -1) { + window.location.href = currentUrl + '?lang=' + languageCode; + } else { + window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode); + } + } else { + console.error("Language code is not set for this item."); // for debugging + } +} + + +document.addEventListener('DOMContentLoaded', function() { + document.querySelectorAll('.nav-item.dropdown').forEach((element) => { + const dropdownMenu = element.querySelector(".dropdown-menu"); + if (dropdownMenu.id !== 'favoritesDropdown' && dropdownMenu.children.length <= 2 && dropdownMenu.querySelectorAll("hr.dropdown-divider").length === dropdownMenu.children.length) { + if (element.previousElementSibling && element.previousElementSibling.classList.contains('nav-item') && element.previousElementSibling.classList.contains('nav-item-separator')) { + element.previousElementSibling.remove(); + } + element.remove(); + } + }); + //Sort languages by alphabet - var list = $('.dropdown-menu[aria-labelledby="languageDropdown"]').children("a"); + const list = Array.from(document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').children).filter(child => child.matches('a')); list.sort(function(a, b) { - var A = $(a).text().toUpperCase(); - var B = $(b).text().toUpperCase(); - return (A < B) ? -1 : (A > B) ? 1 : 0; - }) - .appendTo('.dropdown-menu[aria-labelledby="languageDropdown"]'); + var A = a.textContent.toUpperCase(); + var B = b.textContent.toUpperCase(); + return (A < B) ? -1 : (A > B) ? 1 : 0; + }).forEach(node => document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').appendChild(node)); + }); \ No newline at end of file diff --git a/src/main/resources/static/js/merge.js b/src/main/resources/static/js/merge.js index 523be4a8..d27730b9 100644 --- a/src/main/resources/static/js/merge.js +++ b/src/main/resources/static/js/merge.js @@ -1,63 +1,113 @@ +let currentSort = { + field: null, + descending: false +}; + document.getElementById("fileInput-input").addEventListener("change", function() { - var files = this.files; - var list = document.getElementById("selectedFiles"); - list.innerHTML = ""; - for (var i = 0; i < files.length; i++) { - var item = document.createElement("li"); - item.className = "list-group-item"; - item.innerHTML = ` -
-
${files[i].name}
-
- - -
-
- `; - list.appendChild(item); - } + var files = this.files; + displayFiles(files); +}); - var moveUpButtons = document.querySelectorAll(".move-up"); - for (var i = 0; i < moveUpButtons.length; i++) { - moveUpButtons[i].addEventListener("click", function(event) { - event.preventDefault(); - var parent = this.closest(".list-group-item"); - var grandParent = parent.parentNode; - if (parent.previousElementSibling) { - grandParent.insertBefore(parent, parent.previousElementSibling); - updateFiles(); - } - }); - } +function displayFiles(files) { + var list = document.getElementById("selectedFiles"); + list.innerHTML = ""; - var moveDownButtons = document.querySelectorAll(".move-down"); - for (var i = 0; i < moveDownButtons.length; i++) { - moveDownButtons[i].addEventListener("click", function(event) { - event.preventDefault(); - var parent = this.closest(".list-group-item"); - var grandParent = parent.parentNode; - if (parent.nextElementSibling) { - grandParent.insertBefore(parent.nextElementSibling, parent); - updateFiles(); - } - }); - } + for (var i = 0; i < files.length; i++) { + var item = document.createElement("li"); + item.className = "list-group-item"; + item.innerHTML = ` +
+
${files[i].name}
+
+ + +
+
+ `; + list.appendChild(item); + } - function updateFiles() { - var dataTransfer = new DataTransfer(); - var liElements = document.querySelectorAll("#selectedFiles li"); + attachMoveButtons(); +} - for (var i = 0; i < liElements.length; i++) { - var fileNameFromList = liElements[i].querySelector(".filename").innerText; - var fileFromFiles; - for (var j = 0; j < files.length; j++) { - var file = files[j]; - if (file.name === fileNameFromList) { - dataTransfer.items.add(file); - break; - } - } - } - document.getElementById("fileInput-input").files = dataTransfer.files; - } -}); \ No newline at end of file +function attachMoveButtons() { + var moveUpButtons = document.querySelectorAll(".move-up"); + for (var i = 0; i < moveUpButtons.length; i++) { + moveUpButtons[i].addEventListener("click", function(event) { + event.preventDefault(); + var parent = this.closest(".list-group-item"); + var grandParent = parent.parentNode; + if (parent.previousElementSibling) { + grandParent.insertBefore(parent, parent.previousElementSibling); + updateFiles(); + } + }); + } + + var moveDownButtons = document.querySelectorAll(".move-down"); + for (var i = 0; i < moveDownButtons.length; i++) { + moveDownButtons[i].addEventListener("click", function(event) { + event.preventDefault(); + var parent = this.closest(".list-group-item"); + var grandParent = parent.parentNode; + if (parent.nextElementSibling) { + grandParent.insertBefore(parent.nextElementSibling, parent); + updateFiles(); + } + }); + } +} + +document.getElementById("sortByNameBtn").addEventListener("click", function() { + if (currentSort.field === "name" && !currentSort.descending) { + currentSort.descending = true; + sortFiles((a, b) => b.name.localeCompare(a.name)); + } else { + currentSort.field = "name"; + currentSort.descending = false; + sortFiles((a, b) => a.name.localeCompare(b.name)); + } +}); + +document.getElementById("sortByDateBtn").addEventListener("click", function() { + if (currentSort.field === "lastModified" && !currentSort.descending) { + currentSort.descending = true; + sortFiles((a, b) => b.lastModified - a.lastModified); + } else { + currentSort.field = "lastModified"; + currentSort.descending = false; + sortFiles((a, b) => a.lastModified - b.lastModified); + } +}); + +function sortFiles(comparator) { + // Convert FileList to array and sort + const sortedFilesArray = Array.from(document.getElementById("fileInput-input").files).sort(comparator); + + // Refresh displayed list + displayFiles(sortedFilesArray); + + // Update the files property + const dataTransfer = new DataTransfer(); + sortedFilesArray.forEach(file => dataTransfer.items.add(file)); + document.getElementById("fileInput-input").files = dataTransfer.files; +} + +function updateFiles() { + var dataTransfer = new DataTransfer(); + var liElements = document.querySelectorAll("#selectedFiles li"); + const files = document.getElementById("fileInput-input").files; + + for (var i = 0; i < liElements.length; i++) { + var fileNameFromList = liElements[i].querySelector(".filename").innerText; + var fileFromFiles; + for (var j = 0; j < files.length; j++) { + var file = files[j]; + if (file.name === fileNameFromList) { + dataTransfer.items.add(file); + break; + } + } + } + document.getElementById("fileInput-input").files = dataTransfer.files; +} diff --git a/src/main/resources/static/js/pipeline.js b/src/main/resources/static/js/pipeline.js index 8b2c263d..06810743 100644 --- a/src/main/resources/static/js/pipeline.js +++ b/src/main/resources/static/js/pipeline.js @@ -241,7 +241,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function() if (parameter.name === 'fileInput') return; let parameterDiv = document.createElement('div'); - parameterDiv.className = "form-group"; + parameterDiv.className = "mb-3"; let parameterLabel = document.createElement('label'); parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `; diff --git a/src/main/resources/static/js/search.js b/src/main/resources/static/js/search.js index a4007206..5dd4acf5 100644 --- a/src/main/resources/static/js/search.js +++ b/src/main/resources/static/js/search.js @@ -43,7 +43,7 @@ document.querySelector('#navbarSearchInput').addEventListener('input', function( var titleElement = item.querySelector('.icon-text'); var iconElement = item.querySelector('.icon'); var itemHref = item.getAttribute('href'); - var tags = item.getAttribute('data-tags') || ""; // If no tags, default to empty string + var tags = item.getAttribute('data-bs-tags') || ""; // If no tags, default to empty string if (titleElement && iconElement && itemHref !== '#') { var title = titleElement.innerText; diff --git a/src/main/resources/static/js/thirdParty/bootstrap.min.js b/src/main/resources/static/js/thirdParty/bootstrap.min.js index cbf856fa..aed031fd 100644 --- a/src/main/resources/static/js/thirdParty/bootstrap.min.js +++ b/src/main/resources/static/js/thirdParty/bootstrap.min.js @@ -1,6 +1,7 @@ /*! - * Bootstrap v4.5.2 (https://getbootstrap.com/) - * Copyright 2011-2020 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Bootstrap v5.0.2 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap={},t.jQuery,t.Popper)}(this,(function(t,e,n){"use strict";function i(t,e){for(var n=0;n=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};a.jQueryDetection(),e.fn.emulateTransitionEnd=r,e.event.special[a.TRANSITION_END]={bindType:"transitionend",delegateType:"transitionend",handle:function(t){if(e(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}};var l="alert",c=e.fn[l],h=function(){function t(t){this._element=t}var n=t.prototype;return n.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},n.dispose=function(){e.removeData(this._element,"bs.alert"),this._element=null},n._getRootElement=function(t){var n=a.getSelectorFromElement(t),i=!1;return n&&(i=document.querySelector(n)),i||(i=e(t).closest(".alert")[0]),i},n._triggerCloseEvent=function(t){var n=e.Event("close.bs.alert");return e(t).trigger(n),n},n._removeElement=function(t){var n=this;if(e(t).removeClass("show"),e(t).hasClass("fade")){var i=a.getTransitionDurationFromElement(t);e(t).one(a.TRANSITION_END,(function(e){return n._destroyElement(t,e)})).emulateTransitionEnd(i)}else this._destroyElement(t)},n._destroyElement=function(t){e(t).detach().trigger("closed.bs.alert").remove()},t._jQueryInterface=function(n){return this.each((function(){var i=e(this),o=i.data("bs.alert");o||(o=new t(this),i.data("bs.alert",o)),"close"===n&&o[n](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}}]),t}();e(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',h._handleDismiss(new h)),e.fn[l]=h._jQueryInterface,e.fn[l].Constructor=h,e.fn[l].noConflict=function(){return e.fn[l]=c,h._jQueryInterface};var u=e.fn.button,d=function(){function t(t){this._element=t}var n=t.prototype;return n.toggle=function(){var t=!0,n=!0,i=e(this._element).closest('[data-toggle="buttons"]')[0];if(i){var o=this._element.querySelector('input:not([type="hidden"])');if(o){if("radio"===o.type)if(o.checked&&this._element.classList.contains("active"))t=!1;else{var s=i.querySelector(".active");s&&e(s).removeClass("active")}t&&("checkbox"!==o.type&&"radio"!==o.type||(o.checked=!this._element.classList.contains("active")),e(o).trigger("change")),o.focus(),n=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(n&&this._element.setAttribute("aria-pressed",!this._element.classList.contains("active")),t&&e(this._element).toggleClass("active"))},n.dispose=function(){e.removeData(this._element,"bs.button"),this._element=null},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.button");i||(i=new t(this),e(this).data("bs.button",i)),"toggle"===n&&i[n]()}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}}]),t}();e(document).on("click.bs.button.data-api",'[data-toggle^="button"]',(function(t){var n=t.target,i=n;if(e(n).hasClass("btn")||(n=e(n).closest(".btn")[0]),!n||n.hasAttribute("disabled")||n.classList.contains("disabled"))t.preventDefault();else{var o=n.querySelector('input:not([type="hidden"])');if(o&&(o.hasAttribute("disabled")||o.classList.contains("disabled")))return void t.preventDefault();("LABEL"!==i.tagName||o&&"checkbox"!==o.type)&&d._jQueryInterface.call(e(n),"toggle")}})).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',(function(t){var n=e(t.target).closest(".btn")[0];e(n).toggleClass("focus",/^focus(in)?$/.test(t.type))})),e(window).on("load.bs.button.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),e=0,n=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var n=t.prototype;return n.next=function(){this._isSliding||this._slide("next")},n.nextWhenVisible=function(){!document.hidden&&e(this._element).is(":visible")&&"hidden"!==e(this._element).css("visibility")&&this.next()},n.prev=function(){this._isSliding||this._slide("prev")},n.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(a.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},n.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},n.to=function(t){var n=this;this._activeElement=this._element.querySelector(".active.carousel-item");var i=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)e(this._element).one("slid.bs.carousel",(function(){return n.to(t)}));else{if(i===t)return this.pause(),void this.cycle();var o=t>i?"next":"prev";this._slide(o,this._items[t])}},n.dispose=function(){e(this._element).off(g),e.removeData(this._element,"bs.carousel"),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},n._getConfig=function(t){return t=s({},p,t),a.typeCheckConfig(f,t,_),t},n._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},n._addEventListeners=function(){var t=this;this._config.keyboard&&e(this._element).on("keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&e(this._element).on("mouseenter.bs.carousel",(function(e){return t.pause(e)})).on("mouseleave.bs.carousel",(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},n._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var n=function(e){t._pointerEvent&&v[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},i=function(e){t._pointerEvent&&v[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};e(this._element.querySelectorAll(".carousel-item img")).on("dragstart.bs.carousel",(function(t){return t.preventDefault()})),this._pointerEvent?(e(this._element).on("pointerdown.bs.carousel",(function(t){return n(t)})),e(this._element).on("pointerup.bs.carousel",(function(t){return i(t)})),this._element.classList.add("pointer-event")):(e(this._element).on("touchstart.bs.carousel",(function(t){return n(t)})),e(this._element).on("touchmove.bs.carousel",(function(e){return function(e){e.originalEvent.touches&&e.originalEvent.touches.length>1?t.touchDeltaX=0:t.touchDeltaX=e.originalEvent.touches[0].clientX-t.touchStartX}(e)})),e(this._element).on("touchend.bs.carousel",(function(t){return i(t)})))}},n._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},n._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(t)},n._getItemByDirection=function(t,e){var n="next"===t,i="prev"===t,o=this._getItemIndex(e),s=this._items.length-1;if((i&&0===o||n&&o===s)&&!this._config.wrap)return e;var r=(o+("prev"===t?-1:1))%this._items.length;return-1===r?this._items[this._items.length-1]:this._items[r]},n._triggerSlideEvent=function(t,n){var i=this._getItemIndex(t),o=this._getItemIndex(this._element.querySelector(".active.carousel-item")),s=e.Event("slide.bs.carousel",{relatedTarget:t,direction:n,from:o,to:i});return e(this._element).trigger(s),s},n._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var n=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));e(n).removeClass("active");var i=this._indicatorsElement.children[this._getItemIndex(t)];i&&e(i).addClass("active")}},n._slide=function(t,n){var i,o,s,r=this,l=this._element.querySelector(".active.carousel-item"),c=this._getItemIndex(l),h=n||l&&this._getItemByDirection(t,l),u=this._getItemIndex(h),d=Boolean(this._interval);if("next"===t?(i="carousel-item-left",o="carousel-item-next",s="left"):(i="carousel-item-right",o="carousel-item-prev",s="right"),h&&e(h).hasClass("active"))this._isSliding=!1;else if(!this._triggerSlideEvent(h,s).isDefaultPrevented()&&l&&h){this._isSliding=!0,d&&this.pause(),this._setActiveIndicatorElement(h);var f=e.Event("slid.bs.carousel",{relatedTarget:h,direction:s,from:c,to:u});if(e(this._element).hasClass("slide")){e(h).addClass(o),a.reflow(h),e(l).addClass(i),e(h).addClass(i);var g=parseInt(h.getAttribute("data-interval"),10);g?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=g):this._config.interval=this._config.defaultInterval||this._config.interval;var m=a.getTransitionDurationFromElement(l);e(l).one(a.TRANSITION_END,(function(){e(h).removeClass(i+" "+o).addClass("active"),e(l).removeClass("active "+o+" "+i),r._isSliding=!1,setTimeout((function(){return e(r._element).trigger(f)}),0)})).emulateTransitionEnd(m)}else e(l).removeClass("active"),e(h).addClass("active"),this._isSliding=!1,e(this._element).trigger(f);d&&this.cycle()}},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.carousel"),o=s({},p,e(this).data());"object"==typeof n&&(o=s({},o,n));var r="string"==typeof n?n:o.slide;if(i||(i=new t(this,o),e(this).data("bs.carousel",i)),"number"==typeof n)i.to(n);else if("string"==typeof r){if("undefined"==typeof i[r])throw new TypeError('No method named "'+r+'"');i[r]()}else o.interval&&o.ride&&(i.pause(),i.cycle())}))},t._dataApiClickHandler=function(n){var i=a.getSelectorFromElement(this);if(i){var o=e(i)[0];if(o&&e(o).hasClass("carousel")){var r=s({},e(o).data(),e(this).data()),l=this.getAttribute("data-slide-to");l&&(r.interval=!1),t._jQueryInterface.call(e(o),r),l&&e(o).data("bs.carousel").to(l),n.preventDefault()}}},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}},{key:"Default",get:function(){return p}}]),t}();e(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",b._dataApiClickHandler),e(window).on("load.bs.carousel.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),n=0,i=t.length;n0&&(this._selector=r,this._triggerArray.push(s))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var n=t.prototype;return n.toggle=function(){e(this._element).hasClass("show")?this.hide():this.show()},n.show=function(){var n,i,o=this;if(!this._isTransitioning&&!e(this._element).hasClass("show")&&(this._parent&&0===(n=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(t){return"string"==typeof o._config.parent?t.getAttribute("data-parent")===o._config.parent:t.classList.contains("collapse")}))).length&&(n=null),!(n&&(i=e(n).not(this._selector).data("bs.collapse"))&&i._isTransitioning))){var s=e.Event("show.bs.collapse");if(e(this._element).trigger(s),!s.isDefaultPrevented()){n&&(t._jQueryInterface.call(e(n).not(this._selector),"hide"),i||e(n).data("bs.collapse",null));var r=this._getDimension();e(this._element).removeClass("collapse").addClass("collapsing"),this._element.style[r]=0,this._triggerArray.length&&e(this._triggerArray).removeClass("collapsed").attr("aria-expanded",!0),this.setTransitioning(!0);var l="scroll"+(r[0].toUpperCase()+r.slice(1)),c=a.getTransitionDurationFromElement(this._element);e(this._element).one(a.TRANSITION_END,(function(){e(o._element).removeClass("collapsing").addClass("collapse show"),o._element.style[r]="",o.setTransitioning(!1),e(o._element).trigger("shown.bs.collapse")})).emulateTransitionEnd(c),this._element.style[r]=this._element[l]+"px"}}},n.hide=function(){var t=this;if(!this._isTransitioning&&e(this._element).hasClass("show")){var n=e.Event("hide.bs.collapse");if(e(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",a.reflow(this._element),e(this._element).addClass("collapsing").removeClass("collapse show");var o=this._triggerArray.length;if(o>0)for(var s=0;s0},i._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=s({},e.offsets,t._config.offset(e.offsets,t._element)||{}),e}:e.offset=this._config.offset,e},i._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),s({},t,this._config.popperConfig)},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.dropdown");if(i||(i=new t(this,"object"==typeof n?n:null),e(this).data("bs.dropdown",i)),"string"==typeof n){if("undefined"==typeof i[n])throw new TypeError('No method named "'+n+'"');i[n]()}}))},t._clearMenus=function(n){if(!n||3!==n.which&&("keyup"!==n.type||9===n.which))for(var i=[].slice.call(document.querySelectorAll('[data-toggle="dropdown"]')),o=0,s=i.length;o0&&r--,40===n.which&&rdocument.documentElement.clientHeight;i||(this._element.style.overflowY="hidden"),this._element.classList.add("modal-static");var o=a.getTransitionDurationFromElement(this._dialog);e(this._element).off(a.TRANSITION_END),e(this._element).one(a.TRANSITION_END,(function(){t._element.classList.remove("modal-static"),i||e(t._element).one(a.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,o)})).emulateTransitionEnd(o),this._element.focus()}else this.hide()},n._showElement=function(t){var n=this,i=e(this._element).hasClass("fade"),o=this._dialog?this._dialog.querySelector(".modal-body"):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),e(this._dialog).hasClass("modal-dialog-scrollable")&&o?o.scrollTop=0:this._element.scrollTop=0,i&&a.reflow(this._element),e(this._element).addClass("show"),this._config.focus&&this._enforceFocus();var s=e.Event("shown.bs.modal",{relatedTarget:t}),r=function(){n._config.focus&&n._element.focus(),n._isTransitioning=!1,e(n._element).trigger(s)};if(i){var l=a.getTransitionDurationFromElement(this._dialog);e(this._dialog).one(a.TRANSITION_END,r).emulateTransitionEnd(l)}else r()},n._enforceFocus=function(){var t=this;e(document).off("focusin.bs.modal").on("focusin.bs.modal",(function(n){document!==n.target&&t._element!==n.target&&0===e(t._element).has(n.target).length&&t._element.focus()}))},n._setEscapeEvent=function(){var t=this;this._isShown?e(this._element).on("keydown.dismiss.bs.modal",(function(e){t._config.keyboard&&27===e.which?(e.preventDefault(),t.hide()):t._config.keyboard||27!==e.which||t._triggerBackdropTransition()})):this._isShown||e(this._element).off("keydown.dismiss.bs.modal")},n._setResizeEvent=function(){var t=this;this._isShown?e(window).on("resize.bs.modal",(function(e){return t.handleUpdate(e)})):e(window).off("resize.bs.modal")},n._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){e(document.body).removeClass("modal-open"),t._resetAdjustments(),t._resetScrollbar(),e(t._element).trigger("hidden.bs.modal")}))},n._removeBackdrop=function(){this._backdrop&&(e(this._backdrop).remove(),this._backdrop=null)},n._showBackdrop=function(t){var n=this,i=e(this._element).hasClass("fade")?"fade":"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",i&&this._backdrop.classList.add(i),e(this._backdrop).appendTo(document.body),e(this._element).on("click.dismiss.bs.modal",(function(t){n._ignoreBackdropClick?n._ignoreBackdropClick=!1:t.target===t.currentTarget&&n._triggerBackdropTransition()})),i&&a.reflow(this._backdrop),e(this._backdrop).addClass("show"),!t)return;if(!i)return void t();var o=a.getTransitionDurationFromElement(this._backdrop);e(this._backdrop).one(a.TRANSITION_END,t).emulateTransitionEnd(o)}else if(!this._isShown&&this._backdrop){e(this._backdrop).removeClass("show");var s=function(){n._removeBackdrop(),t&&t()};if(e(this._element).hasClass("fade")){var r=a.getTransitionDurationFromElement(this._backdrop);e(this._backdrop).one(a.TRANSITION_END,s).emulateTransitionEnd(r)}else s()}else t&&t()},n._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},n._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},n._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:L,popperConfig:null},K={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},X=function(){function t(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var i=t.prototype;return i.enable=function(){this._isEnabled=!0},i.disable=function(){this._isEnabled=!1},i.toggleEnabled=function(){this._isEnabled=!this._isEnabled},i.toggle=function(t){if(this._isEnabled)if(t){var n=this.constructor.DATA_KEY,i=e(t.currentTarget).data(n);i||(i=new this.constructor(t.currentTarget,this._getDelegateConfig()),e(t.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(e(this.getTipElement()).hasClass("show"))return void this._leave(null,this);this._enter(null,this)}},i.dispose=function(){clearTimeout(this._timeout),e.removeData(this.element,this.constructor.DATA_KEY),e(this.element).off(this.constructor.EVENT_KEY),e(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&e(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},i.show=function(){var t=this;if("none"===e(this.element).css("display"))throw new Error("Please use show on visible elements");var i=e.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){e(this.element).trigger(i);var o=a.findShadowRoot(this.element),s=e.contains(null!==o?o:this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),l=a.getUID(this.constructor.NAME);r.setAttribute("id",l),this.element.setAttribute("aria-describedby",l),this.setContent(),this.config.animation&&e(r).addClass("fade");var c="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(c);this.addAttachmentClass(h);var u=this._getContainer();e(r).data(this.constructor.DATA_KEY,this),e.contains(this.element.ownerDocument.documentElement,this.tip)||e(r).appendTo(u),e(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,this._getPopperConfig(h)),e(r).addClass("show"),"ontouchstart"in document.documentElement&&e(document.body).children().on("mouseover",null,e.noop);var d=function(){t.config.animation&&t._fixTransition();var n=t._hoverState;t._hoverState=null,e(t.element).trigger(t.constructor.Event.SHOWN),"out"===n&&t._leave(null,t)};if(e(this.tip).hasClass("fade")){var f=a.getTransitionDurationFromElement(this.tip);e(this.tip).one(a.TRANSITION_END,d).emulateTransitionEnd(f)}else d()}},i.hide=function(t){var n=this,i=this.getTipElement(),o=e.Event(this.constructor.Event.HIDE),s=function(){"show"!==n._hoverState&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),e(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),t&&t()};if(e(this.element).trigger(o),!o.isDefaultPrevented()){if(e(i).removeClass("show"),"ontouchstart"in document.documentElement&&e(document.body).children().off("mouseover",null,e.noop),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,e(this.tip).hasClass("fade")){var r=a.getTransitionDurationFromElement(i);e(i).one(a.TRANSITION_END,s).emulateTransitionEnd(r)}else s();this._hoverState=""}},i.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},i.isWithContent=function(){return Boolean(this.getTitle())},i.addAttachmentClass=function(t){e(this.getTipElement()).addClass("bs-tooltip-"+t)},i.getTipElement=function(){return this.tip=this.tip||e(this.config.template)[0],this.tip},i.setContent=function(){var t=this.getTipElement();this.setElementContent(e(t.querySelectorAll(".tooltip-inner")),this.getTitle()),e(t).removeClass("fade show")},i.setElementContent=function(t,n){"object"!=typeof n||!n.nodeType&&!n.jquery?this.config.html?(this.config.sanitize&&(n=Q(n,this.config.whiteList,this.config.sanitizeFn)),t.html(n)):t.text(n):this.config.html?e(n).parent().is(t)||t.empty().append(n):t.text(e(n).text())},i.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},i._getPopperConfig=function(t){var e=this;return s({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},i._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=s({},e.offsets,t.config.offset(e.offsets,t.element)||{}),e}:e.offset=this.config.offset,e},i._getContainer=function(){return!1===this.config.container?document.body:a.isElement(this.config.container)?e(this.config.container):e(document).find(this.config.container)},i._getAttachment=function(t){return V[t.toUpperCase()]},i._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(n){if("click"===n)e(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==n){var i="hover"===n?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,o="hover"===n?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;e(t.element).on(i,t.config.selector,(function(e){return t._enter(e)})).on(o,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},e(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=s({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},i._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},i._enter=function(t,n){var i=this.constructor.DATA_KEY;(n=n||e(t.currentTarget).data(i))||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),e(t.currentTarget).data(i,n)),t&&(n._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e(n.getTipElement()).hasClass("show")||"show"===n._hoverState?n._hoverState="show":(clearTimeout(n._timeout),n._hoverState="show",n.config.delay&&n.config.delay.show?n._timeout=setTimeout((function(){"show"===n._hoverState&&n.show()}),n.config.delay.show):n.show())},i._leave=function(t,n){var i=this.constructor.DATA_KEY;(n=n||e(t.currentTarget).data(i))||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),e(t.currentTarget).data(i,n)),t&&(n._activeTrigger["focusout"===t.type?"focus":"hover"]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState="out",n.config.delay&&n.config.delay.hide?n._timeout=setTimeout((function(){"out"===n._hoverState&&n.hide()}),n.config.delay.hide):n.hide())},i._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},i._getConfig=function(t){var n=e(this.element).data();return Object.keys(n).forEach((function(t){-1!==M.indexOf(t)&&delete n[t]})),"number"==typeof(t=s({},this.constructor.Default,n,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a.typeCheckConfig(B,t,this.constructor.DefaultType),t.sanitize&&(t.template=Q(t.template,t.whiteList,t.sanitizeFn)),t},i._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},i._cleanTipClass=function(){var t=e(this.getTipElement()),n=t.attr("class").match(U);null!==n&&n.length&&t.removeClass(n.join(""))},i._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},i._fixTransition=function(){var t=this.getTipElement(),n=this.config.animation;null===t.getAttribute("x-placement")&&(e(t).removeClass("fade"),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.tooltip"),o="object"==typeof n&&n;if((i||!/dispose|hide/.test(n))&&(i||(i=new t(this,o),e(this).data("bs.tooltip",i)),"string"==typeof n)){if("undefined"==typeof i[n])throw new TypeError('No method named "'+n+'"');i[n]()}}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}},{key:"Default",get:function(){return z}},{key:"NAME",get:function(){return B}},{key:"DATA_KEY",get:function(){return"bs.tooltip"}},{key:"Event",get:function(){return K}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return W}}]),t}();e.fn[B]=X._jQueryInterface,e.fn[B].Constructor=X,e.fn[B].noConflict=function(){return e.fn[B]=H,X._jQueryInterface};var Y="popover",$=e.fn[Y],J=new RegExp("(^|\\s)bs-popover\\S+","g"),G=s({},X.Default,{placement:"right",trigger:"click",content:"",template:''}),Z=s({},X.DefaultType,{content:"(string|element|function)"}),tt={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},et=function(t){var n,i;function s(){return t.apply(this,arguments)||this}i=t,(n=s).prototype=Object.create(i.prototype),n.prototype.constructor=n,n.__proto__=i;var r=s.prototype;return r.isWithContent=function(){return this.getTitle()||this._getContent()},r.addAttachmentClass=function(t){e(this.getTipElement()).addClass("bs-popover-"+t)},r.getTipElement=function(){return this.tip=this.tip||e(this.config.template)[0],this.tip},r.setContent=function(){var t=e(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(t.find(".popover-body"),n),t.removeClass("fade show")},r._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},r._cleanTipClass=function(){var t=e(this.getTipElement()),n=t.attr("class").match(J);null!==n&&n.length>0&&t.removeClass(n.join(""))},s._jQueryInterface=function(t){return this.each((function(){var n=e(this).data("bs.popover"),i="object"==typeof t?t:null;if((n||!/dispose|hide/.test(t))&&(n||(n=new s(this,i),e(this).data("bs.popover",n)),"string"==typeof t)){if("undefined"==typeof n[t])throw new TypeError('No method named "'+t+'"');n[t]()}}))},o(s,null,[{key:"VERSION",get:function(){return"4.5.2"}},{key:"Default",get:function(){return G}},{key:"NAME",get:function(){return Y}},{key:"DATA_KEY",get:function(){return"bs.popover"}},{key:"Event",get:function(){return tt}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return Z}}]),s}(X);e.fn[Y]=et._jQueryInterface,e.fn[Y].Constructor=et,e.fn[Y].noConflict=function(){return e.fn[Y]=$,et._jQueryInterface};var nt="scrollspy",it=e.fn[nt],ot={offset:10,method:"auto",target:""},st={offset:"number",method:"string",target:"(string|element)"},rt=function(){function t(t,n){var i=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(n),this._selector=this._config.target+" .nav-link,"+this._config.target+" .list-group-item,"+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,e(this._scrollElement).on("scroll.bs.scrollspy",(function(t){return i._process(t)})),this.refresh(),this._process()}var n=t.prototype;return n.refresh=function(){var t=this,n=this._scrollElement===this._scrollElement.window?"offset":"position",i="auto"===this._config.method?n:this._config.method,o="position"===i?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var n,s=a.getSelectorFromElement(t);if(s&&(n=document.querySelector(s)),n){var r=n.getBoundingClientRect();if(r.width||r.height)return[e(n)[i]().top+o,s]}return null})).filter((function(t){return t})).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},n.dispose=function(){e.removeData(this._element,"bs.scrollspy"),e(this._scrollElement).off(".bs.scrollspy"),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},n._getConfig=function(t){if("string"!=typeof(t=s({},ot,"object"==typeof t&&t?t:{})).target&&a.isElement(t.target)){var n=e(t.target).attr("id");n||(n=a.getUID(nt),e(t.target).attr("id",n)),t.target="#"+n}return a.typeCheckConfig(nt,t,st),t},n._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},n._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},n._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},n._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;){this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active":".active";i=(i=e.makeArray(e(o).find(r)))[i.length-1]}var l=e.Event("hide.bs.tab",{relatedTarget:this._element}),c=e.Event("show.bs.tab",{relatedTarget:i});if(i&&e(i).trigger(l),e(this._element).trigger(c),!c.isDefaultPrevented()&&!l.isDefaultPrevented()){s&&(n=document.querySelector(s)),this._activate(this._element,o);var h=function(){var n=e.Event("hidden.bs.tab",{relatedTarget:t._element}),o=e.Event("shown.bs.tab",{relatedTarget:i});e(i).trigger(n),e(t._element).trigger(o)};n?this._activate(n,n.parentNode,h):h()}}},n.dispose=function(){e.removeData(this._element,"bs.tab"),this._element=null},n._activate=function(t,n,i){var o=this,s=(!n||"UL"!==n.nodeName&&"OL"!==n.nodeName?e(n).children(".active"):e(n).find("> li > .active"))[0],r=i&&s&&e(s).hasClass("fade"),l=function(){return o._transitionComplete(t,s,i)};if(s&&r){var c=a.getTransitionDurationFromElement(s);e(s).removeClass("show").one(a.TRANSITION_END,l).emulateTransitionEnd(c)}else l()},n._transitionComplete=function(t,n,i){if(n){e(n).removeClass("active");var o=e(n.parentNode).find("> .dropdown-menu .active")[0];o&&e(o).removeClass("active"),"tab"===n.getAttribute("role")&&n.setAttribute("aria-selected",!1)}if(e(t).addClass("active"),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),a.reflow(t),t.classList.contains("fade")&&t.classList.add("show"),t.parentNode&&e(t.parentNode).hasClass("dropdown-menu")){var s=e(t).closest(".dropdown")[0];if(s){var r=[].slice.call(s.querySelectorAll(".dropdown-toggle"));e(r).addClass("active")}t.setAttribute("aria-expanded",!0)}i&&i()},t._jQueryInterface=function(n){return this.each((function(){var i=e(this),o=i.data("bs.tab");if(o||(o=new t(this),i.data("bs.tab",o)),"string"==typeof n){if("undefined"==typeof o[n])throw new TypeError('No method named "'+n+'"');o[n]()}}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}}]),t}();e(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(t){t.preventDefault(),lt._jQueryInterface.call(e(this),"show")})),e.fn.tab=lt._jQueryInterface,e.fn.tab.Constructor=lt,e.fn.tab.noConflict=function(){return e.fn.tab=at,lt._jQueryInterface};var ct=e.fn.toast,ht={animation:"boolean",autohide:"boolean",delay:"number"},ut={animation:!0,autohide:!0,delay:500},dt=function(){function t(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var n=t.prototype;return n.show=function(){var t=this,n=e.Event("show.bs.toast");if(e(this._element).trigger(n),!n.isDefaultPrevented()){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var i=function(){t._element.classList.remove("showing"),t._element.classList.add("show"),e(t._element).trigger("shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove("hide"),a.reflow(this._element),this._element.classList.add("showing"),this._config.animation){var o=a.getTransitionDurationFromElement(this._element);e(this._element).one(a.TRANSITION_END,i).emulateTransitionEnd(o)}else i()}},n.hide=function(){if(this._element.classList.contains("show")){var t=e.Event("hide.bs.toast");e(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},n.dispose=function(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),e(this._element).off("click.dismiss.bs.toast"),e.removeData(this._element,"bs.toast"),this._element=null,this._config=null},n._getConfig=function(t){return t=s({},ut,e(this._element).data(),"object"==typeof t&&t?t:{}),a.typeCheckConfig("toast",t,this.constructor.DefaultType),t},n._setListeners=function(){var t=this;e(this._element).on("click.dismiss.bs.toast",'[data-dismiss="toast"]',(function(){return t.hide()}))},n._close=function(){var t=this,n=function(){t._element.classList.add("hide"),e(t._element).trigger("hidden.bs.toast")};if(this._element.classList.remove("show"),this._config.animation){var i=a.getTransitionDurationFromElement(this._element);e(this._element).one(a.TRANSITION_END,n).emulateTransitionEnd(i)}else n()},n._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},t._jQueryInterface=function(n){return this.each((function(){var i=e(this),o=i.data("bs.toast");if(o||(o=new t(this,"object"==typeof n&&n),i.data("bs.toast",o)),"string"==typeof n){if("undefined"==typeof o[n])throw new TypeError('No method named "'+n+'"');o[n](this)}}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}},{key:"DefaultType",get:function(){return ht}},{key:"Default",get:function(){return ut}}]),t}();e.fn.toast=dt._jQueryInterface,e.fn.toast.Constructor=dt,e.fn.toast.noConflict=function(){return e.fn.toast=ct,dt._jQueryInterface},t.Alert=h,t.Button=d,t.Carousel=b,t.Collapse=C,t.Dropdown=I,t.Modal=P,t.Popover=et,t.Scrollspy=rt,t.Tab=lt,t.Toast=dt,t.Tooltip=X,t.Util=a,Object.defineProperty(t,"__esModule",{value:!0})})); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e(t.Popper)}(this,(function(t){"use strict";function e(t){if(t&&t.__esModule)return t;var e=Object.create(null);return t&&Object.keys(t).forEach((function(s){if("default"!==s){var i=Object.getOwnPropertyDescriptor(t,s);Object.defineProperty(e,s,i.get?i:{enumerable:!0,get:function(){return t[s]}})}})),e.default=t,Object.freeze(e)}var s=e(t);const i={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const s=[];let i=t.parentNode;for(;i&&i.nodeType===Node.ELEMENT_NODE&&3!==i.nodeType;)i.matches(e)&&s.push(i),i=i.parentNode;return s},prev(t,e){let s=t.previousElementSibling;for(;s;){if(s.matches(e))return[s];s=s.previousElementSibling}return[]},next(t,e){let s=t.nextElementSibling;for(;s;){if(s.matches(e))return[s];s=s.nextElementSibling}return[]}},n=t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t},o=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let s=t.getAttribute("href");if(!s||!s.includes("#")&&!s.startsWith("."))return null;s.includes("#")&&!s.startsWith("#")&&(s="#"+s.split("#")[1]),e=s&&"#"!==s?s.trim():null}return e},r=t=>{const e=o(t);return e&&document.querySelector(e)?e:null},a=t=>{const e=o(t);return e?document.querySelector(e):null},l=t=>{t.dispatchEvent(new Event("transitionend"))},c=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),h=t=>c(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?i.findOne(t):null,d=(t,e,s)=>{Object.keys(s).forEach(i=>{const n=s[i],o=e[i],r=o&&c(o)?"element":null==(a=o)?""+a:{}.toString.call(a).match(/\s([a-z]+)/i)[1].toLowerCase();var a;if(!new RegExp(n).test(r))throw new TypeError(`${t.toUpperCase()}: Option "${i}" provided type "${r}" but expected type "${n}".`)})},u=t=>!(!c(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),g=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),p=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?p(t.parentNode):null},f=()=>{},m=t=>t.offsetHeight,_=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},b=[],v=()=>"rtl"===document.documentElement.dir,y=t=>{var e;e=()=>{const e=_();if(e){const s=t.NAME,i=e.fn[s];e.fn[s]=t.jQueryInterface,e.fn[s].Constructor=t,e.fn[s].noConflict=()=>(e.fn[s]=i,t.jQueryInterface)}},"loading"===document.readyState?(b.length||document.addEventListener("DOMContentLoaded",()=>{b.forEach(t=>t())}),b.push(e)):e()},w=t=>{"function"==typeof t&&t()},E=(t,e,s=!0)=>{if(!s)return void w(t);const i=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:s}=window.getComputedStyle(t);const i=Number.parseFloat(e),n=Number.parseFloat(s);return i||n?(e=e.split(",")[0],s=s.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(s))):0})(e)+5;let n=!1;const o=({target:s})=>{s===e&&(n=!0,e.removeEventListener("transitionend",o),w(t))};e.addEventListener("transitionend",o),setTimeout(()=>{n||l(e)},i)},A=(t,e,s,i)=>{let n=t.indexOf(e);if(-1===n)return t[!s&&i?t.length-1:0];const o=t.length;return n+=s?1:-1,i&&(n=(n+o)%o),t[Math.max(0,Math.min(n,o-1))]},T=/[^.]*(?=\..*)\.|.*/,C=/\..*/,k=/::\d+$/,L={};let O=1;const D={mouseenter:"mouseover",mouseleave:"mouseout"},I=/^(mouseenter|mouseleave)/i,N=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function S(t,e){return e&&`${e}::${O++}`||t.uidEvent||O++}function x(t){const e=S(t);return t.uidEvent=e,L[e]=L[e]||{},L[e]}function M(t,e,s=null){const i=Object.keys(t);for(let n=0,o=i.length;nfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};i?i=t(i):s=t(s)}const[o,r,a]=P(e,s,i),l=x(t),c=l[a]||(l[a]={}),h=M(c,r,o?s:null);if(h)return void(h.oneOff=h.oneOff&&n);const d=S(r,e.replace(T,"")),u=o?function(t,e,s){return function i(n){const o=t.querySelectorAll(e);for(let{target:r}=n;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return n.delegateTarget=r,i.oneOff&&B.off(t,n.type,e,s),s.apply(r,[n]);return null}}(t,s,i):function(t,e){return function s(i){return i.delegateTarget=t,s.oneOff&&B.off(t,i.type,e),e.apply(t,[i])}}(t,s);u.delegationSelector=o?s:null,u.originalHandler=r,u.oneOff=n,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function H(t,e,s,i,n){const o=M(e[s],i,n);o&&(t.removeEventListener(s,o,Boolean(n)),delete e[s][o.uidEvent])}function R(t){return t=t.replace(C,""),D[t]||t}const B={on(t,e,s,i){j(t,e,s,i,!1)},one(t,e,s,i){j(t,e,s,i,!0)},off(t,e,s,i){if("string"!=typeof e||!t)return;const[n,o,r]=P(e,s,i),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void H(t,l,r,o,n?s:null)}c&&Object.keys(l).forEach(s=>{!function(t,e,s,i){const n=e[s]||{};Object.keys(n).forEach(o=>{if(o.includes(i)){const i=n[o];H(t,e,s,i.originalHandler,i.delegationSelector)}})}(t,l,s,e.slice(1))});const h=l[r]||{};Object.keys(h).forEach(s=>{const i=s.replace(k,"");if(!a||e.includes(i)){const e=h[s];H(t,l,r,e.originalHandler,e.delegationSelector)}})},trigger(t,e,s){if("string"!=typeof e||!t)return null;const i=_(),n=R(e),o=e!==n,r=N.has(n);let a,l=!0,c=!0,h=!1,d=null;return o&&i&&(a=i.Event(e,s),i(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(n,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==s&&Object.keys(s).forEach(t=>{Object.defineProperty(d,t,{get:()=>s[t]})}),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},$=new Map;var W={set(t,e,s){$.has(t)||$.set(t,new Map);const i=$.get(t);i.has(e)||0===i.size?i.set(e,s):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(i.keys())[0]}.`)},get:(t,e)=>$.has(t)&&$.get(t).get(e)||null,remove(t,e){if(!$.has(t))return;const s=$.get(t);s.delete(e),0===s.size&&$.delete(t)}};class q{constructor(t){(t=h(t))&&(this._element=t,W.set(this._element,this.constructor.DATA_KEY,this))}dispose(){W.remove(this._element,this.constructor.DATA_KEY),B.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach(t=>{this[t]=null})}_queueCallback(t,e,s=!0){E(t,e,s)}static getInstance(t){return W.get(t,this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.0.2"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return"bs."+this.NAME}static get EVENT_KEY(){return"."+this.DATA_KEY}}class z extends q{static get NAME(){return"alert"}close(t){const e=t?this._getRootElement(t):this._element,s=this._triggerCloseEvent(e);null===s||s.defaultPrevented||this._removeElement(e)}_getRootElement(t){return a(t)||t.closest(".alert")}_triggerCloseEvent(t){return B.trigger(t,"close.bs.alert")}_removeElement(t){t.classList.remove("show");const e=t.classList.contains("fade");this._queueCallback(()=>this._destroyElement(t),t,e)}_destroyElement(t){t.remove(),B.trigger(t,"closed.bs.alert")}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"close"===t&&e[t](this)}))}static handleDismiss(t){return function(e){e&&e.preventDefault(),t.close(this)}}}B.on(document,"click.bs.alert.data-api",'[data-bs-dismiss="alert"]',z.handleDismiss(new z)),y(z);class F extends q{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=F.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function U(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function K(t){return t.replace(/[A-Z]/g,t=>"-"+t.toLowerCase())}B.on(document,"click.bs.button.data-api",'[data-bs-toggle="button"]',t=>{t.preventDefault();const e=t.target.closest('[data-bs-toggle="button"]');F.getOrCreateInstance(e).toggle()}),y(F);const V={setDataAttribute(t,e,s){t.setAttribute("data-bs-"+K(e),s)},removeDataAttribute(t,e){t.removeAttribute("data-bs-"+K(e))},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter(t=>t.startsWith("bs")).forEach(s=>{let i=s.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=U(t.dataset[s])}),e},getDataAttribute:(t,e)=>U(t.getAttribute("data-bs-"+K(e))),offset(t){const e=t.getBoundingClientRect();return{top:e.top+document.body.scrollTop,left:e.left+document.body.scrollLeft}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},Q={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},X={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Y="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z};class et extends q{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=i.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return Q}static get NAME(){return"carousel"}next(){this._slide(Y)}nextWhenVisible(){!document.hidden&&u(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),i.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(l(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=i.findOne(".active.carousel-item",this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void B.one(this._element,"slid.bs.carousel",()=>this.to(t));if(e===t)return this.pause(),void this.cycle();const s=t>e?Y:G;this._slide(s,this._items[t])}_getConfig(t){return t={...Q,...V.getDataAttributes(this._element),..."object"==typeof t?t:{}},d("carousel",t,X),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&B.on(this._element,"keydown.bs.carousel",t=>this._keydown(t)),"hover"===this._config.pause&&(B.on(this._element,"mouseenter.bs.carousel",t=>this.pause(t)),B.on(this._element,"mouseleave.bs.carousel",t=>this.cycle(t))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType?this._pointerEvent||(this.touchStartX=t.touches[0].clientX):this.touchStartX=t.clientX},e=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},s=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType||(this.touchDeltaX=t.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(t=>this.cycle(t),500+this._config.interval))};i.find(".carousel-item img",this._element).forEach(t=>{B.on(t,"dragstart.bs.carousel",t=>t.preventDefault())}),this._pointerEvent?(B.on(this._element,"pointerdown.bs.carousel",e=>t(e)),B.on(this._element,"pointerup.bs.carousel",t=>s(t)),this._element.classList.add("pointer-event")):(B.on(this._element,"touchstart.bs.carousel",e=>t(e)),B.on(this._element,"touchmove.bs.carousel",t=>e(t)),B.on(this._element,"touchend.bs.carousel",t=>s(t)))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?i.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const s=t===Y;return A(this._items,e,s,this._config.wrap)}_triggerSlideEvent(t,e){const s=this._getItemIndex(t),n=this._getItemIndex(i.findOne(".active.carousel-item",this._element));return B.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:s})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=i.findOne(".active",this._indicatorsElement);e.classList.remove("active"),e.removeAttribute("aria-current");const s=i.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{B.trigger(this._element,"slid.bs.carousel",{relatedTarget:r,direction:u,from:o,to:a})};if(this._element.classList.contains("slide")){r.classList.add(d),m(r),n.classList.add(h),r.classList.add(h);const t=()=>{r.classList.remove(h,d),r.classList.add("active"),n.classList.remove("active",d,h),this._isSliding=!1,setTimeout(g,0)};this._queueCallback(t,n,!0)}else n.classList.remove("active"),r.classList.add("active"),this._isSliding=!1,g();l&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?v()?t===Z?G:Y:t===Z?Y:G:t}_orderToDirection(t){return[Y,G].includes(t)?v()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const s=et.getOrCreateInstance(t,e);let{_config:i}=s;"object"==typeof e&&(i={...i,...e});const n="string"==typeof e?e:i.slide;if("number"==typeof e)s.to(e);else if("string"==typeof n){if(void 0===s[n])throw new TypeError(`No method named "${n}"`);s[n]()}else i.interval&&i.ride&&(s.pause(),s.cycle())}static jQueryInterface(t){return this.each((function(){et.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=a(this);if(!e||!e.classList.contains("carousel"))return;const s={...V.getDataAttributes(e),...V.getDataAttributes(this)},i=this.getAttribute("data-bs-slide-to");i&&(s.interval=!1),et.carouselInterface(e,s),i&&et.getInstance(e).to(i),t.preventDefault()}}B.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",et.dataApiClickHandler),B.on(window,"load.bs.carousel.data-api",()=>{const t=i.find('[data-bs-ride="carousel"]');for(let e=0,s=t.length;et===this._element);null!==n&&o.length&&(this._selector=n,this._triggerArray.push(e))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}static get Default(){return st}static get NAME(){return"collapse"}toggle(){this._element.classList.contains("show")?this.hide():this.show()}show(){if(this._isTransitioning||this._element.classList.contains("show"))return;let t,e;this._parent&&(t=i.find(".show, .collapsing",this._parent).filter(t=>"string"==typeof this._config.parent?t.getAttribute("data-bs-parent")===this._config.parent:t.classList.contains("collapse")),0===t.length&&(t=null));const s=i.findOne(this._selector);if(t){const i=t.find(t=>s!==t);if(e=i?nt.getInstance(i):null,e&&e._isTransitioning)return}if(B.trigger(this._element,"show.bs.collapse").defaultPrevented)return;t&&t.forEach(t=>{s!==t&&nt.collapseInterface(t,"hide"),e||W.set(t,"bs.collapse",null)});const n=this._getDimension();this._element.classList.remove("collapse"),this._element.classList.add("collapsing"),this._element.style[n]=0,this._triggerArray.length&&this._triggerArray.forEach(t=>{t.classList.remove("collapsed"),t.setAttribute("aria-expanded",!0)}),this.setTransitioning(!0);const o="scroll"+(n[0].toUpperCase()+n.slice(1));this._queueCallback(()=>{this._element.classList.remove("collapsing"),this._element.classList.add("collapse","show"),this._element.style[n]="",this.setTransitioning(!1),B.trigger(this._element,"shown.bs.collapse")},this._element,!0),this._element.style[n]=this._element[o]+"px"}hide(){if(this._isTransitioning||!this._element.classList.contains("show"))return;if(B.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=this._element.getBoundingClientRect()[t]+"px",m(this._element),this._element.classList.add("collapsing"),this._element.classList.remove("collapse","show");const e=this._triggerArray.length;if(e>0)for(let t=0;t{this.setTransitioning(!1),this._element.classList.remove("collapsing"),this._element.classList.add("collapse"),B.trigger(this._element,"hidden.bs.collapse")},this._element,!0)}setTransitioning(t){this._isTransitioning=t}_getConfig(t){return(t={...st,...t}).toggle=Boolean(t.toggle),d("collapse",t,it),t}_getDimension(){return this._element.classList.contains("width")?"width":"height"}_getParent(){let{parent:t}=this._config;t=h(t);const e=`[data-bs-toggle="collapse"][data-bs-parent="${t}"]`;return i.find(e,t).forEach(t=>{const e=a(t);this._addAriaAndCollapsedClass(e,[t])}),t}_addAriaAndCollapsedClass(t,e){if(!t||!e.length)return;const s=t.classList.contains("show");e.forEach(t=>{s?t.classList.remove("collapsed"):t.classList.add("collapsed"),t.setAttribute("aria-expanded",s)})}static collapseInterface(t,e){let s=nt.getInstance(t);const i={...st,...V.getDataAttributes(t),..."object"==typeof e&&e?e:{}};if(!s&&i.toggle&&"string"==typeof e&&/show|hide/.test(e)&&(i.toggle=!1),s||(s=new nt(t,i)),"string"==typeof e){if(void 0===s[e])throw new TypeError(`No method named "${e}"`);s[e]()}}static jQueryInterface(t){return this.each((function(){nt.collapseInterface(this,t)}))}}B.on(document,"click.bs.collapse.data-api",'[data-bs-toggle="collapse"]',(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=V.getDataAttributes(this),s=r(this);i.find(s).forEach(t=>{const s=nt.getInstance(t);let i;s?(null===s._parent&&"string"==typeof e.parent&&(s._config.parent=e.parent,s._parent=s._getParent()),i="toggle"):i=e,nt.collapseInterface(t,i)})})),y(nt);const ot=new RegExp("ArrowUp|ArrowDown|Escape"),rt=v()?"top-end":"top-start",at=v()?"top-start":"top-end",lt=v()?"bottom-end":"bottom-start",ct=v()?"bottom-start":"bottom-end",ht=v()?"left-start":"right-start",dt=v()?"right-start":"left-start",ut={offset:[0,2],boundary:"clippingParents",reference:"toggle",display:"dynamic",popperConfig:null,autoClose:!0},gt={offset:"(array|string|function)",boundary:"(string|element)",reference:"(string|element|object)",display:"string",popperConfig:"(null|object|function)",autoClose:"(boolean|string)"};class pt extends q{constructor(t,e){super(t),this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}static get Default(){return ut}static get DefaultType(){return gt}static get NAME(){return"dropdown"}toggle(){g(this._element)||(this._element.classList.contains("show")?this.hide():this.show())}show(){if(g(this._element)||this._menu.classList.contains("show"))return;const t=pt.getParentFromElement(this._element),e={relatedTarget:this._element};if(!B.trigger(this._element,"show.bs.dropdown",e).defaultPrevented){if(this._inNavbar)V.setDataAttribute(this._menu,"popper","none");else{if(void 0===s)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:c(this._config.reference)?e=h(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find(t=>"applyStyles"===t.name&&!1===t.enabled);this._popper=s.createPopper(e,this._menu,i),n&&V.setDataAttribute(this._menu,"popper","static")}"ontouchstart"in document.documentElement&&!t.closest(".navbar-nav")&&[].concat(...document.body.children).forEach(t=>B.on(t,"mouseover",f)),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.toggle("show"),this._element.classList.toggle("show"),B.trigger(this._element,"shown.bs.dropdown",e)}}hide(){if(g(this._element)||!this._menu.classList.contains("show"))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_addEventListeners(){B.on(this._element,"click.bs.dropdown",t=>{t.preventDefault(),this.toggle()})}_completeHide(t){B.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>B.off(t,"mouseover",f)),this._popper&&this._popper.destroy(),this._menu.classList.remove("show"),this._element.classList.remove("show"),this._element.setAttribute("aria-expanded","false"),V.removeDataAttribute(this._menu,"popper"),B.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...V.getDataAttributes(this._element),...t},d("dropdown",t,this.constructor.DefaultType),"object"==typeof t.reference&&!c(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError("dropdown".toUpperCase()+': Option "reference" provided type "object" without a required "getBoundingClientRect" method.');return t}_getMenuElement(){return i.next(this._element,".dropdown-menu")[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ht;if(t.classList.contains("dropstart"))return dt;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?at:rt:e?ct:lt}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const s=i.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(u);s.length&&A(s,e,"ArrowDown"===t,!s.includes(e)).focus()}static dropdownInterface(t,e){const s=pt.getOrCreateInstance(t,e);if("string"==typeof e){if(void 0===s[e])throw new TypeError(`No method named "${e}"`);s[e]()}}static jQueryInterface(t){return this.each((function(){pt.dropdownInterface(this,t)}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=i.find('[data-bs-toggle="dropdown"]');for(let s=0,i=e.length;sthis.matches('[data-bs-toggle="dropdown"]')?this:i.prev(this,'[data-bs-toggle="dropdown"]')[0];return"Escape"===t.key?(s().focus(),void pt.clearMenus()):"ArrowUp"===t.key||"ArrowDown"===t.key?(e||s().click(),void pt.getInstance(s())._selectMenuItem(t)):void(e&&"Space"!==t.key||pt.clearMenus())}}B.on(document,"keydown.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',pt.dataApiKeydownHandler),B.on(document,"keydown.bs.dropdown.data-api",".dropdown-menu",pt.dataApiKeydownHandler),B.on(document,"click.bs.dropdown.data-api",pt.clearMenus),B.on(document,"keyup.bs.dropdown.data-api",pt.clearMenus),B.on(document,"click.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',(function(t){t.preventDefault(),pt.dropdownInterface(this)})),y(pt);class ft{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,"paddingRight",e=>e+t),this._setElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight",e=>e+t),this._setElementAttributes(".sticky-top","marginRight",e=>e-t)}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,s){const i=this.getWidth();this._applyManipulationCallback(t,t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+i)return;this._saveInitialAttribute(t,e);const n=window.getComputedStyle(t)[e];t.style[e]=s(Number.parseFloat(n))+"px"})}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight"),this._resetElementAttributes(".sticky-top","marginRight")}_saveInitialAttribute(t,e){const s=t.style[e];s&&V.setDataAttribute(t,e,s)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,t=>{const s=V.getDataAttribute(t,e);void 0===s?t.style.removeProperty(e):(V.removeDataAttribute(t,e),t.style[e]=s)})}_applyManipulationCallback(t,e){c(t)?e(t):i.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const mt={isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},_t={isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"};class bt{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&m(this._getElement()),this._getElement().classList.add("show"),this._emulateAnimation(()=>{w(t)})):w(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove("show"),this._emulateAnimation(()=>{this.dispose(),w(t)})):w(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className="modal-backdrop",this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...mt,..."object"==typeof t?t:{}}).rootElement=h(t.rootElement),d("backdrop",t,_t),t}_append(){this._isAppended||(this._config.rootElement.appendChild(this._getElement()),B.on(this._getElement(),"mousedown.bs.backdrop",()=>{w(this._config.clickCallback)}),this._isAppended=!0)}dispose(){this._isAppended&&(B.off(this._element,"mousedown.bs.backdrop"),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){E(t,this._getElement(),this._config.isAnimated)}}const vt={backdrop:!0,keyboard:!0,focus:!0},yt={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"};class wt extends q{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=i.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new ft}static get Default(){return vt}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||B.trigger(this._element,"show.bs.modal",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add("modal-open"),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),B.on(this._element,"click.dismiss.bs.modal",'[data-bs-dismiss="modal"]',t=>this.hide(t)),B.on(this._dialog,"mousedown.dismiss.bs.modal",()=>{B.one(this._element,"mouseup.dismiss.bs.modal",t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)})}),this._showBackdrop(()=>this._showElement(t)))}hide(t){if(t&&["A","AREA"].includes(t.target.tagName)&&t.preventDefault(),!this._isShown||this._isTransitioning)return;if(B.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const e=this._isAnimated();e&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),B.off(document,"focusin.bs.modal"),this._element.classList.remove("show"),B.off(this._element,"click.dismiss.bs.modal"),B.off(this._dialog,"mousedown.dismiss.bs.modal"),this._queueCallback(()=>this._hideModal(),this._element,e)}dispose(){[window,this._dialog].forEach(t=>B.off(t,".bs.modal")),this._backdrop.dispose(),super.dispose(),B.off(document,"focusin.bs.modal")}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bt({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_getConfig(t){return t={...vt,...V.getDataAttributes(this._element),..."object"==typeof t?t:{}},d("modal",t,yt),t}_showElement(t){const e=this._isAnimated(),s=i.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,s&&(s.scrollTop=0),e&&m(this._element),this._element.classList.add("show"),this._config.focus&&this._enforceFocus(),this._queueCallback(()=>{this._config.focus&&this._element.focus(),this._isTransitioning=!1,B.trigger(this._element,"shown.bs.modal",{relatedTarget:t})},this._dialog,e)}_enforceFocus(){B.off(document,"focusin.bs.modal"),B.on(document,"focusin.bs.modal",t=>{document===t.target||this._element===t.target||this._element.contains(t.target)||this._element.focus()})}_setEscapeEvent(){this._isShown?B.on(this._element,"keydown.dismiss.bs.modal",t=>{this._config.keyboard&&"Escape"===t.key?(t.preventDefault(),this.hide()):this._config.keyboard||"Escape"!==t.key||this._triggerBackdropTransition()}):B.off(this._element,"keydown.dismiss.bs.modal")}_setResizeEvent(){this._isShown?B.on(window,"resize.bs.modal",()=>this._adjustDialog()):B.off(window,"resize.bs.modal")}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove("modal-open"),this._resetAdjustments(),this._scrollBar.reset(),B.trigger(this._element,"hidden.bs.modal")})}_showBackdrop(t){B.on(this._element,"click.dismiss.bs.modal",t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())}),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(B.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:s}=this._element,i=e>document.documentElement.clientHeight;!i&&"hidden"===s.overflowY||t.contains("modal-static")||(i||(s.overflowY="hidden"),t.add("modal-static"),this._queueCallback(()=>{t.remove("modal-static"),i||this._queueCallback(()=>{s.overflowY=""},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),s=e>0;(!s&&t&&!v()||s&&!t&&v())&&(this._element.style.paddingLeft=e+"px"),(s&&!t&&!v()||!s&&t&&v())&&(this._element.style.paddingRight=e+"px")}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const s=wt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===s[t])throw new TypeError(`No method named "${t}"`);s[t](e)}}))}}B.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=a(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),B.one(e,"show.bs.modal",t=>{t.defaultPrevented||B.one(e,"hidden.bs.modal",()=>{u(this)&&this.focus()})}),wt.getOrCreateInstance(e).toggle(this)})),y(wt);const Et={backdrop:!0,keyboard:!0,scroll:!1},At={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"};class Tt extends q{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._addEventListeners()}static get NAME(){return"offcanvas"}static get Default(){return Et}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||B.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||((new ft).hide(),this._enforceFocusOnElement(this._element)),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add("show"),this._queueCallback(()=>{B.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})},this._element,!0))}hide(){this._isShown&&(B.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(B.off(document,"focusin.bs.offcanvas"),this._element.blur(),this._isShown=!1,this._element.classList.remove("show"),this._backdrop.hide(),this._queueCallback(()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new ft).reset(),B.trigger(this._element,"hidden.bs.offcanvas")},this._element,!0)))}dispose(){this._backdrop.dispose(),super.dispose(),B.off(document,"focusin.bs.offcanvas")}_getConfig(t){return t={...Et,...V.getDataAttributes(this._element),..."object"==typeof t?t:{}},d("offcanvas",t,At),t}_initializeBackDrop(){return new bt({isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_enforceFocusOnElement(t){B.off(document,"focusin.bs.offcanvas"),B.on(document,"focusin.bs.offcanvas",e=>{document===e.target||t===e.target||t.contains(e.target)||t.focus()}),t.focus()}_addEventListeners(){B.on(this._element,"click.dismiss.bs.offcanvas",'[data-bs-dismiss="offcanvas"]',()=>this.hide()),B.on(this._element,"keydown.dismiss.bs.offcanvas",t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()})}static jQueryInterface(t){return this.each((function(){const e=Tt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}B.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=a(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),g(this))return;B.one(e,"hidden.bs.offcanvas",()=>{u(this)&&this.focus()});const s=i.findOne(".offcanvas.show");s&&s!==e&&Tt.getInstance(s).hide(),Tt.getOrCreateInstance(e).toggle(this)})),B.on(window,"load.bs.offcanvas.data-api",()=>i.find(".offcanvas.show").forEach(t=>Tt.getOrCreateInstance(t).show())),y(Tt);const Ct=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),kt=/^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i,Lt=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Ot=(t,e)=>{const s=t.nodeName.toLowerCase();if(e.includes(s))return!Ct.has(s)||Boolean(kt.test(t.nodeValue)||Lt.test(t.nodeValue));const i=e.filter(t=>t instanceof RegExp);for(let t=0,e=i.length;t{Ot(t,a)||s.removeAttribute(t.nodeName)})}return i.body.innerHTML}const It=new RegExp("(^|\\s)bs-tooltip\\S+","g"),Nt=new Set(["sanitize","allowList","sanitizeFn"]),St={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},xt={AUTO:"auto",TOP:"top",RIGHT:v()?"left":"right",BOTTOM:"bottom",LEFT:v()?"right":"left"},Mt={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Pt={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"};class jt extends q{constructor(t,e){if(void 0===s)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return Mt}static get NAME(){return"tooltip"}static get Event(){return Pt}static get DefaultType(){return St}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains("show"))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),B.off(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.tip&&this.tip.remove(),this._popper&&this._popper.destroy(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=B.trigger(this._element,this.constructor.Event.SHOW),e=p(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;const o=this.getTipElement(),r=n(this.constructor.NAME);o.setAttribute("id",r),this._element.setAttribute("aria-describedby",r),this.setContent(),this._config.animation&&o.classList.add("fade");const a="function"==typeof this._config.placement?this._config.placement.call(this,o,this._element):this._config.placement,l=this._getAttachment(a);this._addAttachmentClass(l);const{container:c}=this._config;W.set(o,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(c.appendChild(o),B.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=s.createPopper(this._element,o,this._getPopperConfig(l)),o.classList.add("show");const h="function"==typeof this._config.customClass?this._config.customClass():this._config.customClass;h&&o.classList.add(...h.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>{B.on(t,"mouseover",f)});const d=this.tip.classList.contains("fade");this._queueCallback(()=>{const t=this._hoverState;this._hoverState=null,B.trigger(this._element,this.constructor.Event.SHOWN),"out"===t&&this._leave(null,this)},this.tip,d)}hide(){if(!this._popper)return;const t=this.getTipElement();if(B.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove("show"),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>B.off(t,"mouseover",f)),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains("fade");this._queueCallback(()=>{this._isWithActiveTrigger()||("show"!==this._hoverState&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),B.trigger(this._element,this.constructor.Event.HIDDEN),this._popper&&(this._popper.destroy(),this._popper=null))},this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");return t.innerHTML=this._config.template,this.tip=t.children[0],this.tip}setContent(){const t=this.getTipElement();this.setElementContent(i.findOne(".tooltip-inner",t),this.getTitle()),t.classList.remove("fade","show")}setElementContent(t,e){if(null!==t)return c(e)?(e=h(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.appendChild(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Dt(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){let t=this._element.getAttribute("data-bs-original-title");return t||(t="function"==typeof this._config.title?this._config.title.call(this._element):this._config.title),t}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){const s=this.constructor.DATA_KEY;return(e=e||W.get(t.delegateTarget,s))||(e=new this.constructor(t.delegateTarget,this._getDelegateConfig()),W.set(t.delegateTarget,s,e)),e}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add("bs-tooltip-"+this.updateAttachment(t))}_getAttachment(t){return xt[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach(t=>{if("click"===t)B.on(this._element,this.constructor.Event.CLICK,this._config.selector,t=>this.toggle(t));else if("manual"!==t){const e="hover"===t?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,s="hover"===t?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;B.on(this._element,e,this._config.selector,t=>this._enter(t)),B.on(this._element,s,this._config.selector,t=>this._leave(t))}}),this._hideModalHandler=()=>{this._element&&this.hide()},B.on(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e.getTipElement().classList.contains("show")||"show"===e._hoverState?e._hoverState="show":(clearTimeout(e._timeout),e._hoverState="show",e._config.delay&&e._config.delay.show?e._timeout=setTimeout(()=>{"show"===e._hoverState&&e.show()},e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?"focus":"hover"]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState="out",e._config.delay&&e._config.delay.hide?e._timeout=setTimeout(()=>{"out"===e._hoverState&&e.hide()},e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=V.getDataAttributes(this._element);return Object.keys(e).forEach(t=>{Nt.has(t)&&delete e[t]}),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:h(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),d("tooltip",t,this.constructor.DefaultType),t.sanitize&&(t.template=Dt(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};if(this._config)for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(It);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}static jQueryInterface(t){return this.each((function(){const e=jt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}y(jt);const Ht=new RegExp("(^|\\s)bs-popover\\S+","g"),Rt={...jt.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},Bt={...jt.DefaultType,content:"(string|element|function)"},$t={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class Wt extends jt{static get Default(){return Rt}static get NAME(){return"popover"}static get Event(){return $t}static get DefaultType(){return Bt}isWithContent(){return this.getTitle()||this._getContent()}getTipElement(){return this.tip||(this.tip=super.getTipElement(),this.getTitle()||i.findOne(".popover-header",this.tip).remove(),this._getContent()||i.findOne(".popover-body",this.tip).remove()),this.tip}setContent(){const t=this.getTipElement();this.setElementContent(i.findOne(".popover-header",t),this.getTitle());let e=this._getContent();"function"==typeof e&&(e=e.call(this._element)),this.setElementContent(i.findOne(".popover-body",t),e),t.classList.remove("fade","show")}_addAttachmentClass(t){this.getTipElement().classList.add("bs-popover-"+this.updateAttachment(t))}_getContent(){return this._element.getAttribute("data-bs-content")||this._config.content}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(Ht);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}static jQueryInterface(t){return this.each((function(){const e=Wt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}y(Wt);const qt={offset:10,method:"auto",target:""},zt={offset:"number",method:"string",target:"(string|element)"};class Ft extends q{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._selector=`${this._config.target} .nav-link, ${this._config.target} .list-group-item, ${this._config.target} .dropdown-item`,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,B.on(this._scrollElement,"scroll.bs.scrollspy",()=>this._process()),this.refresh(),this._process()}static get Default(){return qt}static get NAME(){return"scrollspy"}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":"position",e="auto"===this._config.method?t:this._config.method,s="position"===e?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),i.find(this._selector).map(t=>{const n=r(t),o=n?i.findOne(n):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[V[e](o).top+s,n]}return null}).filter(t=>t).sort((t,e)=>t[0]-e[0]).forEach(t=>{this._offsets.push(t[0]),this._targets.push(t[1])})}dispose(){B.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){if("string"!=typeof(t={...qt,...V.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target&&c(t.target)){let{id:e}=t.target;e||(e=n("scrollspy"),t.target.id=e),t.target="#"+e}return d("scrollspy",t,zt),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),s=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=s){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`),s=i.findOne(e.join(","));s.classList.contains("dropdown-item")?(i.findOne(".dropdown-toggle",s.closest(".dropdown")).classList.add("active"),s.classList.add("active")):(s.classList.add("active"),i.parents(s,".nav, .list-group").forEach(t=>{i.prev(t,".nav-link, .list-group-item").forEach(t=>t.classList.add("active")),i.prev(t,".nav-item").forEach(t=>{i.children(t,".nav-link").forEach(t=>t.classList.add("active"))})})),B.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){i.find(this._selector).filter(t=>t.classList.contains("active")).forEach(t=>t.classList.remove("active"))}static jQueryInterface(t){return this.each((function(){const e=Ft.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}B.on(window,"load.bs.scrollspy.data-api",()=>{i.find('[data-bs-spy="scroll"]').forEach(t=>new Ft(t))}),y(Ft);class Ut extends q{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains("active"))return;let t;const e=a(this._element),s=this._element.closest(".nav, .list-group");if(s){const e="UL"===s.nodeName||"OL"===s.nodeName?":scope > li > .active":".active";t=i.find(e,s),t=t[t.length-1]}const n=t?B.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(B.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==n&&n.defaultPrevented)return;this._activate(this._element,s);const o=()=>{B.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),B.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,s){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?i.children(e,".active"):i.find(":scope > li > .active",e))[0],o=s&&n&&n.classList.contains("fade"),r=()=>this._transitionComplete(t,n,s);n&&o?(n.classList.remove("show"),this._queueCallback(r,t,!0)):r()}_transitionComplete(t,e,s){if(e){e.classList.remove("active");const t=i.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove("active"),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add("active"),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),m(t),t.classList.contains("fade")&&t.classList.add("show");let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&i.find(".dropdown-toggle",e).forEach(t=>t.classList.add("active")),t.setAttribute("aria-expanded",!0)}s&&s()}static jQueryInterface(t){return this.each((function(){const e=Ut.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}B.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),g(this)||Ut.getOrCreateInstance(this).show()})),y(Ut);const Kt={animation:"boolean",autohide:"boolean",delay:"number"},Vt={animation:!0,autohide:!0,delay:5e3};class Qt extends q{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Kt}static get Default(){return Vt}static get NAME(){return"toast"}show(){B.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove("hide"),m(this._element),this._element.classList.add("showing"),this._queueCallback(()=>{this._element.classList.remove("showing"),this._element.classList.add("show"),B.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){this._element.classList.contains("show")&&(B.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.remove("show"),this._queueCallback(()=>{this._element.classList.add("hide"),B.trigger(this._element,"hidden.bs.toast")},this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),super.dispose()}_getConfig(t){return t={...Vt,...V.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},d("toast",t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const s=t.relatedTarget;this._element===s||this._element.contains(s)||this._maybeScheduleHide()}_setListeners(){B.on(this._element,"click.dismiss.bs.toast",'[data-bs-dismiss="toast"]',()=>this.hide()),B.on(this._element,"mouseover.bs.toast",t=>this._onInteraction(t,!0)),B.on(this._element,"mouseout.bs.toast",t=>this._onInteraction(t,!1)),B.on(this._element,"focusin.bs.toast",t=>this._onInteraction(t,!0)),B.on(this._element,"focusout.bs.toast",t=>this._onInteraction(t,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Qt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return y(Qt),{Alert:z,Button:F,Carousel:et,Collapse:nt,Dropdown:pt,Modal:wt,Offcanvas:Tt,Popover:Wt,ScrollSpy:Ft,Tab:Ut,Toast:Qt,Tooltip:jt}})); +//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/src/main/resources/static/js/thirdParty/popper.min.js b/src/main/resources/static/js/thirdParty/popper.min.js index 1fb2256d..23122ccc 100644 --- a/src/main/resources/static/js/thirdParty/popper.min.js +++ b/src/main/resources/static/js/thirdParty/popper.min.js @@ -1,6 +1,6 @@ /** - * @popperjs/core v2.11.6 - MIT License + * @popperjs/core v2.9.2 - MIT License */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(){var e=navigator.userAgentData;return null!=e&&e.brands?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function c(){return!/^((?!chrome|android).)*safari/i.test(f())}function p(e,o,i){void 0===o&&(o=!1),void 0===i&&(i=!1);var a=e.getBoundingClientRect(),f=1,p=1;o&&r(e)&&(f=e.offsetWidth>0&&s(a.width)/e.offsetWidth||1,p=e.offsetHeight>0&&s(a.height)/e.offsetHeight||1);var u=(n(e)?t(e):window).visualViewport,l=!c()&&i,d=(a.left+(l&&u?u.offsetLeft:0))/f,h=(a.top+(l&&u?u.offsetTop:0))/p,m=a.width/f,v=a.height/p;return{width:m,height:v,top:h,right:d+m,bottom:h+v,left:d,x:d,y:h}}function u(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function l(e){return e?(e.nodeName||"").toLowerCase():null}function d(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function h(e){return p(d(e)).left+u(e).scrollLeft}function m(e){return t(e).getComputedStyle(e)}function v(e){var t=m(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function y(e,n,o){void 0===o&&(o=!1);var i,a,f=r(n),c=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),m=d(n),y=p(e,c,o),g={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(f||!f&&!o)&&(("body"!==l(n)||v(m))&&(g=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:u(i)),r(n)?((b=p(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):m&&(b.x=h(m))),{x:y.left+g.scrollLeft-b.x,y:y.top+g.scrollTop-b.y,width:y.width,height:y.height}}function g(e){var t=p(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function b(e){return"html"===l(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||d(e)}function w(e){return["html","body","#document"].indexOf(l(e))>=0?e.ownerDocument.body:r(e)&&v(e)?e:w(b(e))}function x(e,n){var r;void 0===n&&(n=[]);var o=w(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],v(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(x(b(s)))}function O(e){return["table","td","th"].indexOf(l(e))>=0}function j(e){return r(e)&&"fixed"!==m(e).position?e.offsetParent:null}function E(e){for(var n=t(e),i=j(e);i&&O(i)&&"static"===m(i).position;)i=j(i);return i&&("html"===l(i)||"body"===l(i)&&"static"===m(i).position)?n:i||function(e){var t=/firefox/i.test(f());if(/Trident/i.test(f())&&r(e)&&"fixed"===m(e).position)return null;var n=b(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(l(n))<0;){var i=m(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var D="top",A="bottom",L="right",P="left",M="auto",k=[D,A,L,P],W="start",B="end",H="viewport",T="popper",R=k.reduce((function(e,t){return e.concat([t+"-"+W,t+"-"+B])}),[]),S=[].concat(k,[M]).reduce((function(e,t){return e.concat([t,t+"-"+W,t+"-"+B])}),[]),V=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function q(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function N(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function I(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function _(e,r,o){return r===H?I(function(e,n){var r=t(e),o=d(e),i=r.visualViewport,a=o.clientWidth,s=o.clientHeight,f=0,p=0;if(i){a=i.width,s=i.height;var u=c();(u||!u&&"fixed"===n)&&(f=i.offsetLeft,p=i.offsetTop)}return{width:a,height:s,x:f+h(e),y:p}}(e,o)):n(r)?function(e,t){var n=p(e,!1,"fixed"===t);return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}(r,o):I(function(e){var t,n=d(e),r=u(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+h(e),c=-r.scrollTop;return"rtl"===m(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:c}}(d(e)))}function F(e,t,o,s){var f="clippingParents"===t?function(e){var t=x(b(e)),o=["absolute","fixed"].indexOf(m(e).position)>=0&&r(e)?E(e):e;return n(o)?t.filter((function(e){return n(e)&&N(e,o)&&"body"!==l(e)})):[]}(e):[].concat(t),c=[].concat(f,[o]),p=c[0],u=c.reduce((function(t,n){var r=_(e,n,s);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),_(e,p,s));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function U(e){return e.split("-")[1]}function z(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function X(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?U(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case D:t={x:s,y:n.y-r.height};break;case A:t={x:s,y:n.y+n.height};break;case L:t={x:n.x+n.width,y:f};break;case P:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?z(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case W:t[c]=t[c]-(n[p]/2-r[p]/2);break;case B:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function Y(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function G(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function J(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.strategy,s=void 0===a?e.strategy:a,f=r.boundary,c=void 0===f?"clippingParents":f,u=r.rootBoundary,l=void 0===u?H:u,h=r.elementContext,m=void 0===h?T:h,v=r.altBoundary,y=void 0!==v&&v,g=r.padding,b=void 0===g?0:g,w=Y("number"!=typeof b?b:G(b,k)),x=m===T?"reference":T,O=e.rects.popper,j=e.elements[y?x:m],E=F(n(j)?j:j.contextElement||d(e.elements.popper),c,l,s),P=p(e.elements.reference),M=X({reference:P,element:O,strategy:"absolute",placement:i}),W=I(Object.assign({},O,M)),B=m===T?W:P,R={top:E.top-B.top+w.top,bottom:B.bottom-E.bottom+w.bottom,left:E.left-B.left+w.left,right:B.right-E.right+w.right},S=e.modifiersData.offset;if(m===T&&S){var V=S[i];Object.keys(R).forEach((function(e){var t=[L,A].indexOf(e)>=0?1:-1,n=[D,A].indexOf(e)>=0?"y":"x";R[e]+=V[n]*t}))}return R}var K={placement:"bottom",modifiers:[],strategy:"absolute"};function Q(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[P,L].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},se={left:"right",right:"left",bottom:"top",top:"bottom"};function fe(e){return e.replace(/left|right|bottom|top/g,(function(e){return se[e]}))}var ce={start:"end",end:"start"};function pe(e){return e.replace(/start|end/g,(function(e){return ce[e]}))}function ue(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?S:f,p=U(r),u=p?s?R:R.filter((function(e){return U(e)===p})):k,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=J(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var le={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,y=C(v),g=f||(y===v||!h?[fe(v)]:function(e){if(C(e)===M)return[];var t=fe(e);return[pe(e),t,pe(t)]}(v)),b=[v].concat(g).reduce((function(e,n){return e.concat(C(n)===M?ue(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),w=t.rects.reference,x=t.rects.popper,O=new Map,j=!0,E=b[0],k=0;k=0,S=R?"width":"height",V=J(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),q=R?T?L:P:T?A:D;w[S]>x[S]&&(q=fe(q));var N=fe(q),I=[];if(i&&I.push(V[H]<=0),s&&I.push(V[q]<=0,V[N]<=0),I.every((function(e){return e}))){E=B,j=!1;break}O.set(B,I)}if(j)for(var _=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return E=t,"break"},F=h?3:1;F>0;F--){if("break"===_(F))break}t.placement!==E&&(t.modifiersData[r]._skip=!0,t.placement=E,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function de(e,t,n){return i(e,a(t,n))}var he={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,v=n.tetherOffset,y=void 0===v?0:v,b=J(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),w=C(t.placement),x=U(t.placement),O=!x,j=z(w),M="x"===j?"y":"x",k=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,V={x:0,y:0};if(k){if(s){var q,N="y"===j?D:P,I="y"===j?A:L,_="y"===j?"height":"width",F=k[j],X=F+b[N],Y=F-b[I],G=m?-H[_]/2:0,K=x===W?B[_]:H[_],Q=x===W?-H[_]:-B[_],Z=t.elements.arrow,$=m&&Z?g(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=de(0,B[_],$[_]),oe=O?B[_]/2-G-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=O?-B[_]/2+G+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&E(t.elements.arrow),se=ae?"y"===j?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(q=null==S?void 0:S[j])?q:0,ce=F+ie-fe,pe=de(m?a(X,F+oe-fe-se):X,F,m?i(Y,ce):Y);k[j]=pe,V[j]=pe-F}if(c){var ue,le="x"===j?D:P,he="x"===j?A:L,me=k[M],ve="y"===M?"height":"width",ye=me+b[le],ge=me-b[he],be=-1!==[D,P].indexOf(w),we=null!=(ue=null==S?void 0:S[M])?ue:0,xe=be?ye:me-B[ve]-H[ve]-we+R.altAxis,Oe=be?me+B[ve]+H[ve]-we-R.altAxis:ge,je=m&&be?function(e,t,n){var r=de(e,t,n);return r>n?n:r}(xe,me,Oe):de(m?xe:ye,me,m?Oe:ge);k[M]=je,V[M]=je-me}t.modifiersData[r]=V}},requiresIfExists:["offset"]};var me={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=z(s),c=[P,L].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return Y("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:G(e,k))}(o.padding,n),u=g(i),l="y"===f?D:P,d="y"===f?A:L,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],v=E(i),y=v?"y"===f?v.clientHeight||0:v.clientWidth||0:0,b=h/2-m/2,w=p[l],x=y-u[c]-p[d],O=y/2-u[c]/2+b,j=de(w,O,x),M=f;n.modifiersData[r]=((t={})[M]=j,t.centerOffset=j-O,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&N(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ve(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function ye(e){return[D,L,A,P].some((function(t){return e[t]>=0}))}var ge={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=J(t,{elementContext:"reference"}),s=J(t,{altBoundary:!0}),f=ve(a,r),c=ve(s,o,i),p=ye(f),u=ye(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},be=Z({defaultModifiers:[ee,te,oe,ie]}),we=[ee,te,oe,ie,ae,le,he,me,ge],xe=Z({defaultModifiers:we});e.applyStyles=ie,e.arrow=me,e.computeStyles=oe,e.createPopper=xe,e.createPopperLite=be,e.defaultModifiers=we,e.detectOverflow=J,e.eventListeners=ee,e.flip=le,e.hide=ge,e.offset=ae,e.popperGenerator=Z,e.popperOffsets=te,e.preventOverflow=he,Object.defineProperty(e,"__esModule",{value:!0})})); +"use strict";!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){function t(e){return{width:(e=e.getBoundingClientRect()).width,height:e.height,top:e.top,right:e.right,bottom:e.bottom,left:e.left,x:e.left,y:e.top}}function n(e){return null==e?window:"[object Window]"!==e.toString()?(e=e.ownerDocument)&&e.defaultView||window:e}function o(e){return{scrollLeft:(e=n(e)).pageXOffset,scrollTop:e.pageYOffset}}function r(e){return e instanceof n(e).Element||e instanceof Element}function i(e){return e instanceof n(e).HTMLElement||e instanceof HTMLElement}function a(e){return"undefined"!=typeof ShadowRoot&&(e instanceof n(e).ShadowRoot||e instanceof ShadowRoot)}function s(e){return e?(e.nodeName||"").toLowerCase():null}function f(e){return((r(e)?e.ownerDocument:e.document)||window.document).documentElement}function p(e){return t(f(e)).left+o(e).scrollLeft}function c(e){return n(e).getComputedStyle(e)}function l(e){return e=c(e),/auto|scroll|overlay|hidden/.test(e.overflow+e.overflowY+e.overflowX)}function u(e,r,a){void 0===a&&(a=!1);var c=f(r);e=t(e);var u=i(r),d={scrollLeft:0,scrollTop:0},m={x:0,y:0};return(u||!u&&!a)&&(("body"!==s(r)||l(c))&&(d=r!==n(r)&&i(r)?{scrollLeft:r.scrollLeft,scrollTop:r.scrollTop}:o(r)),i(r)?((m=t(r)).x+=r.clientLeft,m.y+=r.clientTop):c&&(m.x=p(c))),{x:e.left+d.scrollLeft-m.x,y:e.top+d.scrollTop-m.y,width:e.width,height:e.height}}function d(e){var n=t(e),o=e.offsetWidth,r=e.offsetHeight;return 1>=Math.abs(n.width-o)&&(o=n.width),1>=Math.abs(n.height-r)&&(r=n.height),{x:e.offsetLeft,y:e.offsetTop,width:o,height:r}}function m(e){return"html"===s(e)?e:e.assignedSlot||e.parentNode||(a(e)?e.host:null)||f(e)}function h(e){return 0<=["html","body","#document"].indexOf(s(e))?e.ownerDocument.body:i(e)&&l(e)?e:h(m(e))}function v(e,t){var o;void 0===t&&(t=[]);var r=h(e);return e=r===(null==(o=e.ownerDocument)?void 0:o.body),o=n(r),r=e?[o].concat(o.visualViewport||[],l(r)?r:[]):r,t=t.concat(r),e?t:t.concat(v(m(r)))}function g(e){return i(e)&&"fixed"!==c(e).position?e.offsetParent:null}function y(e){for(var t=n(e),o=g(e);o&&0<=["table","td","th"].indexOf(s(o))&&"static"===c(o).position;)o=g(o);if(o&&("html"===s(o)||"body"===s(o)&&"static"===c(o).position))return t;if(!o)e:{if(o=-1!==navigator.userAgent.toLowerCase().indexOf("firefox"),-1===navigator.userAgent.indexOf("Trident")||!i(e)||"fixed"!==c(e).position)for(e=m(e);i(e)&&0>["html","body"].indexOf(s(e));){var r=c(e);if("none"!==r.transform||"none"!==r.perspective||"paint"===r.contain||-1!==["transform","perspective"].indexOf(r.willChange)||o&&"filter"===r.willChange||o&&r.filter&&"none"!==r.filter){o=e;break e}e=e.parentNode}o=null}return o||t}function b(e){function t(e){o.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){o.has(e)||(e=n.get(e))&&t(e)})),r.push(e)}var n=new Map,o=new Set,r=[];return e.forEach((function(e){n.set(e.name,e)})),e.forEach((function(e){o.has(e.name)||t(e)})),r}function w(e){var t;return function(){return t||(t=new Promise((function(n){Promise.resolve().then((function(){t=void 0,n(e())}))}))),t}}function x(e){return e.split("-")[0]}function O(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&a(n))do{if(t&&e.isSameNode(t))return!0;t=t.parentNode||t.host}while(t);return!1}function j(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function E(e,r){if("viewport"===r){r=n(e);var a=f(e);r=r.visualViewport;var s=a.clientWidth;a=a.clientHeight;var l=0,u=0;r&&(s=r.width,a=r.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(l=r.offsetLeft,u=r.offsetTop)),e=j(e={width:s,height:a,x:l+p(e),y:u})}else i(r)?((e=t(r)).top+=r.clientTop,e.left+=r.clientLeft,e.bottom=e.top+r.clientHeight,e.right=e.left+r.clientWidth,e.width=r.clientWidth,e.height=r.clientHeight,e.x=e.left,e.y=e.top):(u=f(e),e=f(u),s=o(u),r=null==(a=u.ownerDocument)?void 0:a.body,a=_(e.scrollWidth,e.clientWidth,r?r.scrollWidth:0,r?r.clientWidth:0),l=_(e.scrollHeight,e.clientHeight,r?r.scrollHeight:0,r?r.clientHeight:0),u=-s.scrollLeft+p(u),s=-s.scrollTop,"rtl"===c(r||e).direction&&(u+=_(e.clientWidth,r?r.clientWidth:0)-a),e=j({width:a,height:l,x:u,y:s}));return e}function D(e,t,n){return t="clippingParents"===t?function(e){var t=v(m(e)),n=0<=["absolute","fixed"].indexOf(c(e).position)&&i(e)?y(e):e;return r(n)?t.filter((function(e){return r(e)&&O(e,n)&&"body"!==s(e)})):[]}(e):[].concat(t),(n=(n=[].concat(t,[n])).reduce((function(t,n){return n=E(e,n),t.top=_(n.top,t.top),t.right=U(n.right,t.right),t.bottom=U(n.bottom,t.bottom),t.left=_(n.left,t.left),t}),E(e,n[0]))).width=n.right-n.left,n.height=n.bottom-n.top,n.x=n.left,n.y=n.top,n}function L(e){return 0<=["top","bottom"].indexOf(e)?"x":"y"}function P(e){var t=e.reference,n=e.element,o=(e=e.placement)?x(e):null;e=e?e.split("-")[1]:null;var r=t.x+t.width/2-n.width/2,i=t.y+t.height/2-n.height/2;switch(o){case"top":r={x:r,y:t.y-n.height};break;case"bottom":r={x:r,y:t.y+t.height};break;case"right":r={x:t.x+t.width,y:i};break;case"left":r={x:t.x-n.width,y:i};break;default:r={x:t.x,y:t.y}}if(null!=(o=o?L(o):null))switch(i="y"===o?"height":"width",e){case"start":r[o]-=t[i]/2-n[i]/2;break;case"end":r[o]+=t[i]/2-n[i]/2}return r}function M(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function k(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function A(e,n){void 0===n&&(n={});var o=n;n=void 0===(n=o.placement)?e.placement:n;var i=o.boundary,a=void 0===i?"clippingParents":i,s=void 0===(i=o.rootBoundary)?"viewport":i;i=void 0===(i=o.elementContext)?"popper":i;var p=o.altBoundary,c=void 0!==p&&p;o=M("number"!=typeof(o=void 0===(o=o.padding)?0:o)?o:k(o,C));var l=e.elements.reference;p=e.rects.popper,a=D(r(c=e.elements[c?"popper"===i?"reference":"popper":i])?c:c.contextElement||f(e.elements.popper),a,s),c=P({reference:s=t(l),element:p,strategy:"absolute",placement:n}),p=j(Object.assign({},p,c)),s="popper"===i?p:s;var u={top:a.top-s.top+o.top,bottom:s.bottom-a.bottom+o.bottom,left:a.left-s.left+o.left,right:s.right-a.right+o.right};if(e=e.modifiersData.offset,"popper"===i&&e){var d=e[n];Object.keys(u).forEach((function(e){var t=0<=["right","bottom"].indexOf(e)?1:-1,n=0<=["top","bottom"].indexOf(e)?"y":"x";u[e]+=d[n]*t}))}return u}function W(){for(var e=arguments.length,t=Array(e),n=0;n(g.devicePixelRatio||1)?"translate("+e+"px, "+u+"px)":"translate3d("+e+"px, "+u+"px, 0)",m)):Object.assign({},o,((t={})[v]=a?u+"px":"",t[h]=d?e+"px":"",t.transform="",t))}function H(e){return e.replace(/left|right|bottom|top/g,(function(e){return $[e]}))}function R(e){return e.replace(/start|end/g,(function(e){return ee[e]}))}function S(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function q(e){return["top","right","bottom","left"].some((function(t){return 0<=e[t]}))}var C=["top","bottom","right","left"],N=C.reduce((function(e,t){return e.concat([t+"-start",t+"-end"])}),[]),V=[].concat(C,["auto"]).reduce((function(e,t){return e.concat([t,t+"-start",t+"-end"])}),[]),I="beforeRead read afterRead beforeMain main afterMain beforeWrite write afterWrite".split(" "),_=Math.max,U=Math.min,z=Math.round,F={placement:"bottom",modifiers:[],strategy:"absolute"},X={passive:!0},Y={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(e){var t=e.state,o=e.instance,r=(e=e.options).scroll,i=void 0===r||r,a=void 0===(e=e.resize)||e,s=n(t.elements.popper),f=[].concat(t.scrollParents.reference,t.scrollParents.popper);return i&&f.forEach((function(e){e.addEventListener("scroll",o.update,X)})),a&&s.addEventListener("resize",o.update,X),function(){i&&f.forEach((function(e){e.removeEventListener("scroll",o.update,X)})),a&&s.removeEventListener("resize",o.update,X)}},data:{}},G={name:"popperOffsets",enabled:!0,phase:"read",fn:function(e){var t=e.state;t.modifiersData[e.name]=P({reference:t.rects.reference,element:t.rects.popper,strategy:"absolute",placement:t.placement})},data:{}},J={top:"auto",right:"auto",bottom:"auto",left:"auto"},K={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(e){var t=e.state,n=e.options;e=void 0===(e=n.gpuAcceleration)||e;var o=n.adaptive;o=void 0===o||o,n=void 0===(n=n.roundOffsets)||n,e={placement:x(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:e},null!=t.modifiersData.popperOffsets&&(t.styles.popper=Object.assign({},t.styles.popper,T(Object.assign({},e,{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:o,roundOffsets:n})))),null!=t.modifiersData.arrow&&(t.styles.arrow=Object.assign({},t.styles.arrow,T(Object.assign({},e,{offsets:t.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:n})))),t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-placement":t.placement})},data:{}},Q={name:"applyStyles",enabled:!0,phase:"write",fn:function(e){var t=e.state;Object.keys(t.elements).forEach((function(e){var n=t.styles[e]||{},o=t.attributes[e]||{},r=t.elements[e];i(r)&&s(r)&&(Object.assign(r.style,n),Object.keys(o).forEach((function(e){var t=o[e];!1===t?r.removeAttribute(e):r.setAttribute(e,!0===t?"":t)})))}))},effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow),function(){Object.keys(t.elements).forEach((function(e){var o=t.elements[e],r=t.attributes[e]||{};e=Object.keys(t.styles.hasOwnProperty(e)?t.styles[e]:n[e]).reduce((function(e,t){return e[t]="",e}),{}),i(o)&&s(o)&&(Object.assign(o.style,e),Object.keys(r).forEach((function(e){o.removeAttribute(e)})))}))}},requires:["computeStyles"]},Z={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(e){var t=e.state,n=e.name,o=void 0===(e=e.options.offset)?[0,0]:e,r=(e=V.reduce((function(e,n){var r=t.rects,i=x(n),a=0<=["left","top"].indexOf(i)?-1:1,s="function"==typeof o?o(Object.assign({},r,{placement:n})):o;return r=(r=s[0])||0,s=((s=s[1])||0)*a,i=0<=["left","right"].indexOf(i)?{x:s,y:r}:{x:r,y:s},e[n]=i,e}),{}))[t.placement],i=r.x;r=r.y,null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=i,t.modifiersData.popperOffsets.y+=r),t.modifiersData[n]=e}},$={left:"right",right:"left",bottom:"top",top:"bottom"},ee={start:"end",end:"start"},te={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options;if(e=e.name,!t.modifiersData[e]._skip){var o=n.mainAxis;o=void 0===o||o;var r=n.altAxis;r=void 0===r||r;var i=n.fallbackPlacements,a=n.padding,s=n.boundary,f=n.rootBoundary,p=n.altBoundary,c=n.flipVariations,l=void 0===c||c,u=n.allowedAutoPlacements;c=x(n=t.options.placement),i=i||(c!==n&&l?function(e){if("auto"===x(e))return[];var t=H(e);return[R(e),t,R(t)]}(n):[H(n)]);var d=[n].concat(i).reduce((function(e,n){return e.concat("auto"===x(n)?function(e,t){void 0===t&&(t={});var n=t.boundary,o=t.rootBoundary,r=t.padding,i=t.flipVariations,a=t.allowedAutoPlacements,s=void 0===a?V:a,f=t.placement.split("-")[1];0===(i=(t=f?i?N:N.filter((function(e){return e.split("-")[1]===f})):C).filter((function(e){return 0<=s.indexOf(e)}))).length&&(i=t);var p=i.reduce((function(t,i){return t[i]=A(e,{placement:i,boundary:n,rootBoundary:o,padding:r})[x(i)],t}),{});return Object.keys(p).sort((function(e,t){return p[e]-p[t]}))}(t,{placement:n,boundary:s,rootBoundary:f,padding:a,flipVariations:l,allowedAutoPlacements:u}):n)}),[]);n=t.rects.reference,i=t.rects.popper;var m=new Map;c=!0;for(var h=d[0],v=0;vi[O]&&(b=H(b)),O=H(b),w=[],o&&w.push(0>=j[y]),r&&w.push(0>=j[b],0>=j[O]),w.every((function(e){return e}))){h=g,c=!1;break}m.set(g,w)}if(c)for(o=function(e){var t=d.find((function(t){if(t=m.get(t))return t.slice(0,e).every((function(e){return e}))}));if(t)return h=t,"break"},r=l?3:1;0 + + + + + + +
+
+
+

+
+
+
+ + +

User Settings

+
+
+ Default message if not found +
+
+ Default message if not found +
+
+ Default message if not found +
+
+ Default message if not found +
+ + + + + + + +

User!

+ + + +

+
+
+ + +
+
+ + +
+
+ +
+
+ +
+ + +

Change Password?

+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+ +
+
+ +
+
+
+ +
+ + + + + +
+
+
+
+ + + + +
+ +

Sync browser settings with Account

+
+

Settings Comparison:

+ + + + + + + + + + + +
PropertyAccount SettingWeb Browser Setting
+ +
+ + +
+
+ + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+ + diff --git a/src/main/resources/templates/addUsers.html b/src/main/resources/templates/addUsers.html new file mode 100644 index 00000000..4ab59a8b --- /dev/null +++ b/src/main/resources/templates/addUsers.html @@ -0,0 +1,83 @@ + + + + + + + +
+
+
+

+
+
+
+ + +

Admin User Control Settings

+ + + + + + + + + + + + + + + + + + + +
UsernameRolesActions
+
+ +
+
+ + + +

Add New User

+
+ Default message if not found +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + +
+
+
+
+ +
+
+
+ + diff --git a/src/main/resources/templates/auto-split-pdf.html b/src/main/resources/templates/auto-split-pdf.html index d6f68fe1..4cc39c99 100644 --- a/src/main/resources/templates/auto-split-pdf.html +++ b/src/main/resources/templates/auto-split-pdf.html @@ -22,12 +22,12 @@
  • -
    +

    - +

    diff --git a/src/main/resources/templates/change-creds.html b/src/main/resources/templates/change-creds.html new file mode 100644 index 00000000..94b87b64 --- /dev/null +++ b/src/main/resources/templates/change-creds.html @@ -0,0 +1,90 @@ + + + + + + + +
    +
    +
    +

    +
    +
    +
    + + +

    User Settings

    +
    + +
    + Default message if not found +
    +
    + Default message if not found +
    +
    + Default message if not found +
    +
    + Default message if not found +
    + + +

    User!

    + + + +

    +

    Change Username and password

    + +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    + + + +
    + + + + + +
    +
    + +
    +
    +
    + + diff --git a/src/main/resources/templates/convert/file-to-pdf.html b/src/main/resources/templates/convert/file-to-pdf.html index f90572f0..fe56c164 100644 --- a/src/main/resources/templates/convert/file-to-pdf.html +++ b/src/main/resources/templates/convert/file-to-pdf.html @@ -15,7 +15,7 @@

    -

    +

    diff --git a/src/main/resources/templates/convert/html-to-pdf.html b/src/main/resources/templates/convert/html-to-pdf.html index 7a384d19..546ef1fc 100644 --- a/src/main/resources/templates/convert/html-to-pdf.html +++ b/src/main/resources/templates/convert/html-to-pdf.html @@ -12,7 +12,7 @@

    - +

    diff --git a/src/main/resources/templates/convert/img-to-pdf.html b/src/main/resources/templates/convert/img-to-pdf.html index 3182eb9d..ae38143c 100644 --- a/src/main/resources/templates/convert/img-to-pdf.html +++ b/src/main/resources/templates/convert/img-to-pdf.html @@ -15,21 +15,27 @@

    - +
    -
    - - -
    +
    + + +
    + +
    - +
    -
    +
    -
    +
    + + +
    -
    +
    -
    +
    -
    +
    @@ -56,4 +59,4 @@
    - \ No newline at end of file + diff --git a/src/main/resources/templates/convert/pdf-to-pdfa.html b/src/main/resources/templates/convert/pdf-to-pdfa.html index 6a5a17c6..dfddb792 100644 --- a/src/main/resources/templates/convert/pdf-to-pdfa.html +++ b/src/main/resources/templates/convert/pdf-to-pdfa.html @@ -15,7 +15,7 @@

    Currently doesn't work for multiple inputs at once

    - +

    diff --git a/src/main/resources/templates/convert/pdf-to-presentation.html b/src/main/resources/templates/convert/pdf-to-presentation.html index f5f73397..51a0440b 100644 --- a/src/main/resources/templates/convert/pdf-to-presentation.html +++ b/src/main/resources/templates/convert/pdf-to-presentation.html @@ -12,10 +12,10 @@

    - +
    -
    +
    diff --git a/src/main/resources/templates/convert/pdf-to-word.html b/src/main/resources/templates/convert/pdf-to-word.html index c183d1bc..d2531084 100644 --- a/src/main/resources/templates/convert/pdf-to-word.html +++ b/src/main/resources/templates/convert/pdf-to-word.html @@ -12,10 +12,10 @@

    - +
    -
    +

    diff --git a/src/main/resources/templates/crop.html b/src/main/resources/templates/crop.html index 911a8f32..b0347477 100644 --- a/src/main/resources/templates/crop.html +++ b/src/main/resources/templates/crop.html @@ -13,7 +13,7 @@

    - +
    diff --git a/src/main/resources/templates/extract-page.html b/src/main/resources/templates/extract-page.html index 9dd1b9ed..d8ae7fb5 100644 --- a/src/main/resources/templates/extract-page.html +++ b/src/main/resources/templates/extract-page.html @@ -13,12 +13,12 @@

    - +
    -
    +
    - +
    diff --git a/src/main/resources/templates/fragments/card.html b/src/main/resources/templates/fragments/card.html index 48142b03..c3583da6 100644 --- a/src/main/resources/templates/fragments/card.html +++ b/src/main/resources/templates/fragments/card.html @@ -1,8 +1,8 @@ -
    +
    Icon -
    +

    diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html index 74536458..36ee712a 100644 --- a/src/main/resources/templates/fragments/common.html +++ b/src/main/resources/templates/fragments/common.html @@ -35,7 +35,6 @@ - @@ -52,12 +51,10 @@ - - - + -
    -
    - - -
    -
    -
    +
    +
    + +
    +
    +
    + diff --git a/src/main/resources/templates/fragments/errorBanner.html b/src/main/resources/templates/fragments/errorBanner.html index 98fd9aeb..cdba311a 100644 --- a/src/main/resources/templates/fragments/errorBanner.html +++ b/src/main/resources/templates/fragments/errorBanner.html @@ -24,9 +24,7 @@

    - +
    diff --git a/src/main/resources/templates/fragments/errorBannerPerPage.html b/src/main/resources/templates/fragments/errorBannerPerPage.html index e1365513..ccc2f7b4 100644 --- a/src/main/resources/templates/fragments/errorBannerPerPage.html +++ b/src/main/resources/templates/fragments/errorBannerPerPage.html @@ -7,7 +7,7 @@ - @@ -29,7 +29,7 @@ Go to Homepage - Close + Close
    diff --git a/src/main/resources/templates/fragments/languages.html b/src/main/resources/templates/fragments/languages.html new file mode 100644 index 00000000..1ebed837 --- /dev/null +++ b/src/main/resources/templates/fragments/languages.html @@ -0,0 +1,62 @@ + + + icon Български + + + icon العربية + + + icon Català + + + icon 简体中文 + + + icon Deutsch + + + icon English (GB) + + + icon English (US) + + + icon Euskara + + + icon Español + + + icon Français + + + icon Italiano + + + icon Nederlands + + + icon Polski + + + icon Português (BR) + + + icon Romanian + + + icon Svenska + + + icon Русский + + + icon 한국어 + + + icon 日本語 + + + icon Ελληνικά + + diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index 89215fcb..4326afcc 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -5,7 +5,7 @@ const currentVersion = /*[[${@appVersion}]]*/ ''; const noFavourites = /*[[#{noFavourites}]]*/ ''; - + @@ -17,15 +17,15 @@ - - - - + - +
    - +