Compare commits

..

1 Commits

Author SHA1 Message Date
Anthony Stirling
ca8295b02e Create sonarqube.yml 2025-02-03 15:06:44 +00:00
58 changed files with 11871 additions and 639 deletions

View File

@@ -37,6 +37,12 @@ jobs:
java-version: ${{ matrix.jdk-version }} java-version: ${{ matrix.jdk-version }}
distribution: "temurin" distribution: "temurin"
- name: PR | Generate verification metadata with signatures and checksums for dependabot[bot]
if: github.event.pull_request.user.login == 'dependabot[bot]'
run: |
./gradlew clean dependencies buildEnvironment spotlessApply --write-verification-metadata sha256 --refresh-dependencies help
./gradlew clean dependencies buildEnvironment spotlessApply --write-verification-metadata sha256,pgp --refresh-keys --export-keys --refresh-dependencies help
- name: Build with Gradle and no spring security - name: Build with Gradle and no spring security
run: ./gradlew clean build run: ./gradlew clean build
env: env:
@@ -141,4 +147,4 @@ jobs:
run: | run: |
chmod +x ./testing/test_webpages.sh chmod +x ./testing/test_webpages.sh
chmod +x ./testing/test.sh chmod +x ./testing/test.sh
./testing/test.sh ./testing/test.sh "${{ github.event.pull_request.user.login == 'dependabot[bot]' }}"

View File

@@ -38,7 +38,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "adopt" distribution: "adopt"
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0 - uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
- name: check the licenses for compatibility - name: check the licenses for compatibility
run: ./gradlew clean checkLicense run: ./gradlew clean checkLicense

View File

@@ -63,7 +63,7 @@ jobs:
java-version: "21" java-version: "21"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0 - uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with: with:
gradle-version: 8.12 gradle-version: 8.12
@@ -151,7 +151,7 @@ jobs:
java-version: "21" java-version: "21"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0 - uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with: with:
gradle-version: 8.12 gradle-version: 8.12

View File

@@ -30,7 +30,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0 - uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with: with:
gradle-version: 8.12 gradle-version: 8.12

View File

@@ -35,7 +35,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0 - uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with: with:
gradle-version: 8.12 gradle-version: 8.12

View File

@@ -8,57 +8,30 @@ on:
permissions: permissions:
pull-requests: read pull-requests: read
actions: read
name: Run Sonarqube name: Run Sonarqube
jobs: jobs:
sonarqube: sonarqube:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Analyze with SonarCloud
# You can pin the exact commit or the version.
- name: Harden Runner # uses: SonarSource/sonarcloud-github-action@v2.2.0
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 uses: SonarSource/sonarcloud-github-action@4006f663ecaf1f8093e8e4abb9227f6041f52216 #v2.2.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
with:
java-version: '17'
distribution: 'temurin'
- name: Build and analyze with Gradle
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret)
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
DOCKER_ENABLE_SECURITY: true
STIRLING_PDF_DESKTOP_UI: true
run: |
./gradlew clean build sonar \
-Dsonar.projectKey=Stirling-Tools_Stirling-PDF \
-Dsonar.organization=stirling-tools \
-Dsonar.host.url=https://sonarcloud.io \
-Dsonar.log.level=DEBUG \
--info
- name: Upload Problems Report on Failure
if: failure()
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with: with:
name: gradle-problems-report # Additional arguments for the SonarScanner CLI
path: build/reports/problems/problems-report.html args:
retention-days: 7 # Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu)
# mandatory
- name: Upload Sonar Logs on Failure -Dsonar.projectKey=Stirling-Tools_Stirling-PDF
if: failure() -Dsonar.organization=stirling-tools
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 # Comma-separated paths to directories containing main source files.
with: #-Dsonar.sources= # optional, default is project base directory
name: sonar-logs # Comma-separated paths to directories containing test source files.
path: | #-Dsonar.tests= # optional. For more info about Code Coverage, please refer to https://docs.sonarcloud.io/enriching/test-coverage/overview/
.scannerwork/report-task.txt # Adds more detail to both client and server-side analysis logs, activating DEBUG mode for the scanner, and adding client-side environment variables and system properties to the server-side log of analysis report processing.
build/sonar/ #-Dsonar.verbose= # optional, default is false
retention-days: 7 # When you need the analysis to take place in a directory other than the one from which it was launched, default is .
projectBaseDir: .

View File

@@ -26,7 +26,7 @@ jobs:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
- uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0 - uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
- name: Generate Swagger documentation - name: Generate Swagger documentation
run: ./gradlew generateOpenApiDocs run: ./gradlew generateOpenApiDocs

View File

@@ -8,6 +8,8 @@ on:
paths: paths:
- "build.gradle" - "build.gradle"
- "README.md" - "README.md"
- "gradle/verification-keyring.keys"
- "gradle/verification-metadata.xml"
- "src/main/resources/messages_*.properties" - "src/main/resources/messages_*.properties"
- "src/main/resources/static/3rdPartyLicenses.json" - "src/main/resources/static/3rdPartyLicenses.json"
- "scripts/ignore_translation.toml" - "scripts/ignore_translation.toml"
@@ -102,6 +104,22 @@ jobs:
git add README.md git add README.md
git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "no changes" git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "no changes"
- name: Generate verification metadata with signatures and checksums
run: |
set -e
if [ -f ./gradle/verification-metadata.xml ]; then
rm ./gradle/verification-metadata.xml
fi
./gradlew clean dependencies buildEnvironment spotlessApply --write-verification-metadata sha256 help
./gradlew clean dependencies buildEnvironment spotlessApply --write-verification-metadata sha256,pgp --refresh-keys --export-keys --refresh-dependencies help
./gradlew clean build
- name: Run git add
run: |
git add gradle/verification-keyring.keys
git add gradle/verification-metadata.xml
git diff --staged --quiet || git commit -m ":memo: Generate verification metadata with signatures and checksums" || echo "no changes"
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6 uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
with: with:
@@ -111,11 +129,11 @@ jobs:
author: ${{ needs.read_bot_entries.outputs.committer }} author: ${{ needs.read_bot_entries.outputs.committer }}
signoff: true signoff: true
branch: sync_readme branch: sync_readme
title: ":globe_with_meridians: Sync Translations + Update README Progress Table" title: ":globe_with_meridians: Sync Translations + Update README Progress Table + Update Verification Metadata"
body: | body: |
### Description of Changes ### Description of Changes
This Pull Request was automatically generated to synchronize updates to translation files and documentation. Below are the details of the changes made: This Pull Request was automatically generated to synchronize updates to translation files, verification metadata, and documentation. Below are the details of the changes made:
#### **1. Synchronization of Translation Files** #### **1. Synchronization of Translation Files**
- Updated translation files (`messages_*.properties`) to reflect changes in the reference file `messages_en_GB.properties`. - Updated translation files (`messages_*.properties`) to reflect changes in the reference file `messages_en_GB.properties`.
@@ -127,9 +145,14 @@ jobs:
- Added a summary of the current translation status for all supported languages. - Added a summary of the current translation status for all supported languages.
- Included up-to-date statistics on translation coverage. - Included up-to-date statistics on translation coverage.
#### **3. Verification Metadata Updates**
- Generated or refreshed the `verification-keyring.keys` and `verification-metadata.xml` files.
- Included the latest dependency signatures and checksums to enhance the build's integrity.
#### **Why these changes are necessary** #### **Why these changes are necessary**
- Keeps translation files aligned with the latest reference updates. - Keeps translation files aligned with the latest reference updates.
- Ensures the documentation reflects the current translation progress. - Ensures the documentation reflects the current translation progress.
- Strengthens dependency verification for a more secure build process.
--- ---
@@ -143,3 +166,5 @@ jobs:
add-paths: | add-paths: |
README.md README.md
src/main/resources/messages_*.properties src/main/resources/messages_*.properties
gradle/verification-keyring.keys
gradle/verification-metadata.xml

View File

@@ -585,3 +585,41 @@ In your Thymeleaf templates, use the `#{key}` syntax to reference the new transl
``` ```
Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization. Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization.
## Managing Dependencies
When adding new dependencies or updating existing ones in Stirling-PDF, follow these steps to ensure proper verification and security:
1. Update the dependency in `build.gradle`:
```groovy
dependencies {
// Add or update your dependency
implementation "com.example:new-library:1.2.3"
}
```
2. Generate new verification metadata and keys:
```bash
# Generate verification metadata with signatures and checksums
./gradlew clean dependencies buildEnvironment spotlessApply --write-verification-metadata sha256,pgp
# Export the .keys file
./gradlew --export-keys
```
3. Files to commit:
- `build.gradle` - Your dependency changes
- `gradle/verification-metadata.xml` - Contains verification rules and checksums
- `gradle/verification-keyring.keys` - Contains PGP keys in text format
4. Verify the build works with the new verification:
```bash
./gradlew build
```
5. Before committing, check:
- Verify any new BOM files are properly handled in verification metadata
- Review the changes in `verification-metadata.xml` to ensure they match your dependency updates
This ensures dependencies are properly verified and secure while maintaining transparency in the repository.

View File

@@ -56,8 +56,6 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
openssl-dev \ openssl-dev \
openjdk21-jre \ openjdk21-jre \
# Doc conversion # Doc conversion
gcompat \
libc6-compat \
libreoffice \ libreoffice \
# pdftohtml # pdftohtml
poppler-utils \ poppler-utils \

View File

@@ -57,8 +57,6 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
openssl-dev \ openssl-dev \
openjdk21-jre \ openjdk21-jre \
# Doc conversion # Doc conversion
gcompat \
libc6-compat \
libreoffice \ libreoffice \
# pdftohtml # pdftohtml
poppler-utils \ poppler-utils \

View File

@@ -8,8 +8,7 @@ plugins {
id "com.diffplug.spotless" version "7.0.2" id "com.diffplug.spotless" version "7.0.2"
id "com.github.jk1.dependency-license-report" version "2.9" id "com.github.jk1.dependency-license-report" version "2.9"
//id "nebula.lint" version "19.0.3" //id "nebula.lint" version "19.0.3"
id("org.panteleyev.jpackageplugin") version "1.6.1" id("org.panteleyev.jpackageplugin") version "1.6.0"
id "org.sonarqube" version "6.0.1.5171"
} }
import com.github.jk1.license.render.* import com.github.jk1.license.render.*
@@ -26,7 +25,7 @@ ext {
} }
group = "stirling.software" group = "stirling.software"
version = "0.41.0" version = "0.40.1"
java { java {
// 17 is lowest but we support and recommend 21 // 17 is lowest but we support and recommend 21
@@ -35,6 +34,7 @@ java {
repositories { repositories {
mavenCentral() mavenCentral()
maven { url = "https://jitpack.io" }
maven { url = "https://build.shibboleth.net/maven/releases" } maven { url = "https://build.shibboleth.net/maven/releases" }
maven { url = "https://maven.pkg.github.com/jcefmaven/jcefmaven" } maven { url = "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
} }
@@ -269,17 +269,6 @@ spotless {
} }
} }
sonar {
properties {
property "sonar.projectKey", "Stirling-Tools_Stirling-PDF"
property "sonar.organization", "stirling-tools"
property "sonar.exclusions", "**/build-wrapper-dump.json, src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
property "sonar.coverage.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
property "sonar.cpd.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
}
}
//gradleLint { //gradleLint {
// rules=['unused-dependency'] // rules=['unused-dependency']
// } // }

BIN
docs/stirling-pdf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 145 KiB

View File

@@ -265,6 +265,9 @@ public class EndpointConfiguration {
// Pdftohtml dependent endpoints // Pdftohtml dependent endpoints
addEndpointToGroup("Pdftohtml", "pdf-to-html"); addEndpointToGroup("Pdftohtml", "pdf-to-html");
addEndpointToGroup("Pdftohtml", "pdf-to-markdown"); addEndpointToGroup("Pdftohtml", "pdf-to-markdown");
// disabled for now while we resolve issues
disableEndpoint("pdf-to-pdfa");
} }
private void processEnvironmentConfigs() { private void processEnvironmentConfigs() {

View File

@@ -32,10 +32,7 @@ public class AdditionalLanguageJsController {
response.setContentType("application/javascript"); response.setContentType("application/javascript");
PrintWriter writer = response.getWriter(); PrintWriter writer = response.getWriter();
// Erstelle das JavaScript dynamisch // Erstelle das JavaScript dynamisch
writer.println( writer.println("const supportedLanguages = " + toJsonArray(new ArrayList<>(supportedLanguages)) + ";");
"const supportedLanguages = "
+ toJsonArray(new ArrayList<>(supportedLanguages))
+ ";");
// Generiere die `getDetailedLanguageCode`-Funktion // Generiere die `getDetailedLanguageCode`-Funktion
writer.println( writer.println(
""" """

View File

@@ -13,8 +13,8 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest; import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -44,7 +44,7 @@ public class ConvertHtmlToPDF {
@Operation( @Operation(
summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF", summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF",
description = description =
"This endpoint takes an HTML or ZIP file input and converts it to a PDF format. Input:HTML Output:PDF Type:SISO") "This endpoint takes an HTML or ZIP file input and converts it to a PDF format.")
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute HTMLToPdfRequest request) public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute HTMLToPdfRequest request)
throws Exception { throws Exception {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
@@ -60,8 +60,7 @@ public class ConvertHtmlToPDF {
throw new IllegalArgumentException("File must be either .html or .zip format."); throw new IllegalArgumentException("File must be either .html or .zip format.");
} }
boolean disableSanitize = boolean disableSanitize = Boolean.TRUE.equals(applicationProperties.getSystem().getDisableSanitize());
Boolean.TRUE.equals(applicationProperties.getSystem().getDisableSanitize());
byte[] pdfBytes = byte[] pdfBytes =
FileToPdf.convertHtmlToPdf( FileToPdf.convertHtmlToPdf(

View File

@@ -23,8 +23,8 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.GeneralFile; import stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -81,8 +81,7 @@ public class ConvertMarkdownToPdf {
String htmlContent = renderer.render(document); String htmlContent = renderer.render(document);
boolean disableSanitize = boolean disableSanitize = Boolean.TRUE.equals(applicationProperties.getSystem().getDisableSanitize());
Boolean.TRUE.equals(applicationProperties.getSystem().getDisableSanitize());
byte[] pdfBytes = byte[] pdfBytes =
FileToPdf.convertHtmlToPdf( FileToPdf.convertHtmlToPdf(

View File

@@ -73,8 +73,8 @@ public class ConvertPDFToPDFA {
// Determine PDF/A filter based on requested format // Determine PDF/A filter based on requested format
String pdfFilter = String pdfFilter =
"pdfa".equals(outputFormat) "pdfa".equals(outputFormat)
? "pdf:writer_pdf_Export:{\"SelectPdfVersion\":{\"type\":\"long\",\"value\":\"2\"}}" ? "writer_pdf_Export:{'SelectPdfVersion':{'Value':'2'}}:writer_pdf_Export"
: "pdf:writer_pdf_Export:{\"SelectPdfVersion\":{\"type\":\"long\",\"value\":\"1\"}}"; : "writer_pdf_Export:{'SelectPdfVersion':{'Value':'1'}}:writer_pdf_Export";
// Prepare LibreOffice command // Prepare LibreOffice command
List<String> command = List<String> command =
@@ -84,7 +84,7 @@ public class ConvertPDFToPDFA {
"--headless", "--headless",
"--nologo", "--nologo",
"--convert-to", "--convert-to",
pdfFilter, "pdf:" + pdfFilter,
"--outdir", "--outdir",
tempOutputDir.toString(), tempOutputDir.toString(),
tempInputFile.toString())); tempInputFile.toString()));

View File

@@ -8,9 +8,7 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@@ -43,12 +41,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "Misc", description = "Miscellaneous APIs")
public class AutoSplitPdfController { public class AutoSplitPdfController {
private static final Set<String> VALID_QR_CONTENTS = private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF";
new HashSet<>( private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF";
Set.of(
"https://github.com/Stirling-Tools/Stirling-PDF",
"https://github.com/Frooodle/Stirling-PDF",
"https://stirlingpdf.com"));
private final CustomPDDocumentFactory pdfDocumentFactory; private final CustomPDDocumentFactory pdfDocumentFactory;
@@ -126,14 +120,13 @@ public class AutoSplitPdfController {
for (int page = 0; page < document.getNumberOfPages(); ++page) { for (int page = 0; page < document.getNumberOfPages(); ++page) {
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150); BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
String result = decodeQRCode(bim); String result = decodeQRCode(bim);
if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) {
boolean isValidQrCode = VALID_QR_CONTENTS.contains(result);
log.debug("detected qr code {}, code is vale={}", result, isValidQrCode);
if (isValidQrCode && page != 0) {
splitDocuments.add(new PDDocument()); splitDocuments.add(new PDDocument());
} }
if (!splitDocuments.isEmpty() && !isValidQrCode) { if (!splitDocuments.isEmpty()
&& !QR_CONTENT.equals(result)
&& !QR_CONTENT_OLD.equals(result)) {
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page)); splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
} else if (page == 0) { } else if (page == 0) {
PDDocument firstDocument = new PDDocument(); PDDocument firstDocument = new PDDocument();
@@ -142,7 +135,7 @@ public class AutoSplitPdfController {
} }
// If duplexMode is true and current page is a divider, then skip next page // If duplexMode is true and current page is a divider, then skip next page
if (duplexMode && isValidQrCode) { if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) {
page++; page++;
} }
} }
@@ -175,9 +168,6 @@ public class AutoSplitPdfController {
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
} catch (Exception e) {
log.error("Error in auto split", e);
throw e;
} finally { } finally {
// Clean up resources // Clean up resources
if (document != null) { if (document != null) {

View File

@@ -26,7 +26,6 @@ import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.BoundedLineReader; import io.github.pixee.security.BoundedLineReader;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -66,10 +65,6 @@ public class OCRController {
} }
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf") @PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
@Operation(
summary = "Process PDF files with OCR using Tesseract",
description =
"Takes a PDF file as input, performs OCR using specified languages and OCR type (skip-text/force-ocr), and returns the processed PDF. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> processPdfWithOCR( public ResponseEntity<byte[]> processPdfWithOCR(
@ModelAttribute ProcessPdfWithOcrRequest request) @ModelAttribute ProcessPdfWithOcrRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {

View File

@@ -25,7 +25,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineResult;
import stirling.software.SPDF.model.api.HandleDataRequest; import stirling.software.SPDF.model.api.HandleDataRequest;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -59,8 +58,7 @@ public class PipelineController {
if (inputFiles == null || inputFiles.size() == 0) { if (inputFiles == null || inputFiles.size() == 0) {
return null; return null;
} }
PipelineResult result = processor.runPipelineAgainstFiles(inputFiles, config); List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config);
List<Resource> outputFiles = result.getOutputFiles();
if (outputFiles != null && outputFiles.size() == 1) { if (outputFiles != null && outputFiles.size() == 1) {
// If there is only one file, return it directly // If there is only one file, return it directly
Resource singleFile = outputFiles.get(0); Resource singleFile = outputFiles.get(0);

View File

@@ -27,7 +27,6 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation; import stirling.software.SPDF.model.PipelineOperation;
import stirling.software.SPDF.model.PipelineResult;
import stirling.software.SPDF.utils.FileMonitor; import stirling.software.SPDF.utils.FileMonitor;
@Service @Service
@@ -144,64 +143,19 @@ public class PipelineDirectoryProcessor {
private File[] collectFilesForProcessing(Path dir, Path jsonFile, PipelineOperation operation) private File[] collectFilesForProcessing(Path dir, Path jsonFile, PipelineOperation operation)
throws IOException { throws IOException {
List<String> inputExtensions =
apiDocService.getExtensionTypes(false, operation.getOperation());
log.info(
"Allowed extensions for operation {}: {}",
operation.getOperation(),
inputExtensions);
boolean allowAllFiles = inputExtensions.contains("ALL");
try (Stream<Path> paths = Files.list(dir)) { try (Stream<Path> paths = Files.list(dir)) {
File[] files = if ("automated".equals(operation.getParameters().get("fileInput"))) {
paths.filter( return paths.filter(
path -> { path ->
if (Files.isDirectory(path)) { !Files.isDirectory(path)
return false; && !path.equals(jsonFile)
} && fileMonitor.isFileReadyForProcessing(path))
if (path.equals(jsonFile)) {
return false;
}
// Get file extension
String filename = path.getFileName().toString();
String extension =
filename.contains(".")
? filename.substring(
filename.lastIndexOf(".")
+ 1)
.toLowerCase()
: "";
// Check against allowed extensions
boolean isAllowed =
allowAllFiles
|| inputExtensions.contains(extension);
if (!isAllowed) {
log.info(
"Skipping file with unsupported extension: {} ({})",
filename,
extension);
}
return isAllowed;
})
.filter(
path -> {
boolean isReady =
fileMonitor.isFileReadyForProcessing(path);
if (!isReady) {
log.info(
"File not ready for processing (locked/created last 5s): {}",
path);
}
return isReady;
})
.map(Path::toFile) .map(Path::toFile)
.toArray(File[]::new); .toArray(File[]::new);
log.info("Collected {} files for processing", files.length); } else {
return files; String fileInput = (String) operation.getParameters().get("fileInput");
return new File[] {new File(fileInput)};
}
} }
} }
@@ -244,37 +198,19 @@ public class PipelineDirectoryProcessor {
try { try {
List<Resource> inputFiles = List<Resource> inputFiles =
processor.generateInputFiles(filesToProcess.toArray(new File[0])); processor.generateInputFiles(filesToProcess.toArray(new File[0]));
if (inputFiles == null || inputFiles.isEmpty()) { if (inputFiles == null || inputFiles.size() == 0) {
return; return;
} }
PipelineResult result = processor.runPipelineAgainstFiles(inputFiles, config); List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config);
if (outputFiles == null) return;
if (result.isHasErrors()) { moveAndRenameFiles(outputFiles, config, dir);
log.error("Errors occurred during processing, retaining original files");
moveToErrorDirectory(filesToProcess, dir);
} else {
moveAndRenameFiles(result.getOutputFiles(), config, dir);
deleteOriginalFiles(filesToProcess, processingDir); deleteOriginalFiles(filesToProcess, processingDir);
}
} catch (Exception e) { } catch (Exception e) {
log.error("Error during processing", e); log.error("error during processing", e);
moveFilesBack(filesToProcess, processingDir); moveFilesBack(filesToProcess, processingDir);
} }
} }
private void moveToErrorDirectory(List<File> files, Path originalDir) throws IOException {
Path errorDir = originalDir.resolve("error");
if (!Files.exists(errorDir)) {
Files.createDirectories(errorDir);
}
for (File file : files) {
Path target = errorDir.resolve(file.getName());
Files.move(file.toPath(), target);
log.info("Moved failed file to error directory for investigation: {}", target);
}
}
private void moveAndRenameFiles(List<Resource> resources, PipelineConfig config, Path dir) private void moveAndRenameFiles(List<Resource> resources, PipelineConfig config, Path dir)
throws IOException { throws IOException {
for (Resource resource : resources) { for (Resource resource : resources) {

View File

@@ -33,7 +33,6 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.SPDFApplication; import stirling.software.SPDF.SPDFApplication;
import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation; import stirling.software.SPDF.model.PipelineOperation;
import stirling.software.SPDF.model.PipelineResult;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
@Service @Service
@@ -85,10 +84,8 @@ public class PipelineProcessor {
return "http://localhost:" + port + contextPath + "/"; return "http://localhost:" + port + contextPath + "/";
} }
PipelineResult runPipelineAgainstFiles(List<Resource> outputFiles, PipelineConfig config) List<Resource> runPipelineAgainstFiles(List<Resource> outputFiles, PipelineConfig config)
throws Exception { throws Exception {
PipelineResult result = new PipelineResult();
ByteArrayOutputStream logStream = new ByteArrayOutputStream(); ByteArrayOutputStream logStream = new ByteArrayOutputStream();
PrintStream logPrintStream = new PrintStream(logStream); PrintStream logPrintStream = new PrintStream(logStream);
boolean hasErrors = false; boolean hasErrors = false;
@@ -133,8 +130,7 @@ public class PipelineProcessor {
if (operation.startsWith("filter-") if (operation.startsWith("filter-")
&& (response.getBody() == null && (response.getBody() == null
|| response.getBody().length == 0)) { || response.getBody().length == 0)) {
result.setFiltersApplied(true); log.info("Skipping file due to failing {}", operation);
log.info("Skipping file due to filtering {}", operation);
continue; continue;
} }
if (!response.getStatusCode().equals(HttpStatus.OK)) { if (!response.getStatusCode().equals(HttpStatus.OK)) {
@@ -212,10 +208,7 @@ public class PipelineProcessor {
if (hasErrors) { if (hasErrors) {
log.error("Errors occurred during processing. Log: {}", logStream.toString()); log.error("Errors occurred during processing. Log: {}", logStream.toString());
} }
result.setHasErrors(hasErrors); return outputFiles;
result.setFiltersApplied(hasErrors);
result.setOutputFiles(outputFiles);
return result;
} }
private ResponseEntity<byte[]> sendWebRequest(String url, MultiValueMap<String, Object> body) { private ResponseEntity<byte[]> sendWebRequest(String url, MultiValueMap<String, Object> body) {

View File

@@ -40,7 +40,8 @@ public class RemoveCertSignController {
@Operation( @Operation(
summary = "Remove digital signature from PDF", summary = "Remove digital signature from PDF",
description = description =
"This endpoint accepts a PDF file and returns the PDF file without the digital signature. Input:PDF, Output:PDF Type:SISO") "This endpoint accepts a PDF file and returns the PDF file without the digital signature."
+ " Input: PDF, Output: PDF")
public ResponseEntity<byte[]> removeCertSignPDF(@ModelAttribute PDFFile request) public ResponseEntity<byte[]> removeCertSignPDF(@ModelAttribute PDFFile request)
throws Exception { throws Exception {
MultipartFile pdf = request.getFileInput(); MultipartFile pdf = request.getFileInput();

View File

@@ -1,14 +0,0 @@
package stirling.software.SPDF.model;
import java.util.List;
import org.springframework.core.io.Resource;
import lombok.Data;
@Data
public class PipelineResult {
private List<Resource> outputFiles;
private boolean hasErrors;
private boolean filtersApplied;
}

View File

@@ -1,10 +1,10 @@
package stirling.software.SPDF.service; package stirling.software.SPDF.service;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.Arrays;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
@@ -21,7 +21,8 @@ public class LanguageService {
private final PathMatchingResourcePatternResolver resourcePatternResolver = private final PathMatchingResourcePatternResolver resourcePatternResolver =
new PathMatchingResourcePatternResolver(); new PathMatchingResourcePatternResolver();
public LanguageService(ApplicationProperties applicationProperties) { public LanguageService(
ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties; this.applicationProperties = applicationProperties;
} }

View File

@@ -36,9 +36,7 @@ public class FileToPdf {
try { try {
if (fileName.endsWith(".html")) { if (fileName.endsWith(".html")) {
tempInputFile = Files.createTempFile("input_", ".html"); tempInputFile = Files.createTempFile("input_", ".html");
String sanitizedHtml = String sanitizedHtml = sanitizeHtmlContent(new String(fileBytes, StandardCharsets.UTF_8), disableSanitize);
sanitizeHtmlContent(
new String(fileBytes, StandardCharsets.UTF_8), disableSanitize);
Files.write(tempInputFile, sanitizedHtml.getBytes(StandardCharsets.UTF_8)); Files.write(tempInputFile, sanitizedHtml.getBytes(StandardCharsets.UTF_8));
} else if (fileName.endsWith(".zip")) { } else if (fileName.endsWith(".zip")) {
tempInputFile = Files.createTempFile("input_", ".zip"); tempInputFile = Files.createTempFile("input_", ".zip");
@@ -95,8 +93,7 @@ public class FileToPdf {
return (!disableSanitize) ? CustomHtmlSanitizer.sanitize(htmlContent) : htmlContent; return (!disableSanitize) ? CustomHtmlSanitizer.sanitize(htmlContent) : htmlContent;
} }
private static void sanitizeHtmlFilesInZip(Path zipFilePath, boolean disableSanitize) private static void sanitizeHtmlFilesInZip(Path zipFilePath, boolean disableSanitize) throws IOException {
throws IOException {
Path tempUnzippedDir = Files.createTempDirectory("unzipped_"); Path tempUnzippedDir = Files.createTempDirectory("unzipped_");
try (ZipInputStream zipIn = try (ZipInputStream zipIn =
ZipSecurity.createHardenedInputStream( ZipSecurity.createHardenedInputStream(

View File

@@ -587,7 +587,9 @@ public class GeneralUtils {
for (byte b : hash) { for (byte b : hash) {
fingerprint.append(String.format("%02x", b)); fingerprint.append(String.format("%02x", b));
} }
return fingerprint.toString(); return fingerprint.toString();
} catch (Exception e) { } catch (Exception e) {
return "GenericID"; return "GenericID";
} }

View File

@@ -218,9 +218,6 @@ public class ProcessExecutor {
errorReaderThread.join(); errorReaderThread.join();
outputReaderThread.join(); outputReaderThread.join();
boolean isQpdf =
command != null && !command.isEmpty() && command.get(0).contains("qpdf");
if (outputLines.size() > 0) { if (outputLines.size() > 0) {
String outputMessage = String.join("\n", outputLines); String outputMessage = String.join("\n", outputLines);
messages += outputMessage; messages += outputMessage;
@@ -236,9 +233,6 @@ public class ProcessExecutor {
log.warn("Command error output:\n" + errorMessage); log.warn("Command error output:\n" + errorMessage);
} }
if (exitCode != 0) { if (exitCode != 0) {
if (isQpdf && exitCode == 3) {
log.warn("qpdf succeeded with warnings: {}", messages);
} else {
throw new IOException( throw new IOException(
"Command process failed with exit code " "Command process failed with exit code "
+ exitCode + exitCode
@@ -246,19 +240,14 @@ public class ProcessExecutor {
+ errorMessage); + errorMessage);
} }
} }
}
if (exitCode != 0) { if (exitCode != 0) {
if (isQpdf && exitCode == 3) {
log.warn("qpdf succeeded with warnings: {}", messages);
} else {
throw new IOException( throw new IOException(
"Command process failed with exit code " "Command process failed with exit code "
+ exitCode + exitCode
+ "\nLogs: " + "\nLogs: "
+ messages); + messages);
} }
}
} finally { } finally {
semaphore.release(); semaphore.release();
} }

View File

@@ -768,6 +768,7 @@ autoSplitPDF.selectText.3=Upload the single large scanned PDF file and let Stirl
autoSplitPDF.selectText.4=Divider pages are automatically detected and removed, guaranteeing a neat final document. 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.formPrompt=Submit PDF containing Stirling-PDF Page dividers:
autoSplitPDF.duplexMode=Duplex Mode (Front and back scanning) 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.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf'
autoSplitPDF.submit=Submit autoSplitPDF.submit=Submit
@@ -1184,7 +1185,7 @@ changeMetadata.submit=Change
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF To PDF/A pdfToPDFA.title=PDF To PDF/A
pdfToPDFA.header=PDF To PDF/A pdfToPDFA.header=PDF To PDF/A
pdfToPDFA.credit=This service uses libreoffice for PDF/A conversion pdfToPDFA.credit=This service uses qpdf for PDF/A conversion
pdfToPDFA.submit=Convert pdfToPDFA.submit=Convert
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format pdfToPDFA.outputFormat=Output format

View File

@@ -1185,7 +1185,7 @@ changeMetadata.submit=Change
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF To PDF/A pdfToPDFA.title=PDF To PDF/A
pdfToPDFA.header=PDF To PDF/A pdfToPDFA.header=PDF To PDF/A
pdfToPDFA.credit=This service uses libreoffice for PDF/A conversion pdfToPDFA.credit=This service uses qpdf for PDF/A conversion
pdfToPDFA.submit=Convert pdfToPDFA.submit=Convert
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format pdfToPDFA.outputFormat=Output format

View File

@@ -27,7 +27,7 @@ bored=Stanco di aspettare?
alphabet=Alfabeto alphabet=Alfabeto
downloadPdf=Scarica PDF downloadPdf=Scarica PDF
text=Testo text=Testo
font=Font font=Fonte
selectFillter=-- Seleziona -- selectFillter=-- Seleziona --
pageNum=Numero pagina pageNum=Numero pagina
sizes.small=Piccolo sizes.small=Piccolo
@@ -273,7 +273,7 @@ home.legacyHomepage=Vecchia homepage
home.newHomePage=Prova la nostra nuova homepage! home.newHomePage=Prova la nostra nuova homepage!
home.alphabetical=Alfabetico home.alphabetical=Alfabetico
home.globalPopularity=Popolarità globale home.globalPopularity=Popolarità globale
home.sortBy=Ordina per: home.sortBy=Sort by:
home.multiTool.title=Multifunzione PDF home.multiTool.title=Multifunzione PDF
home.multiTool.desc=Unisci, Ruota, Riordina, e Rimuovi pagine home.multiTool.desc=Unisci, Ruota, Riordina, e Rimuovi pagine
@@ -952,7 +952,7 @@ compress.title=Comprimi
compress.header=Comprimi PDF compress.header=Comprimi PDF
compress.credit=Questo servizio utilizza qpdf per la compressione/ottimizzazione dei PDF. compress.credit=Questo servizio utilizza qpdf per la compressione/ottimizzazione dei PDF.
compress.selectText.1=Modalità manuale - Da 1 a 5 compress.selectText.1=Modalità manuale - Da 1 a 5
compress.selectText.1.1=Nei livelli di ottimizzazione da 6 a 9, oltre alla compressione PDF generale, la risoluzione dell'immagine viene ridotta per ridurre ulteriormente le dimensioni del file. Livelli più alti comportano una compressione dell'immagine più forte (fino al 50% delle dimensioni originali), ottenendo una maggiore riduzione delle dimensioni ma con una potenziale perdita di qualità nelle immagini. compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
compress.selectText.2=Livello di ottimizzazione: compress.selectText.2=Livello di ottimizzazione:
compress.selectText.3=4 (Terribile per le immagini di testo) compress.selectText.3=4 (Terribile per le immagini di testo)
compress.selectText.4=Modalità automatica - Regola automaticamente la qualità per ottenere le dimensioni esatte del PDF compress.selectText.4=Modalità automatica - Regola automaticamente la qualità per ottenere le dimensioni esatte del PDF

View File

@@ -138,7 +138,7 @@ analytics.settings=Você pode alterar as configurações de coleta de dados no a
# NAVBAR # # NAVBAR #
############# #############
navbar.favorite=Favoritos navbar.favorite=Favoritos
navbar.recent=Novos e Recentemente Atualizados navbar.recent=New and recently updated
navbar.darkmode=Modo Escuro navbar.darkmode=Modo Escuro
navbar.language=Idiomas navbar.language=Idiomas
navbar.settings=Configurações navbar.settings=Configurações
@@ -266,14 +266,14 @@ home.viewPdf.title=Visualizar PDF
home.viewPdf.desc=Visualizar, anotar, adicionar texto ou imagens ao PDF. home.viewPdf.desc=Visualizar, anotar, adicionar texto ou imagens ao PDF.
viewPdf.tags=visualizar,ler,anotar,texto,imagem viewPdf.tags=visualizar,ler,anotar,texto,imagem
home.setFavorites=Adicionar Favoritos home.setFavorites=Set Favourites
home.hideFavorites=Ocultar Favoritos home.hideFavorites=Hide Favourites
home.showFavorites=Mostrar Favoritos home.showFavorites=Show Favourites
home.legacyHomepage=Homepage Antiga home.legacyHomepage=Old homepage
home.newHomePage=Experimente nossa nova Homepage! home.newHomePage=Try our new homepage!
home.alphabetical=Alfabética home.alphabetical=Alphabetical
home.globalPopularity=Popularidade Global home.globalPopularity=Global Popularity
home.sortBy=Ordenar por: home.sortBy=Sort by:
home.multiTool.title=Multiferramentas de PDF home.multiTool.title=Multiferramentas de PDF
home.multiTool.desc=Mesclar, girar, reorganizar, dividir, inserir e remover páginas. home.multiTool.desc=Mesclar, girar, reorganizar, dividir, inserir e remover páginas.
@@ -417,7 +417,7 @@ home.pageLayout.title=Layout de Múltiplas Páginas
home.pageLayout.desc=Mesclar várias páginas de um documento PDF em uma única página. home.pageLayout.desc=Mesclar várias páginas de um documento PDF em uma única página.
pageLayout.tags=mesclar,composto,vista-única,organizar pageLayout.tags=mesclar,composto,vista-única,organizar
home.scalePages.title=Ajustar Dimensões da Página home.scalePages.title=Ajustar Tamanho/Escala da Página
home.scalePages.desc=Alterar o tamanho/escala da página e/ou seu conteúdo. home.scalePages.desc=Alterar o tamanho/escala da página e/ou seu conteúdo.
scalePages.tags=redimensionar,modificar,dimensão,adaptar scalePages.tags=redimensionar,modificar,dimensão,adaptar
@@ -429,7 +429,7 @@ home.add-page-numbers.title=Adicionar Números de Página
home.add-page-numbers.desc=Adicionar números de página no documento, em um local definido. home.add-page-numbers.desc=Adicionar números de página no documento, em um local definido.
add-page-numbers.tags=paginar,rotular,organizar,índice add-page-numbers.tags=paginar,rotular,organizar,índice
home.auto-rename.title=Renomeação Automática do PDF home.auto-rename.title=Renomear Automaticamente o PDF
home.auto-rename.desc=Renomeia automaticamente o PDF com base no cabeçalho detectado. home.auto-rename.desc=Renomeia automaticamente o PDF com base no cabeçalho detectado.
auto-rename.tags=detecção-automática,baseado-em-cabeçalho,organizar,relabel auto-rename.tags=detecção-automática,baseado-em-cabeçalho,organizar,relabel
@@ -738,8 +738,8 @@ addPageNumbers.submit=Adicionar Números de Página
#auto-rename #auto-rename
auto-rename.title=Renomeação Automática do PDF auto-rename.title=Renomear Automaticamente o PDF
auto-rename.header=Renomeação Automática do PDF auto-rename.header=Renomear Automaticamente o PDF
auto-rename.submit=Renomeação Automática auto-rename.submit=Renomeação Automática
@@ -786,8 +786,8 @@ pageLayout.submit=Enviar
#scalePages #scalePages
scalePages.title=Ajustar Dimensões da Página scalePages.title=Ajustar Tamanho/Escala da Página
scalePages.header=Ajustar Dimensões da Página scalePages.header=Ajustar Tamanho/Escala da Página
scalePages.pageSize=Tamanho desejado do documento: scalePages.pageSize=Tamanho desejado do documento:
scalePages.keepPageSize=Tamanho Original scalePages.keepPageSize=Tamanho Original
scalePages.scaleFactor=Fator de zoom (corte) de uma página: scalePages.scaleFactor=Fator de zoom (corte) de uma página:

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<symbol id="icon-redact-auto" viewBox="0 0 24 24"> <g id="Layer_2" data-name="Layer 2"> <symbol id="icon-redact-auto" viewBox="0 0 24 24"> <g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1"> <g id="Layer_1-2" data-name="Layer 1">
<rect width="24" height="24" style="fill: none"/> <rect width="24" height="24" style="fill: none"/>
@@ -14,5 +14,4 @@
</g> </g>
</g> </g>
</g> </g>
</symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<symbol id="icon-redact-manual" viewBox="-2 0 24 24"> <symbol id="icon-redact-manual" viewBox="-2 0 24 24">
<g id="Layer_2" data-name="Layer 2"> <g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1"> <g id="Layer_1-2" data-name="Layer 1">

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<symbol id="icon-split-auto" viewBox="0 0 24 24"> <symbol id="icon-split-auto" viewBox="0 0 24 24"> <g id="Layer_2" data-name="Layer 2"> <g id="Layer_2" data-name="Layer 2">
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1"> <g id="Layer_1-2" data-name="Layer 1">
<g> <g>
<path d="M18.42466,20.16555,12,13.74089,9.84315,15.89774a2.45776,2.45776,0,0,1,.2524.73425,4.481,4.481,0,0,1,.06883.78013A3.53515,3.53515,0,0,1,9.086,20.00493a3.53516,3.53516,0,0,1-2.59281,1.07843,3.53516,3.53516,0,0,1-2.59281-1.07843,3.6561,3.6561,0,0,1,0-5.18561,3.53516,3.53516,0,0,1,2.59281-1.07843,4.48117,4.48117,0,0,1,.78014.06884,2.45778,2.45778,0,0,1,.73424.25239l2.15685-2.15685L8.00753,9.74842a2.45752,2.45752,0,0,1-.73424.2524,4.48117,4.48117,0,0,1-.78014.06884A3.53516,3.53516,0,0,1,3.90034,8.99123,3.53515,3.53515,0,0,1,2.82192,6.39842,3.53515,3.53515,0,0,1,3.90034,3.80561,3.53515,3.53515,0,0,1,6.49315,2.72719,3.53515,3.53515,0,0,1,9.086,3.80561a3.53515,3.53515,0,0,1,1.07842,2.59281,4.48107,4.48107,0,0,1-.06883.78014,2.45786,2.45786,0,0,1-.2524.73425L21.17808,19.24774v.91781Zm-3.67123-9.17809L12.91781,9.15185,18.42466,3.645h2.75342v.91781ZM6.49315,8.234A1.841,1.841,0,0,0,8.32877,6.39842,1.841,1.841,0,0,0,6.49315,4.56281,1.841,1.841,0,0,0,4.65753,6.39842,1.841,1.841,0,0,0,6.49315,8.234ZM12,12.36418a.47051.47051,0,1,0-.32123-.13767A.44026.44026,0,0,0,12,12.36418ZM6.49315,19.24774a1.83562,1.83562,0,1,0-1.29641-3.132,1.83562,1.83562,0,0,0,1.29641,3.132Z" style="fill: currentColor"/> <path d="M18.42466,20.16555,12,13.74089,9.84315,15.89774a2.45776,2.45776,0,0,1,.2524.73425,4.481,4.481,0,0,1,.06883.78013A3.53515,3.53515,0,0,1,9.086,20.00493a3.53516,3.53516,0,0,1-2.59281,1.07843,3.53516,3.53516,0,0,1-2.59281-1.07843,3.6561,3.6561,0,0,1,0-5.18561,3.53516,3.53516,0,0,1,2.59281-1.07843,4.48117,4.48117,0,0,1,.78014.06884,2.45778,2.45778,0,0,1,.73424.25239l2.15685-2.15685L8.00753,9.74842a2.45752,2.45752,0,0,1-.73424.2524,4.48117,4.48117,0,0,1-.78014.06884A3.53516,3.53516,0,0,1,3.90034,8.99123,3.53515,3.53515,0,0,1,2.82192,6.39842,3.53515,3.53515,0,0,1,3.90034,3.80561,3.53515,3.53515,0,0,1,6.49315,2.72719,3.53515,3.53515,0,0,1,9.086,3.80561a3.53515,3.53515,0,0,1,1.07842,2.59281,4.48107,4.48107,0,0,1-.06883.78014,2.45786,2.45786,0,0,1-.2524.73425L21.17808,19.24774v.91781Zm-3.67123-9.17809L12.91781,9.15185,18.42466,3.645h2.75342v.91781ZM6.49315,8.234A1.841,1.841,0,0,0,8.32877,6.39842,1.841,1.841,0,0,0,6.49315,4.56281,1.841,1.841,0,0,0,4.65753,6.39842,1.841,1.841,0,0,0,6.49315,8.234ZM12,12.36418a.47051.47051,0,1,0-.32123-.13767A.44026.44026,0,0,0,12,12.36418ZM6.49315,19.24774a1.83562,1.83562,0,1,0-1.29641-3.132,1.83562,1.83562,0,0,0,1.29641,3.132Z" style="fill: currentColor"/>
@@ -10,5 +9,4 @@
<rect width="24" height="24" style="fill: none"/> <rect width="24" height="24" style="fill: none"/>
</g> </g>
</g> </g>
</symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<symbol id="icon-split-chapters" viewBox="0 0 24 24"> <symbol id="icon-split-chapters" viewBox="0 0 24 24"> <g id="Layer_2" data-name="Layer 2"> <g id="Layer_2" data-name="Layer 2">
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1"> <g id="Layer_1-2" data-name="Layer 1">
<g> <g>
<path d="M17.632,9.18527v5.44l1.94-1.16,1.94,1.16v-5.44Z" style="fill: currentColor"/> <path d="M17.632,9.18527v5.44l1.94-1.16,1.94,1.16v-5.44Z" style="fill: currentColor"/>
@@ -10,5 +9,4 @@
<rect width="24" height="24" style="fill: none"/> <rect width="24" height="24" style="fill: none"/>
</g> </g>
</g> </g>
</symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="padding-left: 20px; margin-right: -20px;">
<symbol id="icon-split-size" viewBox="0 0 24 24"> <symbol id="icon-split-size" viewBox="0 0 24 24"> <g id="Layer_2" data-name="Layer 2"> <g id="Layer_2" data-name="Layer 2">
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1"> <g id="Layer_1-2" data-name="Layer 1">
<g> <g>
<path d="M17.90313,20.16555l-6.42466-6.42466L9.32162,15.89774a2.45776,2.45776,0,0,1,.2524.73425,4.481,4.481,0,0,1,.06883.78013,3.53515,3.53515,0,0,1-1.07842,2.59281,3.53516,3.53516,0,0,1-2.59281,1.07843,3.53516,3.53516,0,0,1-2.59281-1.07843,3.6561,3.6561,0,0,1,0-5.18561,3.53516,3.53516,0,0,1,2.59281-1.07843,4.48117,4.48117,0,0,1,.78014.06884,2.45778,2.45778,0,0,1,.73424.25239l2.15685-2.15685L7.486,9.74842a2.45752,2.45752,0,0,1-.73424.2524,4.48117,4.48117,0,0,1-.78014.06884A3.53516,3.53516,0,0,1,3.37881,8.99123,3.53515,3.53515,0,0,1,2.30039,6.39842,3.53515,3.53515,0,0,1,3.37881,3.80561,3.53515,3.53515,0,0,1,5.97162,2.72719,3.53515,3.53515,0,0,1,8.56443,3.80561,3.53515,3.53515,0,0,1,9.64285,6.39842a4.48107,4.48107,0,0,1-.06883.78014,2.45786,2.45786,0,0,1-.2524.73425L20.65655,19.24774v.91781Zm-3.67124-9.17809L12.39628,9.15185,17.90313,3.645h2.75342v.91781ZM5.97162,8.234A1.841,1.841,0,0,0,7.80724,6.39842,1.841,1.841,0,0,0,5.97162,4.56281,1.841,1.841,0,0,0,4.136,6.39842,1.841,1.841,0,0,0,5.97162,8.234Zm5.50685,4.13014a.47051.47051,0,1,0-.32123-.13767A.44026.44026,0,0,0,11.47847,12.36418ZM5.97162,19.24774a1.83562,1.83562,0,1,0-1.29641-3.132,1.83562,1.83562,0,0,0,1.29641,3.132Z" style="fill: currentColor"/> <path d="M17.90313,20.16555l-6.42466-6.42466L9.32162,15.89774a2.45776,2.45776,0,0,1,.2524.73425,4.481,4.481,0,0,1,.06883.78013,3.53515,3.53515,0,0,1-1.07842,2.59281,3.53516,3.53516,0,0,1-2.59281,1.07843,3.53516,3.53516,0,0,1-2.59281-1.07843,3.6561,3.6561,0,0,1,0-5.18561,3.53516,3.53516,0,0,1,2.59281-1.07843,4.48117,4.48117,0,0,1,.78014.06884,2.45778,2.45778,0,0,1,.73424.25239l2.15685-2.15685L7.486,9.74842a2.45752,2.45752,0,0,1-.73424.2524,4.48117,4.48117,0,0,1-.78014.06884A3.53516,3.53516,0,0,1,3.37881,8.99123,3.53515,3.53515,0,0,1,2.30039,6.39842,3.53515,3.53515,0,0,1,3.37881,3.80561,3.53515,3.53515,0,0,1,5.97162,2.72719,3.53515,3.53515,0,0,1,8.56443,3.80561,3.53515,3.53515,0,0,1,9.64285,6.39842a4.48107,4.48107,0,0,1-.06883.78014,2.45786,2.45786,0,0,1-.2524.73425L20.65655,19.24774v.91781Zm-3.67124-9.17809L12.39628,9.15185,17.90313,3.645h2.75342v.91781ZM5.97162,8.234A1.841,1.841,0,0,0,7.80724,6.39842,1.841,1.841,0,0,0,5.97162,4.56281,1.841,1.841,0,0,0,4.136,6.39842,1.841,1.841,0,0,0,5.97162,8.234Zm5.50685,4.13014a.47051.47051,0,1,0-.32123-.13767A.44026.44026,0,0,0,11.47847,12.36418ZM5.97162,19.24774a1.83562,1.83562,0,1,0-1.29641-3.132,1.83562,1.83562,0,0,0,1.29641,3.132Z" style="fill: currentColor"/>
@@ -14,5 +13,4 @@
<rect width="24" height="24" style="fill: none"/> <rect width="24" height="24" style="fill: none"/>
</g> </g>
</g> </g>
</symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -48,7 +48,7 @@ window.tooltipSetup = () => {
tooltipElements.forEach((element) => { tooltipElements.forEach((element) => {
const tooltipText = element.getAttribute('title'); const tooltipText = element.getAttribute('title');
element.removeAttribute('title'); element.removeAttribute('title');
element.setAttribute('data-title', tooltipText); element.setAttribute('data-title', 'tooltipText');
const customTooltip = document.createElement('div'); const customTooltip = document.createElement('div');
customTooltip.className = 'btn-tooltip'; customTooltip.className = 'btn-tooltip';
customTooltip.textContent = tooltipText; customTooltip.textContent = tooltipText;

View File

@@ -17,9 +17,7 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<svg class="material-symbols-rounded tool-header-icon advance"> <span class="material-symbols-rounded tool-header-icon advance">cut</span>
<use xlink:href="/images/split-auto.svg#icon-split-auto"></use>
</svg>
<span class="tool-header-text" th:text="#{autoSplitPDF.header}"></span> <span class="tool-header-text" th:text="#{autoSplitPDF.header}"></span>
</div> </div>
@@ -45,6 +43,8 @@
<li th:text="#{autoSplitPDF.selectText.3}"></li> <li th:text="#{autoSplitPDF.selectText.3}"></li>
<li th:text="#{autoSplitPDF.selectText.4}"></li> <li th:text="#{autoSplitPDF.selectText.4}"></li>
</ul> </ul>
<p><a th:href="@{'/files/Auto%20Splitter%20Divider%20(minimal).pdf'}" download
th:text="#{autoSplitPDF.dividerDownload1}"></a></p>
<p><a th:href="@{'/files/Auto%20Splitter%20Divider%20(with%20instructions).pdf'}" download <p><a th:href="@{'/files/Auto%20Splitter%20Divider%20(with%20instructions).pdf'}" download
th:text="#{autoSplitPDF.dividerDownload2}"></a></p> th:text="#{autoSplitPDF.dividerDownload2}"></a></p>
</div> </div>

View File

@@ -1,6 +1,7 @@
<th:block th:fragment="navElements"> <th:block th:fragment="navElements">
<div id="groupOrganize" class="feature-group"> <div id="groupOrganize" class="feature-group">
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.organize})}"> <div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.organize})}">
</div> </div>
<div class="nav-group-container"> <div class="nav-group-container">
<div <div
@@ -11,8 +12,7 @@
th:replace="~{fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'add_to_photos', 'home.merge.title', 'home.merge.desc', 'merge.tags', 'organize')}"> th:replace="~{fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'add_to_photos', 'home.merge.title', 'home.merge.desc', 'merge.tags', 'organize')}">
</div> </div>
<div <div
th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdfs', 'cut', 'home.split.title', 'home.split.desc', 'split.tags', 'organize')}"> th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdfs', 'cut', 'home.split.title', 'home.split.desc', 'split.tags', 'organize')}"></div>
</div>
<div <div
th:replace="~{fragments/navbarEntry :: navbarEntry('rotate-pdf', 'rotate_right', 'home.rotate.title', 'home.rotate.desc', 'rotate.tags', 'organize')}"> th:replace="~{fragments/navbarEntry :: navbarEntry('rotate-pdf', 'rotate_right', 'home.rotate.title', 'home.rotate.desc', 'rotate.tags', 'organize')}">
</div> </div>
@@ -41,7 +41,8 @@
</div> </div>
<div id="groupConvertTo" class="feature-group"> <div id="groupConvertTo" class="feature-group">
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertTo})}"> <div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertTo})}">
</div> </div>
<div class="nav-group-container"> <div class="nav-group-container">
<div <div
@@ -65,7 +66,8 @@
</div> </div>
</div> </div>
<div id="groupConvertFrom" class="feature-group"> <div id="groupConvertFrom" class="feature-group">
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}"> <div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}">
</div> </div>
<div class="nav-group-container"> <div class="nav-group-container">
<div <div
@@ -102,7 +104,8 @@
</div> </div>
</div> </div>
<div id="convertGroup" class="feature-group"> <div id="convertGroup" class="feature-group">
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertTo})}"> <div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertTo})}">
</div> </div>
<div class="nav-group-container"> <div class="nav-group-container">
<div <div
@@ -124,7 +127,8 @@
th:replace="~{fragments/navbarEntry :: navbarEntry('book-to-pdf', 'book', 'home.BookToPDF.title', 'home.BookToPDF.desc', 'BookToPDF.tags', 'convertto')}"> th:replace="~{fragments/navbarEntry :: navbarEntry('book-to-pdf', 'book', 'home.BookToPDF.title', 'home.BookToPDF.desc', 'BookToPDF.tags', 'convertto')}">
</div> </div>
</div> </div>
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}"> <div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}">
</div> </div>
<div class="nav-group-container"> <div class="nav-group-container">
<div <div
@@ -157,7 +161,8 @@
</div> </div>
</div> </div>
<div id="groupSecurity" class="feature-group"> <div id="groupSecurity" class="feature-group">
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.security})}"> <div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.security})}">
</div> </div>
<div class="nav-group-container"> <div class="nav-group-container">
<div <div
@@ -241,12 +246,13 @@
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-image-pdf', 'remove_selection', 'home.removeImagePdf.title', 'home.removeImagePdf.desc', 'removeImagePdf.tags', 'other')}"> th:replace="~{fragments/navbarEntry :: navbarEntry('remove-image-pdf', 'remove_selection', 'home.removeImagePdf.title', 'home.removeImagePdf.desc', 'removeImagePdf.tags', 'other')}">
</div> </div>
<div <div
th:replace="~{fragments/navbarEntry :: navbarEntry('replace-and-invert-color-pdf', 'format_color_fill', 'home.replaceColorPdf.title', 'home.replaceColorPdf.desc', 'replaceColorPdf.tags', 'other')}"> th:replace="~{fragments/navbarEntry :: navbarEntry('replace-color-pdf', 'format_color_fill', 'home.replaceColorPdf.title', 'home.replaceColorPdf.desc', 'replaceColorPdf.tags', 'other')}">
</div> </div>
</div> </div>
</div> </div>
<div id="groupAdvanced" class="feature-group"> <div id="groupAdvanced" class="feature-group">
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.advance})}"> <div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.advance})}">
</div> </div>
<div class="nav-group-container"> <div class="nav-group-container">
<div <div

View File

@@ -6,9 +6,8 @@
th:data-bs-tags="#{${tagKey}}" th:data-bs-title='#{${titleKey}}'> th:data-bs-tags="#{${tagKey}}" th:data-bs-title='#{${titleKey}}'>
<div style="height:2.5rem;align-items: center;display: flex" th:title="#{${descKey}}" class="icon" alt="icon" <div style="height:2.5rem;align-items: center;display: flex" th:title="#{${descKey}}" class="icon" alt="icon"
th:class="@{${toolGroup}}"> th:class="@{${toolGroup}}">
<svg class="nav-icon" <svg class="nav-icon" style="height: 2.7rem; width:2.7rem; align-items:center; display: flex; justify-content: center;">
style="height: 2.7rem; width:2.7rem; align-items:center; display: flex; justify-content: center;"> <use th:href="@{${toolIcon}}"></use>
<use th:xlink:href="@{${toolIcon}}"></use>
</svg> </svg>
<span class="icon-text" th:text="#{${titleKey}}"></span> <span class="icon-text" th:text="#{${titleKey}}"></span>
</div> </div>

View File

@@ -27,10 +27,9 @@
<span class="material-symbols-rounded search-icon"> <span class="material-symbols-rounded search-icon">
search search
</span> </span>
<input type="text" id="searchBar" onkeyup="filterCardsLegacy()" th:placeholder="#{home.searchBar}" autofocus> <input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}" autofocus>
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<a href="home" onclick="setAsDefault('home')" <a href="home" onclick="setAsDefault('home')" style="text-decoration: none; color: inherit; cursor: pointer; display: flex; align-items: center;">
style="text-decoration: none; color: inherit; cursor: pointer; display: flex; align-items: center;">
<span th:text="#{home.newHomePage}"> <span th:text="#{home.newHomePage}">
</span> </span>
<span class="material-symbols-rounded toggle-favourites" style="font-size: 2rem; margin-left: 0.2rem;" > <span class="material-symbols-rounded toggle-favourites" style="font-size: 2rem; margin-left: 0.2rem;" >
@@ -71,8 +70,7 @@
</div> </div>
<div id="groupFavorites" class="feature-group-legacy"> <div id="groupFavorites" class="feature-group-legacy">
<div <div th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.favorite})}">
th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.favorite})}">
</div> </div>
<div class="feature-group-container"> <div class="feature-group-container">
</div> </div>
@@ -150,7 +148,7 @@
</div> </div>
<div class="feature-group-container"> <div class="feature-group-container">
<div <div
th:replace="~{fragments/card :: card(id='img-to-pdf', cardTitle=#{home.imageToPdf.title}, cardText=#{home.imageToPdf.desc}, cardLink='img-to-pdf', toolIcon='picture_as_pdf', tags=#{imageToPdf.tags}, toolGroup='image')}"> th:replace="~{fragments/card :: card(id='img-to-pdf', cardTitle=#{home.imageToPdf.title}, cardText=#{home.imageToPdf.desc}, cardLink='picture_as_pdf', toolIcon='picture_as_pdf', tags=#{imageToPdf.tags}, toolGroup='image')}">
</div> </div>
<div <div
th:replace="~{fragments/card :: card(id='file-to-pdf', cardTitle=#{home.fileToPDF.title}, cardText=#{home.fileToPDF.desc}, cardLink='file-to-pdf', toolIcon='draft', tags=#{fileToPDF.tags}, toolGroup='convert')}"> th:replace="~{fragments/card :: card(id='file-to-pdf', cardTitle=#{home.fileToPDF.title}, cardText=#{home.fileToPDF.desc}, cardLink='file-to-pdf', toolIcon='draft', tags=#{fileToPDF.tags}, toolGroup='convert')}">
@@ -250,8 +248,7 @@
</div> </div>
<div id="groupView" class="feature-group-legacy"> <div id="groupView" class="feature-group-legacy">
<div <div th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.sections.edit})}">
th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.sections.edit})}">
</div> </div>
<div class="feature-group-container"> <div class="feature-group-container">
<div <div
@@ -292,7 +289,7 @@
th:replace="~{fragments/card :: card(id='remove-image-pdf', cardTitle=#{home.removeImagePdf.title}, cardText=#{home.removeImagePdf.desc}, cardLink='remove-image-pdf', toolIcon='remove_selection', tags=#{removeImagePdf.tags}, toolGroup='other')}"> th:replace="~{fragments/card :: card(id='remove-image-pdf', cardTitle=#{home.removeImagePdf.title}, cardText=#{home.removeImagePdf.desc}, cardLink='remove-image-pdf', toolIcon='remove_selection', tags=#{removeImagePdf.tags}, toolGroup='other')}">
</div> </div>
<div <div
th:replace="~{fragments/card :: card(id='replace-and-invert-color-pdf', cardTitle=#{home.replaceColorPdf.title}, cardText=#{home.replaceColorPdf.desc}, cardLink='replace-and-invert-color-pdf', toolIcon='format_color_fill', tags=#{replaceColorPdf.tags}, toolGroup='other')}"> th:replace="~{fragments/card :: card(id='replace-color-pdf', cardTitle=#{home.replaceColorPdf.title}, cardText=#{home.replaceColorPdf.desc}, cardLink='replace-and-invert-color-pdf', toolIcon='format_color_fill', tags=#{replaceColorPdf.tags}, toolGroup='other')}">
</div> </div>
</div> </div>

View File

@@ -1,7 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
xmlns:th="https://www.thymeleaf.org">
<head> <head>
<th:block th:insert="~{fragments/common :: head(title=#{autoRedact.title}, header=#{autoRedact.header})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{autoRedact.title}, header=#{autoRedact.header})}"></th:block>
</head> </head>
@@ -15,27 +13,22 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<svg class="material-symbols-rounded tool-header-icon security"> <span class="material-symbols-rounded tool-header-icon security">playlist_remove</span>
<use xlink:href="/images/redact-auto.svg#icon-redact-auto"></use>
</svg>
<span class="tool-header-text" th:text="#{autoRedact.header}"></span> <span class="tool-header-text" th:text="#{autoRedact.header}"></span>
</div> </div>
<form th:action="@{'api/v1/security/auto-redact'}" method="post" enctype="multipart/form-data"> <form th:action="@{'api/v1/security/auto-redact'}" method="post" enctype="multipart/form-data">
<div class="mb-3"> <div class="mb-3">
<input type="file" class="form-control" id="fileInput" name="fileInput" required <input type="file" class="form-control" id="fileInput" name="fileInput" required accept="application/pdf">
accept="application/pdf">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="listOfText" class="form-label" th:text="#{autoRedact.textsToRedactLabel}"></label> <label for="listOfText" class="form-label" th:text="#{autoRedact.textsToRedactLabel}"></label>
<textarea class="form-control" id="listOfText" name="listOfText" rows="4" required <textarea class="form-control" id="listOfText" name="listOfText" rows="4" required th:placeholder="#{autoRedact.textsToRedactPlaceholder}"></textarea>
th:placeholder="#{autoRedact.textsToRedactPlaceholder}"></textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="defaultColor" class="form-label" th:text="#{autoRedact.colorLabel}">Color</label> <label for="defaultColor" class="form-label" th:text="#{autoRedact.colorLabel}">Color</label>
<select class="form-control" id="defaultColor" name="defaultColor" <select class="form-control" id="defaultColor" name="defaultColor" onchange="handleColorChange(this.value)">
onchange="handleColorChange(this.value)">
<option value="#000000" th:text="#{black}">Black</option> <option value="#000000" th:text="#{black}">Black</option>
<option value="#FFFFFF" th:text="#{white}">White</option> <option value="#FFFFFF" th:text="#{white}">White</option>
<option value="#FF0000" th:text="#{red}">Red</option> <option value="#FF0000" th:text="#{red}">Red</option>
@@ -73,8 +66,7 @@
<div class="mb-3"> <div class="mb-3">
<label for="customPadding" class="form-label" th:text="#{autoRedact.customPaddingLabel}"></label> <label for="customPadding" class="form-label" th:text="#{autoRedact.customPaddingLabel}"></label>
<input type="number" step="0.1" class="form-control" id="customPadding" name="customPadding" <input type="number" step="0.1" class="form-control" id="customPadding" name="customPadding" value="0.1">
value="0.1">
</div> </div>
<div class="mb-3 form-check"> <div class="mb-3 form-check">
@@ -82,8 +74,7 @@
<label for="convertPDFToImage" th:text="#{autoRedact.convertPDFToImageLabel}"></label> <label for="convertPDFToImage" th:text="#{autoRedact.convertPDFToImageLabel}"></label>
</div> </div>
<button type="submit" id="submitBtn" class="btn btn-primary" <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{autoRedact.submitButton}"></button>
th:text="#{autoRedact.submitButton}"></button>
</form> </form>
</div> </div>
</div> </div>
@@ -92,5 +83,4 @@
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -31,9 +31,7 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<svg class="material-symbols-rounded tool-header-icon security"> <span class="material-symbols-rounded tool-header-icon security">playlist_remove</span>
<use xlink:href="/images/redact-manual.svg#icon-redact-manual"></use>
</svg>
<span class="tool-header-text" th:text="#{redact.header}"></span> <span class="tool-header-text" th:text="#{redact.header}"></span>
</div> </div>
<form th:action="@{'api/v1/security/redact'}" method="post" enctype="multipart/form-data"> <form th:action="@{'api/v1/security/redact'}" method="post" enctype="multipart/form-data">

View File

@@ -1,11 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
xmlns:th="https://www.thymeleaf.org">
<head> <head>
<th:block <th:block th:insert="~{fragments/common :: head(title=#{split-by-size-or-count.title}, header=#{split-by-size-or-count.header})}"></th:block>
th:insert="~{fragments/common :: head(title=#{split-by-size-or-count.title}, header=#{split-by-size-or-count.header})}">
</th:block>
</head> </head>
<body> <body>
@@ -18,15 +14,11 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<svg class="material-symbols-rounded tool-header-icon advance"> <span class="material-symbols-rounded tool-header-icon advance">vertical_split</span>
<use xlink:href="/images/split-size.svg#icon-split-size"></use>
</svg>
<span class="tool-header-text" th:text="#{split-by-size-or-count.header}"></span> <span class="tool-header-text" th:text="#{split-by-size-or-count.header}"></span>
</div> </div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/general/split-by-size-or-count'}"> <form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/general/split-by-size-or-count'}">
<div <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
</div>
<label for="splitType" th:text="#{split-by-size-or-count.type.label}">Split Type</label> <label for="splitType" th:text="#{split-by-size-or-count.type.label}">Split Type</label>
<select id="splitType" name="splitType" class="form-control"> <select id="splitType" name="splitType" class="form-control">
<option value="0" th:text="#{split-by-size-or-count.type.size}">Size</option> <option value="0" th:text="#{split-by-size-or-count.type.size}">Size</option>
@@ -35,11 +27,9 @@
</select> </select>
<br> <br>
<label for="splitValue" th:text="#{split-by-size-or-count.value.label}">Split Value</label> <label for="splitValue" th:text="#{split-by-size-or-count.value.label}">Split Value</label>
<input type="text" id="splitValue" name="splitValue" class="form-control" required <input type="text" id="splitValue" name="splitValue" class="form-control" required th:placeholder="#{split-by-size-or-count.value.placeholder}">
th:placeholder="#{split-by-size-or-count.value.placeholder}">
<br> <br>
<button type="submit" id="submitBtn" class="btn btn-primary" <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split-by-size-or-count.submit}">Submit</button>
th:text="#{split-by-size-or-count.submit}">Submit</button>
</form> </form>
</div> </div>
</div> </div>
@@ -48,5 +38,4 @@
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -3,8 +3,7 @@
xmlns:th="https://www.thymeleaf.org"> xmlns:th="https://www.thymeleaf.org">
<head> <head>
<th:block th:insert="~{fragments/common :: head(title=#{splitByChapters.title}, header=#{splitByChapters.header})}"> <th:block th:insert="~{fragments/common :: head(title=#{splitByChapters.title}, header=#{splitByChapters.header})}"></th:block>
</th:block>
</head> </head>
<body> <body>
@@ -16,9 +15,7 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<svg class="material-symbols-rounded tool-header-icon advance"> <span class="material-symbols-rounded tool-header-icon organize">book</span>
<use xlink:href="/images/split-chapters.svg#icon-split-chapters"></use>
</svg>
<span class="tool-header-text" th:text="#{splitByChapters.header}"></span> <span class="tool-header-text" th:text="#{splitByChapters.header}"></span>
</div> </div>
<form th:action="@{'/api/v1/general/split-pdf-by-chapters'}" method="post" enctype="multipart/form-data"> <form th:action="@{'/api/v1/general/split-pdf-by-chapters'}" method="post" enctype="multipart/form-data">
@@ -28,21 +25,18 @@
<div class="mb-3"> <div class="mb-3">
<label for="bookmarkLevel" th:text="#{splitByChapters.bookmarkLevel}"></label> <label for="bookmarkLevel" th:text="#{splitByChapters.bookmarkLevel}"></label>
<input type="number" class="form-control" id="bookmarkLevel" name="bookmarkLevel" min="0" value="0" <input type="number" class="form-control" id="bookmarkLevel" name="bookmarkLevel" min="0" value="0" required>
required>
</div> </div>
<div class="mb-3 form-check"> <div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="includeMetadata" name="includeMetadata"> <input type="checkbox" class="form-check-input" id="includeMetadata" name="includeMetadata">
<label class="form-check-label" for="includeMetadata" <label class="form-check-label" for="includeMetadata" th:text="#{splitByChapters.includeMetadata}"></label>
th:text="#{splitByChapters.includeMetadata}"></label>
<input type="hidden" name="includeMetadata" value="false" /> <input type="hidden" name="includeMetadata" value="false" />
</div> </div>
<div class="mb-3 form-check"> <div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="allowDuplicates" name="allowDuplicates"> <input type="checkbox" class="form-check-input" id="allowDuplicates" name="allowDuplicates">
<label class="form-check-label" for="allowDuplicates" <label class="form-check-label" for="allowDuplicates" th:text="#{splitByChapters.allowDuplicates}"></label>
th:text="#{splitByChapters.allowDuplicates}"></label>
<input type="hidden" name="allowDuplicates" value="false" /> <input type="hidden" name="allowDuplicates" value="false" />
</div> </div>

View File

@@ -1,5 +1,8 @@
#!/bin/bash #!/bin/bash
# Default value for the Boolean parameter
VERIFICATION=${1:-false} # Default is "false" if no parameter is passed
# Find project root by locating build.gradle # Find project root by locating build.gradle
find_root() { find_root() {
local dir="$PWD" local dir="$PWD"
@@ -82,6 +85,12 @@ main() {
cd "$PROJECT_ROOT" cd "$PROJECT_ROOT"
# Run the gradlew build command and check if it fails
if [[ "$VERIFICATION" == "true" ]]; then
./gradlew clean dependencies buildEnvironment spotlessApply --write-verification-metadata sha256 --refresh-dependencies help
./gradlew clean dependencies buildEnvironment spotlessApply --write-verification-metadata sha256,pgp --refresh-keys --export-keys --refresh-dependencies help
fi
export DOCKER_ENABLE_SECURITY=false export DOCKER_ENABLE_SECURITY=false
# Run the gradlew build command and check if it fails # Run the gradlew build command and check if it fails
if ! ./gradlew clean build; then if ! ./gradlew clean build; then
@@ -89,6 +98,7 @@ main() {
exit 1 exit 1
fi fi
# Building Docker images # Building Docker images
# docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile . # docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile .
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite . docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .
@@ -107,6 +117,7 @@ main() {
cd "$PROJECT_ROOT" cd "$PROJECT_ROOT"
docker-compose -f "./exampleYmlFiles/docker-compose-latest-ultra-lite.yml" down docker-compose -f "./exampleYmlFiles/docker-compose-latest-ultra-lite.yml" down
#run_tests "Stirling-PDF" "./exampleYmlFiles/docker-compose-latest.yml" #run_tests "Stirling-PDF" "./exampleYmlFiles/docker-compose-latest.yml"
#docker-compose -f "./exampleYmlFiles/docker-compose-latest.yml" down #docker-compose -f "./exampleYmlFiles/docker-compose-latest.yml" down
@@ -117,6 +128,7 @@ main() {
exit 1 exit 1
fi fi
# Building Docker images with security enabled # Building Docker images with security enabled
# docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile . # docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile .
# docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite . # docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .
@@ -144,6 +156,7 @@ main() {
docker-compose -f "./exampleYmlFiles/docker-compose-latest-fat-security.yml" down docker-compose -f "./exampleYmlFiles/docker-compose-latest-fat-security.yml" down
run_tests "Stirling-PDF-Security-Fat-with-login" "./exampleYmlFiles/test_cicd.yml" run_tests "Stirling-PDF-Security-Fat-with-login" "./exampleYmlFiles/test_cicd.yml"
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
@@ -179,6 +192,8 @@ main() {
echo -e "\e[31m$test\e[0m" # Red color for failed tests echo -e "\e[31m$test\e[0m" # Red color for failed tests
done done
# Check if there are any failed tests and exit with an error code if so # Check if there are any failed tests and exit with an error code if so
if [ ${#failed_tests[@]} -ne 0 ]; then if [ ${#failed_tests[@]} -ne 0 ]; then
echo "Some tests failed." echo "Some tests failed."
@@ -187,6 +202,7 @@ main() {
echo "All tests passed successfully." echo "All tests passed successfully."
exit 0 exit 0
fi fi
} }
main main