diff --git a/Dockerfile-fat b/Dockerfile-fat index 1efb0eaea7..f0b7bc6be2 100644 --- a/Dockerfile-fat +++ b/Dockerfile-fat @@ -12,7 +12,7 @@ RUN DOCKER_ENABLE_SECURITY=true \ ./gradlew clean build # Main stage -FROM alpine:3.20.2 +FROM alpine:3.20.3 # Copy necessary files COPY scripts /scripts diff --git a/Dockerfile-ultra-lite b/Dockerfile-ultra-lite index 6fcb1f50fa..c1bdd80de6 100644 --- a/Dockerfile-ultra-lite +++ b/Dockerfile-ultra-lite @@ -15,6 +15,7 @@ ENV DOCKER_ENABLE_SECURITY=false \ # Copy necessary files COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh +COPY scripts/installFonts.sh /scripts/installFonts.sh COPY pipeline /pipeline COPY build/libs/*.jar app.jar @@ -33,11 +34,11 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et su-exec \ openjdk21-jre && \ # User permissions - mkdir /configs /logs /customFiles && \ + mkdir -p /configs /logs /customFiles /usr/share/fonts/opentype/noto && \ chmod +x /scripts/*.sh && \ addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \ - chown stirlingpdfuser:stirlingpdfgroup /app.jar + chown stirlingpdfuser:stirlingpdfgroup /app.jar # Set environment variables ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI diff --git a/LocalRunGuide.md b/LocalRunGuide.md index 0c34420312..edb136ec25 100644 --- a/LocalRunGuide.md +++ b/LocalRunGuide.md @@ -257,9 +257,11 @@ To override the default configuration, you can add the following to `/.git/Stirl ```bash server: - host: 0.0.0.0 + host: 0.0.0.0 # Not working - use instead address + address: 0.0.0.0 port: 3000 ``` +'-Djava.net.preferIPv4Stack=true' --> To force ipv4 only in the java starting command **Note:** This file is created after the first application launch. To have it before that, you can create the directory and add the file yourself. diff --git a/README.md b/README.md index 8aae661ce5..1ac27bb8ed 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ Demo of the app is available [here](https://stirlingpdf.io). - [PDF-LIB.js](https://github.com/Hopding/pdf-lib) ## How to use +### Windows +For windows users download the latest Stirling-PDF.exe from our [release](https://github.com/Stirling-Tools/Stirling-PDF/releases) section or by clicking [here](https://github.com/Stirling-Tools/Stirling-PDF/releases/latest/download/Stirling-PDF.exe) ### Locally @@ -172,35 +174,35 @@ Stirling PDF currently supports 38! | ------------------------------------------- | -------------------------------------- | | Arabic (العربية) (ar_AR) | ![99%](https://geps.dev/progress/99) | | Basque (Euskara) (eu_ES) | ![60%](https://geps.dev/progress/60) | -| Bulgarian (Български) (bg_BG) | ![92%](https://geps.dev/progress/92) | +| Bulgarian (Български) (bg_BG) | ![91%](https://geps.dev/progress/91) | | Catalan (Català) (ca_CA) | ![47%](https://geps.dev/progress/47) | -| Croatian (Hrvatski) (hr_HR) | ![92%](https://geps.dev/progress/92) | -| Czech (Česky) (cs_CZ) | ![88%](https://geps.dev/progress/88) | -| Danish (Dansk) (da_DK) | ![97%](https://geps.dev/progress/97) | +| Croatian (Hrvatski) (hr_HR) | ![91%](https://geps.dev/progress/91) | +| Czech (Česky) (cs_CZ) | ![87%](https://geps.dev/progress/87) | +| Danish (Dansk) (da_DK) | ![96%](https://geps.dev/progress/96) | | Dutch (Nederlands) (nl_NL) | ![93%](https://geps.dev/progress/93) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) | -| French (Français) (fr_FR) | ![91%](https://geps.dev/progress/91) | +| French (Français) (fr_FR) | ![90%](https://geps.dev/progress/90) | | German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) | -| Greek (Ελληνικά) (el_GR) | ![80%](https://geps.dev/progress/80) | +| Greek (Ελληνικά) (el_GR) | ![79%](https://geps.dev/progress/79) | | Hindi (हिंदी) (hi_IN) | ![76%](https://geps.dev/progress/76) | | Hungarian (Magyar) (hu_HU) | ![73%](https://geps.dev/progress/73) | | Indonesia (Bahasa Indonesia) (id_ID) | ![74%](https://geps.dev/progress/74) | -| Irish (Gaeilge) (ga_IE) | ![96%](https://geps.dev/progress/96) | +| Irish (Gaeilge) (ga_IE) | ![95%](https://geps.dev/progress/95) | | Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | -| Japanese (日本語) (ja_JP) | ![90%](https://geps.dev/progress/90) | -| Korean (한국어) (ko_KR) | ![82%](https://geps.dev/progress/82) | +| Japanese (日本語) (ja_JP) | ![92%](https://geps.dev/progress/92) | +| Korean (한국어) (ko_KR) | ![81%](https://geps.dev/progress/81) | | Norwegian (Norsk) (no_NB) | ![95%](https://geps.dev/progress/95) | -| Polish (Polski) (pl_PL) | ![90%](https://geps.dev/progress/90) | +| Polish (Polski) (pl_PL) | ![89%](https://geps.dev/progress/89) | | Portuguese (Português) (pt_PT) | ![76%](https://geps.dev/progress/76) | | Portuguese Brazilian (Português) (pt_BR) | ![99%](https://geps.dev/progress/99) | -| Romanian (Română) (ro_RO) | ![98%](https://geps.dev/progress/98) | +| Romanian (Română) (ro_RO) | ![97%](https://geps.dev/progress/97) | | Russian (Русский) (ru_RU) | ![81%](https://geps.dev/progress/81) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![76%](https://geps.dev/progress/76) | -| Simplified Chinese (简体中文) (zh_CN) | ![96%](https://geps.dev/progress/96) | +| Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) | | Slovakian (Slovensky) (sk_SK) | ![89%](https://geps.dev/progress/89) | -| Spanish (Español) (es_ES) | ![99%](https://geps.dev/progress/99) | -| Swedish (Svenska) (sv_SE) | ![98%](https://geps.dev/progress/98) | +| Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) | +| Swedish (Svenska) (sv_SE) | ![97%](https://geps.dev/progress/97) | | Thai (ไทย) (th_TH) | ![96%](https://geps.dev/progress/96) | | Traditional Chinese (繁體中文) (zh_TW) | ![95%](https://geps.dev/progress/95) | | Turkish (Türkçe) (tr_TR) | ![96%](https://geps.dev/progress/96) | diff --git a/build.gradle b/build.gradle index 6ab3a70d39..ce09352a6e 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ ext { } group = "stirling.software" -version = "0.28.3" +version = "0.29.0" java { // 17 is lowest but we support and recommend 21 @@ -115,7 +115,7 @@ configurations.all { } dependencies { //security updates - implementation "org.springframework:spring-webmvc:6.1.9" + implementation "org.springframework:spring-webmvc:6.1.13" implementation("io.github.pixee:java-security-toolkit:1.2.0") @@ -184,14 +184,14 @@ dependencies { implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" - implementation "io.micrometer:micrometer-core:1.13.3" + implementation "io.micrometer:micrometer-core:1.13.4" implementation group: "com.google.zxing", name: "core", version: "3.5.3" // https://mvnrepository.com/artifact/org.commonmark/commonmark - implementation "org.commonmark:commonmark:0.22.0" - implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0" + implementation "org.commonmark:commonmark:0.23.0" + implementation "org.commonmark:commonmark-ext-gfm-tables:0.23.0" // https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17 implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0" - implementation "com.fathzer:javaluator:3.0.4" + implementation "com.fathzer:javaluator:3.0.5" developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion") compileOnly "org.projectlombok:lombok:$lombokVersion" diff --git a/chart/stirling-pdf/Chart.yaml b/chart/stirling-pdf/Chart.yaml index d11a62a1b5..b276a0a211 100644 --- a/chart/stirling-pdf/Chart.yaml +++ b/chart/stirling-pdf/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: 0.28.3 +appVersion: 0.29.0 description: locally hosted web application that allows you to perform various operations on PDF files home: https://github.com/Stirling-Tools/Stirling-PDF diff --git a/exampleYmlFiles/docker-compose-latest-fat-security.yml b/exampleYmlFiles/docker-compose-latest-fat-security.yml index a581fa9b86..f29a8a9fa1 100644 --- a/exampleYmlFiles/docker-compose-latest-fat-security.yml +++ b/exampleYmlFiles/docker-compose-latest-fat-security.yml @@ -1,4 +1,3 @@ -version: '3.3' services: stirling-pdf: container_name: Stirling-PDF-Security-Fat diff --git a/exampleYmlFiles/docker-compose-latest-security-with-sso.yml b/exampleYmlFiles/docker-compose-latest-security-with-sso.yml index 1970a079dd..7791ae7ae0 100644 --- a/exampleYmlFiles/docker-compose-latest-security-with-sso.yml +++ b/exampleYmlFiles/docker-compose-latest-security-with-sso.yml @@ -1,4 +1,3 @@ -version: '3.3' services: stirling-pdf: container_name: Stirling-PDF-Security diff --git a/exampleYmlFiles/docker-compose-latest-security.yml b/exampleYmlFiles/docker-compose-latest-security.yml index b0ec6ea1d0..c1e8e712f4 100644 --- a/exampleYmlFiles/docker-compose-latest-security.yml +++ b/exampleYmlFiles/docker-compose-latest-security.yml @@ -1,4 +1,3 @@ -version: '3.3' services: stirling-pdf: container_name: Stirling-PDF-Security diff --git a/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml b/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml index 2faac865f5..d20ed21ceb 100644 --- a/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml +++ b/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml @@ -1,4 +1,3 @@ -version: '3.3' services: stirling-pdf: container_name: Stirling-PDF-Ultra-Lite-Security diff --git a/exampleYmlFiles/docker-compose-latest-ultra-lite.yml b/exampleYmlFiles/docker-compose-latest-ultra-lite.yml index da868ff288..990ba959bd 100644 --- a/exampleYmlFiles/docker-compose-latest-ultra-lite.yml +++ b/exampleYmlFiles/docker-compose-latest-ultra-lite.yml @@ -1,4 +1,3 @@ -version: '3.3' services: stirling-pdf: container_name: Stirling-PDF-Ultra-Lite diff --git a/exampleYmlFiles/docker-compose-latest.yml b/exampleYmlFiles/docker-compose-latest.yml index 6090e291e3..cab8c3cd76 100644 --- a/exampleYmlFiles/docker-compose-latest.yml +++ b/exampleYmlFiles/docker-compose-latest.yml @@ -1,4 +1,3 @@ -version: '3.3' services: stirling-pdf: container_name: Stirling-PDF diff --git a/scripts/replace_translation_line.sh b/scripts/replace_translation_line.sh new file mode 100644 index 0000000000..d5161d36ae --- /dev/null +++ b/scripts/replace_translation_line.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +translation_key="pdfToPDFA.credit" +old_value="OCRmyPDF" +new_value="ghostscript" + +for file in ../src/main/resources/messages_*.properties; do + sed -i "/^$translation_key=/s/$old_value/$new_value/" "$file" + echo "Updated $file" +done diff --git a/src/main/java/stirling/software/SPDF/EE/EEAppConfig.java b/src/main/java/stirling/software/SPDF/EE/EEAppConfig.java new file mode 100644 index 0000000000..0818aa7df7 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/EE/EEAppConfig.java @@ -0,0 +1,25 @@ +package stirling.software.SPDF.EE; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; + +import stirling.software.SPDF.model.ApplicationProperties; + +@Configuration +@Lazy +public class EEAppConfig { + + private static final Logger logger = LoggerFactory.getLogger(EEAppConfig.class); + + @Autowired ApplicationProperties applicationProperties; + + @Bean(name = "RunningEE") + public boolean runningEnterpriseEdition() { + // TODO: Implement EE detection + return false; + } +} diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index bc17e0f139..94f1db983f 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -1,6 +1,7 @@ package stirling.software.SPDF; import java.io.IOException; +import java.net.ServerSocket; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -30,15 +31,35 @@ public class SPdfApplication { private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class); @Autowired private Environment env; - - @Autowired ApplicationProperties applicationProperties; private static String serverPortStatic; @Value("${server.port:8080}") public void setServerPortStatic(String port) { - SPdfApplication.serverPortStatic = port; + if (port.equalsIgnoreCase("auto")) { + // Use Spring Boot's automatic port assignment (server.port=0) + SPdfApplication.serverPortStatic = "0"; // This will let Spring Boot assign an available port + } else { + SPdfApplication.serverPortStatic = port; + } + } + + // Optionally keep this method if you want to provide a manual port-incrementation fallback. + private static String findAvailablePort(int startPort) { + int port = startPort; + while (!isPortAvailable(port)) { + port++; + } + return String.valueOf(port); + } + + private static boolean isPortAvailable(int port) { + try (ServerSocket socket = new ServerSocket(port)) { + return true; + } catch (IOException e) { + return false; + } } @PostConstruct @@ -48,13 +69,17 @@ public void init() { boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv); if (browserOpen) { try { - String url = "http://localhost:" + getNonStaticPort(); + String url = "http://localhost:" + getStaticPort(); String os = System.getProperty("os.name").toLowerCase(); Runtime rt = Runtime.getRuntime(); if (os.contains("win")) { // For Windows SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url); + } else if (os.contains("mac")) { + rt.exec("open " + url); + } else if (os.contains("nix") || os.contains("nux")) { + rt.exec("xdg-open " + url); } } catch (Exception e) { logger.error("Error opening browser: {}", e.getMessage()); @@ -70,15 +95,13 @@ public static void main(String[] args) throws IOException, InterruptedException app.addInitializers(new ConfigInitializer()); Map propertyFiles = new HashMap<>(); - // stirling pdf settings file + // External config files if (Files.exists(Paths.get("configs/settings.yml"))) { propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml"); } else { - logger.warn( - "External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); + logger.warn("External configuration file 'configs/settings.yml' does not exist."); } - // custom javs settings file if (Files.exists(Paths.get("configs/custom_settings.yml"))) { String existingLocation = propertyFiles.getOrDefault("spring.config.additional-location", ""); @@ -101,19 +124,14 @@ public static void main(String[] args) throws IOException, InterruptedException app.run(args); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException("Thread interrupted while sleeping", e); - } - + // Ensure directories are created try { Files.createDirectories(Path.of("customFiles/static/")); Files.createDirectories(Path.of("customFiles/templates/")); } catch (Exception e) { logger.error("Error creating directories: {}", e.getMessage()); } + printStartupLogs(); } diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java index ffa56c197a..6fc0991761 100644 --- a/src/main/java/stirling/software/SPDF/config/AppConfig.java +++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java @@ -135,4 +135,29 @@ public Predicate processOnlyFiles() { } }; } + + @Bean(name = "termsAndConditions") + public String termsAndConditions() { + return applicationProperties.getLegal().getTermsAndConditions(); + } + + @Bean(name = "privacyPolicy") + public String privacyPolicy() { + return applicationProperties.getLegal().getPrivacyPolicy(); + } + + @Bean(name = "cookiePolicy") + public String cookiePolicy() { + return applicationProperties.getLegal().getCookiePolicy(); + } + + @Bean(name = "impressum") + public String impressum() { + return applicationProperties.getLegal().getImpressum(); + } + + @Bean(name = "accessibilityStatement") + public String accessibilityStatement() { + return applicationProperties.getLegal().getAccessibilityStatement(); + } } diff --git a/src/main/java/stirling/software/SPDF/config/AppUpdateService.java b/src/main/java/stirling/software/SPDF/config/AppUpdateService.java index 7c7a9a49c7..7fc8762908 100644 --- a/src/main/java/stirling/software/SPDF/config/AppUpdateService.java +++ b/src/main/java/stirling/software/SPDF/config/AppUpdateService.java @@ -18,7 +18,7 @@ class AppUpdateService { @Bean(name = "shouldShow") @Scope("request") public boolean shouldShow() { - boolean showUpdate = applicationProperties.getSystem().getShowUpdate(); + boolean showUpdate = applicationProperties.getSystem().isShowUpdate(); boolean showAdminResult = (showAdmin != null) ? showAdmin.getShowUpdateOnlyAdmins() : true; return showUpdate && showAdminResult; } diff --git a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java index 876613f4fc..c0231ca7f3 100644 --- a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java +++ b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java @@ -13,6 +13,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import stirling.software.SPDF.utils.RequestUriUtils; @Component public class MetricsFilter extends OncePerRequestFilter { @@ -30,32 +31,16 @@ protected void doFilterInternal( throws ServletException, IOException { String uri = request.getRequestURI(); - // System.out.println("uri="+uri + ", method=" + request.getMethod() ); - // Ignore static resources - if (!(uri.startsWith("/js") - || uri.startsWith("/v1/api-docs") - || uri.endsWith("robots.txt") - || uri.startsWith("/images") - || uri.endsWith(".png") - || uri.endsWith(".ico") - || uri.endsWith(".css") - || uri.endsWith(".map") - || uri.endsWith(".svg") - || uri.endsWith(".js") - || uri.contains("swagger") - || uri.startsWith("/api/v1/info") - || uri.startsWith("/site.webmanifest") - || uri.startsWith("/fonts") - || uri.startsWith("/pdfjs"))) { + if (RequestUriUtils.isTrackableResource(request.getContextPath(), uri)) { Counter counter = Counter.builder("http.requests") - .tag("uri", uri) + .tag("session", request.getSession().getId()) .tag("method", request.getMethod()) + .tag("uri", uri) .register(meterRegistry); counter.increment(); - // System.out.println("Counted"); } filterChain.doFilter(request, response); diff --git a/src/main/java/stirling/software/SPDF/config/PdfMetadataService.java b/src/main/java/stirling/software/SPDF/config/PdfMetadataService.java new file mode 100644 index 0000000000..7b40a878ae --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/PdfMetadataService.java @@ -0,0 +1,109 @@ +package stirling.software.SPDF.config; + +import java.util.Calendar; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.PdfMetadata; + +@Service +public class PdfMetadataService { + + private final ApplicationProperties applicationProperties; + private final String appVersion; + private final UserServiceInterface userService; + + @Autowired + public PdfMetadataService( + ApplicationProperties applicationProperties, + @Qualifier("appVersion") String appVersion, + @Autowired(required = false) UserServiceInterface userService) { + this.applicationProperties = applicationProperties; + this.appVersion = appVersion; + this.userService = userService; + } + + public PdfMetadata extractMetadataFromPdf(PDDocument pdf) { + return PdfMetadata.builder() + .author(pdf.getDocumentInformation().getAuthor()) + .producer(pdf.getDocumentInformation().getProducer()) + .title(pdf.getDocumentInformation().getTitle()) + .creator(pdf.getDocumentInformation().getCreator()) + .subject(pdf.getDocumentInformation().getSubject()) + .keywords(pdf.getDocumentInformation().getKeywords()) + .creationDate(pdf.getDocumentInformation().getCreationDate()) + .modificationDate(pdf.getDocumentInformation().getModificationDate()) + .build(); + } + + public void setDefaultMetadata(PDDocument pdf) { + PdfMetadata metadata = extractMetadataFromPdf(pdf); + setMetadataToPdf(pdf, metadata); + } + + public void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata) { + setMetadataToPdf(pdf, pdfMetadata, false); + } + + public void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata, boolean newlyCreated) { + if (newlyCreated || pdfMetadata.getCreationDate() == null) { + setNewDocumentMetadata(pdf, pdfMetadata); + } + setCommonMetadata(pdf, pdfMetadata); + } + + private void setNewDocumentMetadata(PDDocument pdf, PdfMetadata pdfMetadata) { + + String creator = "Stirling-PDF"; + + // if (applicationProperties + // .getEnterpriseEdition() + // .getCustomMetadata() + // .isAutoUpdateMetadata()) { + + // producer = + // + // applicationProperties.getEnterpriseEdition().getCustomMetadata().getProducer(); + // creator = + // applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator(); + // title = applicationProperties.getEnterpriseEdition().getCustomMetadata().getTitle(); + + // if ("{filename}".equals(title)) { + // title = "Filename"; // Replace with actual filename logic + // } else if ("{unchanged}".equals(title)) { + // title = pdfMetadata.getTitle(); // Keep the original title + // } + // } + + pdf.getDocumentInformation().setCreator(creator + " " + appVersion); + pdf.getDocumentInformation().setCreationDate(Calendar.getInstance()); + } + + private void setCommonMetadata(PDDocument pdf, PdfMetadata pdfMetadata) { + String producer = "Stirling-PDF"; + String title = pdfMetadata.getTitle(); + pdf.getDocumentInformation().setTitle(title); + pdf.getDocumentInformation().setProducer(producer + " " + appVersion); + pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject()); + pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords()); + pdf.getDocumentInformation().setModificationDate(Calendar.getInstance()); + + String author = pdfMetadata.getAuthor(); + // if (applicationProperties + // .getEnterpriseEdition() + // .getCustomMetadata() + // .isAutoUpdateMetadata()) { + // author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor(); + + // if (userService != null) { + // author = author.replace("username", userService.getCurrentUsername()); + // } + // } + pdf.getDocumentInformation().setAuthor(author); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/AppUpdateAuthService.java b/src/main/java/stirling/software/SPDF/config/security/AppUpdateAuthService.java index c4b53ad51f..5a16aa308a 100644 --- a/src/main/java/stirling/software/SPDF/config/security/AppUpdateAuthService.java +++ b/src/main/java/stirling/software/SPDF/config/security/AppUpdateAuthService.java @@ -20,7 +20,7 @@ class AppUpdateAuthService implements ShowAdminInterface { @Override public boolean getShowUpdateOnlyAdmins() { - boolean showUpdate = applicationProperties.getSystem().getShowUpdate(); + boolean showUpdate = applicationProperties.getSystem().isShowUpdate(); if (!showUpdate) { return showUpdate; } diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java index ed7b7921a7..aa266d2f14 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -152,8 +152,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authenticated()); // Handle OAUTH2 Logins - if (applicationProperties.getSecurity().getOAUTH2() != null - && applicationProperties.getSecurity().getOAUTH2().getEnabled() + if (applicationProperties.getSecurity().getOauth2() != null + && applicationProperties.getSecurity().getOauth2().getEnabled() && !applicationProperties .getSecurity() .getLoginMethod() @@ -222,7 +222,7 @@ public ClientRegistrationRepository clientRegistrationRepository() { } private Optional googleClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); if (oauth == null || !oauth.getEnabled()) { return Optional.empty(); } @@ -251,7 +251,7 @@ private Optional googleClientRegistration() { } private Optional keycloakClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); if (oauth == null || !oauth.getEnabled()) { return Optional.empty(); } @@ -275,7 +275,7 @@ private Optional keycloakClientRegistration() { } private Optional githubClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); if (oauth == null || !oauth.getEnabled()) { return Optional.empty(); } @@ -304,7 +304,7 @@ private Optional githubClientRegistration() { } private Optional oidcClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); if (oauth == null || oauth.getIssuer() == null || oauth.getIssuer().isEmpty() @@ -352,7 +352,7 @@ GrantedAuthoritiesMapper userAuthoritiesMapper() { String useAsUsername = applicationProperties .getSecurity() - .getOAUTH2() + .getOauth2() .getUseAsUsername(); Optional userOpt = userService.findByUsernameIgnoreCase( diff --git a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java index b71fab7703..79b285211d 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java @@ -159,7 +159,10 @@ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletExce }; for (String pattern : permitAllPatterns) { - if (uri.startsWith(pattern) || uri.endsWith(".svg") || uri.endsWith(".png") || uri.endsWith(".ico")) { + if (uri.startsWith(pattern) + || uri.endsWith(".svg") + || uri.endsWith(".png") + || uri.endsWith(".ico")) { return true; } } diff --git a/src/main/java/stirling/software/SPDF/config/security/UserService.java b/src/main/java/stirling/software/SPDF/config/security/UserService.java index 5adfdbc768..ece8135550 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserService.java @@ -11,6 +11,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -342,4 +343,14 @@ public void invalidateUserSessions(String username) { } } } + + public String getCurrentUsername() { + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + + if (principal instanceof UserDetails) { + return ((UserDetails) principal).getUsername(); + } else { + return principal.toString(); + } + } } diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java index 4c7e04d9bf..c8c3f2174d 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java @@ -66,7 +66,7 @@ public void onAuthenticationSuccess( // Redirect to the original destination super.onAuthenticationSuccess(request, response, authentication); } else { - OAUTH2 oAuth = applicationProperties.getSecurity().getOAUTH2(); + OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2(); if (loginAttemptService.isBlocked(username)) { if (session != null) { diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java index 907ddd4bcf..5bbff53f62 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java @@ -43,7 +43,7 @@ public void onLogoutSuccess( } return; } - OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); if (authentication instanceof OAuth2AuthenticationToken) { OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java index b9766480fc..ebe734b580 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java @@ -43,7 +43,7 @@ public CustomOAuth2UserService( @Override public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { - OAUTH2 oauth2 = applicationProperties.getSecurity().getOAUTH2(); + OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2(); String usernameAttribute = oauth2.getUseAsUsername(); if (usernameAttribute == null || usernameAttribute.trim().isEmpty()) { Client client = oauth2.getClient(); 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 42addb2671..551cd72dae 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/CropController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/CropController.java @@ -13,6 +13,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -23,6 +24,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.general.CropPdfForm; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -32,6 +34,13 @@ public class CropController { private static final Logger logger = LoggerFactory.getLogger(CropController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public CropController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(value = "/crop", consumes = "multipart/form-data") @Operation( summary = "Crops a PDF document", @@ -40,7 +49,8 @@ public class CropController { public ResponseEntity cropPdf(@ModelAttribute CropPdfForm form) throws IOException { PDDocument sourceDocument = Loader.loadPDF(form.getFileInput().getBytes()); - PDDocument newDocument = new PDDocument(); + PDDocument newDocument = + pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); int totalPages = sourceDocument.getNumberOfPages(); 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 6bb2565094..9a60fcf6e9 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -22,6 +22,7 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -33,6 +34,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.general.MergePdfsRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -43,9 +45,16 @@ public class MergeController { private static final Logger logger = LoggerFactory.getLogger(MergeController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public MergeController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + // Merges a list of PDDocument objects into a single PDDocument public PDDocument mergeDocuments(List documents) throws IOException { - PDDocument mergedDoc = new PDDocument(); + PDDocument mergedDoc = pdfDocumentFactory.createNewDocument(); for (PDDocument doc : documents) { for (PDPage page : doc.getPages()) { mergedDoc.addPage(page); 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 adc5424d96..fbfec73131 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java @@ -14,6 +14,7 @@ import org.apache.pdfbox.util.Matrix; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -26,6 +27,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -35,6 +37,13 @@ public class MultiPageLayoutController { private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public MultiPageLayoutController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data") @Operation( summary = "Merge multiple pages of a PDF document into a single page", @@ -60,7 +69,8 @@ public ResponseEntity mergeMultiplePagesIntoOne( int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet); PDDocument sourceDocument = Loader.loadPDF(file.getBytes()); - PDDocument newDocument = new PDDocument(); + PDDocument newDocument = + pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); PDPage newPage = new PDPage(PDRectangle.A4); newDocument.addPage(newPage); diff --git a/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java b/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java index d56b7d2d20..302b00f4ca 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java @@ -3,16 +3,15 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import stirling.software.SPDF.model.api.PDFFile; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.service.PdfImageRemovalService; import stirling.software.SPDF.utils.WebResponseUtils; @@ -25,15 +24,21 @@ public class PdfImageRemovalController { // Service for removing images from PDFs - @Autowired private PdfImageRemovalService pdfImageRemovalService; + private final PdfImageRemovalService pdfImageRemovalService; + + private final CustomPDDocumentFactory pdfDocumentFactory; /** * Constructor for dependency injection of PdfImageRemovalService. * * @param pdfImageRemovalService The service used for removing images from PDFs. */ - public PdfImageRemovalController(PdfImageRemovalService pdfImageRemovalService) { + @Autowired + public PdfImageRemovalController( + PdfImageRemovalService pdfImageRemovalService, + CustomPDDocumentFactory pdfDocumentFactory) { this.pdfImageRemovalService = pdfImageRemovalService; + this.pdfDocumentFactory = pdfDocumentFactory; } /** @@ -53,14 +58,8 @@ public PdfImageRemovalController(PdfImageRemovalService pdfImageRemovalService) description = "This endpoint remove images from file to reduce the file size.Input:PDF Output:PDF Type:MISO") public ResponseEntity removeImages(@ModelAttribute PDFFile file) throws IOException { - - MultipartFile pdf = file.getFileInput(); - - // Convert the MultipartFile to a byte array - byte[] pdfBytes = pdf.getBytes(); - - // Load the PDF document from the byte array - PDDocument document = Loader.loadPDF(pdfBytes); + // Load the PDF document + PDDocument document = pdfDocumentFactory.load(file); // Remove images from the PDF document using the service PDDocument modifiedDocument = pdfImageRemovalService.removeImagesFromPdf(document); @@ -74,7 +73,8 @@ public ResponseEntity removeImages(@ModelAttribute PDFFile file) throws // Generate a new filename for the modified PDF String mergedFileName = - pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_images.pdf"; + file.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_removed_images.pdf"; // Convert the byte array to a web response and return it return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName); diff --git a/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java b/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java index 48c987cf0a..7ff17ccb5a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java @@ -12,6 +12,7 @@ import org.apache.pdfbox.Loader; import org.apache.pdfbox.multipdf.Overlay; import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -25,6 +26,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.general.OverlayPdfsRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -33,6 +35,13 @@ @Tag(name = "General", description = "General APIs") public class PdfOverlayController { + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public PdfOverlayController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data") @Operation( summary = "Overlay PDF files in various modes", @@ -56,7 +65,7 @@ public ResponseEntity overlayPdfs(@ModelAttribute OverlayPdfsRequest req // "FixedRepeatOverlay" int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode - try (PDDocument basePdf = Loader.loadPDF(baseFile.getBytes()); + try (PDDocument basePdf = pdfDocumentFactory.load(baseFile); Overlay overlay = new Overlay()) { Map overlayGuide = prepareOverlayGuide( 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 3a9ed47b11..8eb08dbf2a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java @@ -10,6 +10,7 @@ import org.apache.pdfbox.pdmodel.PDPage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -24,6 +25,7 @@ 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.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -34,6 +36,13 @@ public class RearrangePagesPDFController { private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public RearrangePagesPDFController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/remove-pages") @Operation( summary = "Remove pages from a PDF file", @@ -45,7 +54,7 @@ public ResponseEntity deletePages(@ModelAttribute PDFWithPageNums reques MultipartFile pdfFile = request.getFileInput(); String pagesToDelete = request.getPageNumbers(); - PDDocument document = Loader.loadPDF(pdfFile.getBytes()); + PDDocument document = pdfDocumentFactory.load(pdfFile); // Split the page order string into an array of page numbers or range of numbers String[] pageOrderArr = pagesToDelete.split(","); 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 76f508d3f4..1c416a071e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RotationController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RotationController.java @@ -2,12 +2,12 @@ import java.io.IOException; -import org.apache.pdfbox.Loader; 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.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.general.RotatePDFRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -29,6 +30,13 @@ public class RotationController { private static final Logger logger = LoggerFactory.getLogger(RotationController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public RotationController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf") @Operation( summary = "Rotate a PDF file", @@ -39,7 +47,7 @@ public ResponseEntity rotatePDF(@ModelAttribute RotatePDFRequest request MultipartFile pdfFile = request.getFileInput(); Integer angle = request.getAngle(); // Load the PDF document - PDDocument document = Loader.loadPDF(pdfFile.getBytes()); + PDDocument document = pdfDocumentFactory.load(request); // Get the list of pages in the document PDPageTree pages = document.getPages(); 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 5eb1205d06..9b7714ad2d 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java @@ -15,6 +15,7 @@ import org.apache.pdfbox.util.Matrix; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -27,6 +28,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.general.ScalePagesRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -36,6 +38,13 @@ public class ScalePagesController { private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public ScalePagesController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(value = "/scale-pages", consumes = "multipart/form-data") @Operation( summary = "Change the size of a PDF page/document", @@ -48,7 +57,8 @@ public ResponseEntity scalePages(@ModelAttribute ScalePagesRequest reque float scaleFactor = request.getScaleFactor(); PDDocument sourceDocument = Loader.loadPDF(file.getBytes()); - PDDocument outputDocument = new PDDocument(); + PDDocument outputDocument = + pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); PDRectangle targetSize = getTargetSize(targetPDRectangle, sourceDocument); 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 ab80cad34a..2da76fa363 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java @@ -15,6 +15,7 @@ import org.apache.pdfbox.pdmodel.PDPage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -27,9 +28,8 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import stirling.software.SPDF.model.PdfMetadata; import stirling.software.SPDF.model.api.PDFWithPageNums; -import stirling.software.SPDF.utils.PdfUtils; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -38,6 +38,12 @@ public class SplitPDFController { private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public SplitPDFController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } @PostMapping(consumes = "multipart/form-data", value = "/split-pages") @Operation( @@ -51,7 +57,7 @@ public ResponseEntity splitPdf(@ModelAttribute PDFWithPageNums request) // open the pdf document PDDocument document = Loader.loadPDF(file.getBytes()); - PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document); + // PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document); int totalPages = document.getNumberOfPages(); List pageNumbers = request.getPageNumbersList(document, false); System.out.println( @@ -70,7 +76,8 @@ public ResponseEntity splitPdf(@ModelAttribute PDFWithPageNums request) List splitDocumentsBoas = new ArrayList<>(); int previousPageNumber = 0; for (int splitPoint : pageNumbers) { - try (PDDocument splitDocument = new PDDocument()) { + try (PDDocument splitDocument = + pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) { for (int i = previousPageNumber; i <= splitPoint; i++) { PDPage page = document.getPage(i); splitDocument.addPage(page); @@ -79,7 +86,7 @@ public ResponseEntity splitPdf(@ModelAttribute PDFWithPageNums request) previousPageNumber = splitPoint + 1; // Transfer metadata to split pdf - PdfUtils.setMetadataToPdf(splitDocument, metadata); + // PdfMetadataService.setMetadataToPdf(splitDocument, metadata); ByteArrayOutputStream baos = new ByteArrayOutputStream(); splitDocument.save(baos); diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java index 776856ecef..9d03f8b6b0 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java @@ -15,6 +15,7 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -31,9 +32,9 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import stirling.software.SPDF.config.PdfMetadataService; import stirling.software.SPDF.model.PdfMetadata; import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest; -import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -44,6 +45,13 @@ public class SplitPdfByChaptersController { private static final Logger logger = LoggerFactory.getLogger(SplitPdfByChaptersController.class); + private final PdfMetadataService pdfMetadataService; + + @Autowired + public SplitPdfByChaptersController(PdfMetadataService pdfMetadataService) { + this.pdfMetadataService = pdfMetadataService; + } + @PostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data") @Operation( summary = "Split PDFs by Chapters", @@ -258,7 +266,7 @@ public List getSplitDocumentsBoas( List splitDocumentsBoas = new ArrayList<>(); PdfMetadata metadata = null; if (includeMetadata) { - metadata = PdfUtils.extractMetadataFromPdf(sourceDocument); + metadata = pdfMetadataService.extractMetadataFromPdf(sourceDocument); } for (Bookmark bookmark : bookmarks) { try (PDDocument splitDocument = new PDDocument()) { @@ -273,7 +281,7 @@ public List getSplitDocumentsBoas( } ByteArrayOutputStream baos = new ByteArrayOutputStream(); if (includeMetadata) { - PdfUtils.setMetadataToPdf(splitDocument, metadata); + pdfMetadataService.setMetadataToPdf(splitDocument, metadata); } splitDocument.save(baos); diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java index ce4a61d490..2b4f131338 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java @@ -20,6 +20,7 @@ import org.apache.pdfbox.util.Matrix; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -33,6 +34,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -43,6 +45,13 @@ public class SplitPdfBySectionsController { private static final Logger logger = LoggerFactory.getLogger(SplitPdfBySectionsController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public SplitPdfBySectionsController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data") @Operation( summary = "Split PDF pages into smaller sections", @@ -65,7 +74,7 @@ public ResponseEntity splitPdf(@ModelAttribute SplitPdfBySectionsRequest Filenames.toSimpleFileName(file.getOriginalFilename()) .replaceFirst("[.][^.]+$", ""); if (merge) { - MergeController mergeController = new MergeController(); + MergeController mergeController = new MergeController(pdfDocumentFactory); ByteArrayOutputStream baos = new ByteArrayOutputStream(); mergeController.mergeDocuments(splitDocuments).save(baos); return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf"); diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java index b3772b3b4e..6016e532b7 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java @@ -12,6 +12,7 @@ import org.apache.pdfbox.pdmodel.PDPage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -25,6 +26,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -34,6 +36,12 @@ public class SplitPdfBySizeController { private static final Logger logger = LoggerFactory.getLogger(SplitPdfBySizeController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public SplitPdfBySizeController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } @PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data") @Operation( @@ -84,7 +92,8 @@ private void handleSplitBySize( PDDocument sourceDocument, long maxBytes, ZipOutputStream zipOut, String baseFilename) throws IOException { long currentSize = 0; - PDDocument currentDoc = new PDDocument(); + PDDocument currentDoc = + pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); int fileIndex = 1; for (int pageIndex = 0; pageIndex < sourceDocument.getNumberOfPages(); pageIndex++) { @@ -121,7 +130,8 @@ private void handleSplitByPageCount( PDDocument sourceDocument, int pageCount, ZipOutputStream zipOut, String baseFilename) throws IOException { int currentPageCount = 0; - PDDocument currentDoc = new PDDocument(); + PDDocument currentDoc = + pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); int fileIndex = 1; for (PDPage page : sourceDocument.getPages()) { currentDoc.addPage(page); @@ -152,7 +162,8 @@ private void handleSplitByDocCount( int currentPageIndex = 0; int fileIndex = 1; for (int i = 0; i < documentCount; i++) { - PDDocument currentDoc = new PDDocument(); + PDDocument currentDoc = + pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0); for (int j = 0; j < pagesToAdd; j++) { 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 30406ec2a7..9401f3a627 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java @@ -13,6 +13,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -23,6 +24,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.PDFFile; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -32,6 +34,13 @@ public class ToSinglePageController { private static final Logger logger = LoggerFactory.getLogger(ToSinglePageController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public ToSinglePageController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page") @Operation( summary = "Convert a multi-page PDF into a single long page PDF", @@ -53,7 +62,8 @@ public ResponseEntity pdfToSinglePage(@ModelAttribute PDFFile request) } // Create new document and page with calculated dimensions - PDDocument newDocument = new PDDocument(); + PDDocument newDocument = + pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight)); newDocument.addPage(newPage); diff --git a/src/main/java/stirling/software/SPDF/controller/api/UserController.java b/src/main/java/stirling/software/SPDF/controller/api/UserController.java index bc59b91e57..719adac19a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/UserController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/UserController.java @@ -208,8 +208,8 @@ public String updateUserSettings(HttpServletRequest request, Principal principal @PreAuthorize("hasRole('ROLE_ADMIN')") @PostMapping("/admin/saveUser") public RedirectView saveUser( - @RequestParam String username, - @RequestParam(name = "password", required = false) String password, + @RequestParam(name = "username", required = true) String username, + @RequestParam(name = "password", required = true) String password, @RequestParam(name = "role") String role, @RequestParam(name = "authType") String authType, @RequestParam(name = "forceChange", required = false, defaultValue = "false") diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java index 41e6520ddc..694d30ab88 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java @@ -1,30 +1,36 @@ package stirling.software.SPDF.controller.api.converters; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; 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.github.pixee.security.Filenames; 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.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.WebResponseUtils; -@RestController -@Tag(name = "Convert", description = "Convert APIs") -@RequestMapping("/api/v1/convert") +// Disabled for now +// @RestController +// @Tag(name = "Convert", description = "Convert APIs") +// @RequestMapping("/api/v1/convert") public class ConvertBookToPDFController { - @Autowired - @Qualifier("bookAndHtmlFormatsInstalled") - private boolean bookAndHtmlFormatsInstalled; + private final boolean bookAndHtmlFormatsInstalled; + + private final CustomPDDocumentFactory pdfDocumentFactory; + + // @Autowired + public ConvertBookToPDFController( + CustomPDDocumentFactory pdfDocumentFactory, + @Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) { + this.pdfDocumentFactory = pdfDocumentFactory; + this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled; + } @PostMapping(consumes = "multipart/form-data", value = "/book/pdf") @Operation( 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 c7cfc19643..19ba1ac43a 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,28 +1,25 @@ package stirling.software.SPDF.controller.api.converters; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; 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.github.pixee.security.Filenames; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest; import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.WebResponseUtils; -@RestController -@Tag(name = "Convert", description = "Convert APIs") -@RequestMapping("/api/v1/convert") +// Disabled for now +// @RestController +// @Tag(name = "Convert", description = "Convert APIs") +// @RequestMapping("/api/v1/convert") public class ConvertHtmlToPDF { - @Autowired + // @Autowired @Qualifier("bookAndHtmlFormatsInstalled") private boolean bookAndHtmlFormatsInstalled; 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 e54f1fe410..8cf4246ecd 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 @@ -16,6 +16,7 @@ import org.apache.pdfbox.rendering.ImageType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -30,6 +31,7 @@ import stirling.software.SPDF.model.api.converters.ConvertToImageRequest; import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.CheckProgramInstall; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.ProcessExecutor; @@ -43,6 +45,13 @@ public class ConvertImgPDFController { private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public ConvertImgPDFController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/pdf/img") @Operation( summary = "Convert PDF to image(s)", @@ -178,7 +187,8 @@ public ResponseEntity convertToPdf(@ModelAttribute ConvertToPdfRequest r boolean autoRotate = request.isAutoRotate(); // Convert the file to PDF and get the resulting bytes - byte[] bytes = PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType); + byte[] bytes = + PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory); 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 9cefe1ff80..5b2f3fdf5b 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 @@ -10,29 +10,26 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.HtmlRenderer; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; 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.github.pixee.security.Filenames; 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") +// Disabled for now +// @RestController +// @Tag(name = "Convert", description = "Convert APIs") +// @RequestMapping("/api/v1/convert") public class ConvertMarkdownToPdf { - @Autowired + // @Autowired @Qualifier("bookAndHtmlFormatsInstalled") private boolean bookAndHtmlFormatsInstalled; 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 5e6b3dfdc6..36e29f2750 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 @@ -1,5 +1,6 @@ package stirling.software.SPDF.controller.api.converters; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -8,6 +9,8 @@ import java.util.List; import org.apache.commons.io.FilenameUtils; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -20,6 +23,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.GeneralFile; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @@ -29,7 +33,7 @@ @RequestMapping("/api/v1/convert") public class ConvertOfficeController { - public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { + public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { // Check for valid file extension String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename()); if (originalFilename == null @@ -62,12 +66,10 @@ public byte[] convertToPdf(MultipartFile inputFile) throws IOException, Interrup .runCommandWithOutputHandling(command); // Read the converted PDF file - byte[] pdfBytes = Files.readAllBytes(tempOutputFile); - return pdfBytes; + return tempOutputFile.toFile(); } finally { // Clean up the temporary files if (tempInputFile != null) Files.deleteIfExists(tempInputFile); - Files.deleteIfExists(tempOutputFile); } } @@ -76,6 +78,13 @@ private boolean isValidFileExtension(String fileExtension) { return fileExtension.matches(extensionPattern); } + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public ConvertOfficeController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/file/pdf") @Operation( summary = "Convert a file to a PDF using LibreOffice", @@ -86,12 +95,18 @@ public ResponseEntity processFileToPDF(@ModelAttribute GeneralFile reque MultipartFile inputFile = request.getFileInput(); // unused but can start server instance if startup time is to long // LibreOfficeListener.getInstance().start(); + File file = null; + try { + file = convertToPdf(inputFile); - byte[] pdfByteArray = convertToPdf(inputFile); - return WebResponseUtils.bytesToWebResponse( - pdfByteArray, - Filenames.toSimpleFileName(inputFile.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") - + "_convertedToPDF.pdf"); + PDDocument doc = pdfDocumentFactory.load(file); + return WebResponseUtils.pdfDocToWebResponse( + doc, + Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + .replaceFirst("[.][^.]+$", "") + + "_convertedToPDF.pdf"); + } finally { + if (file != null) file.delete(); + } } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java index c8b9dd4dc8..181b5713ad 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java @@ -6,30 +6,27 @@ import java.util.Arrays; import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; 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.github.pixee.security.Filenames; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.converters.PdfToBookRequest; 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") +// Disabled for now +// @RestController +// @Tag(name = "Convert", description = "Convert APIs") +// @RequestMapping("/api/v1/convert") public class ConvertPDFToBookController { - @Autowired + // @Autowired @Qualifier("bookAndHtmlFormatsInstalled") private boolean bookAndHtmlFormatsInstalled; 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 cd748d533d..14aef73462 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,22 +1,15 @@ package stirling.software.SPDF.controller.api.converters; -import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; -import org.apache.pdfbox.Loader; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDDocumentCatalog; -import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; -import org.apache.pdfbox.pdmodel.interactive.form.PDField; -import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; 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; @@ -53,32 +46,7 @@ public ResponseEntity pdfToPdfA(@ModelAttribute PdfToPdfARequest request // Convert MultipartFile to byte[] byte[] pdfBytes = inputFile.getBytes(); - // Load the PDF document - PDDocument document = Loader.loadPDF(pdfBytes); - - // Get the document catalog - PDDocumentCatalog catalog = document.getDocumentCatalog(); - - // Get the AcroForm - PDAcroForm acroForm = catalog.getAcroForm(); - if (acroForm != null) { - // Remove signature fields safely - List fieldsToRemove = - acroForm.getFields().stream() - .filter(field -> field instanceof PDSignatureField) - .collect(Collectors.toList()); - - if (!fieldsToRemove.isEmpty()) { - acroForm.flatten(fieldsToRemove, false); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - document.save(baos); - pdfBytes = baos.toByteArray(); - } - } - document.close(); - - // Save the uploaded (and possibly modified) file to a temporary location + // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); try (OutputStream outputStream = new FileOutputStream(tempInputFile.toFile())) { outputStream.write(pdfBytes); @@ -87,32 +55,41 @@ public ResponseEntity pdfToPdfA(@ModelAttribute PdfToPdfARequest request // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - // Prepare the OCRmyPDF command + // Prepare the ghostscript command List command = new ArrayList<>(); - command.add("ocrmypdf"); - command.add("--skip-text"); - command.add("--tesseract-timeout=0"); - command.add("--output-type"); - command.add(outputFormat.toString()); - command.add(tempInputFile.toString()); + command.add("gs"); + command.add("-dPDFA=" + ("pdfa".equals(outputFormat) ? "2" : "1")); + command.add("-dNOPAUSE"); + command.add("-dBATCH"); + command.add("-sColorConversionStrategy=UseDeviceIndependentColor"); + command.add("-sDEVICE=pdfwrite"); + command.add("-dPDFACompatibilityPolicy=2"); + command.add("-o"); command.add(tempOutputFile.toString()); + command.add(tempInputFile.toString()); ProcessExecutorResult returnCode = - ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF) + ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) .runCommandWithOutputHandling(command); - // Read the optimized PDF file - byte[] optimizedPdfBytes = Files.readAllBytes(tempOutputFile); - - // Clean up the temporary files - Files.deleteIfExists(tempInputFile); - Files.deleteIfExists(tempOutputFile); + if (returnCode.getRc() != 0) { + logger.info( + outputFormat + " conversion failed with return code: " + returnCode.getRc()); + } - // Return the optimized PDF as a response - String outputFilename = - Filenames.toSimpleFileName(inputFile.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") - + "_PDFA.pdf"; - return WebResponseUtils.bytesToWebResponse(optimizedPdfBytes, outputFilename); + try { + byte[] pdfBytesOutput = Files.readAllBytes(tempOutputFile); + // Return the optimized PDF as a response + String outputFilename = + Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + .replaceFirst("[.][^.]+$", "") + + "_PDFA.pdf"; + return WebResponseUtils.bytesToWebResponse( + pdfBytesOutput, outputFilename, MediaType.APPLICATION_PDF); + } finally { + // Clean up the temporary files + Files.deleteIfExists(tempInputFile); + Files.deleteIfExists(tempOutputFile); + } } } 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 026690fd65..6119bfcfb0 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 @@ -6,6 +6,10 @@ import java.util.ArrayList; import java.util.List; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -16,6 +20,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.converters.UrlToPdfRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; @@ -26,6 +31,15 @@ @RequestMapping("/api/v1/convert") public class ConvertWebsiteToPDF { + private static final Logger logger = LoggerFactory.getLogger(ConvertWebsiteToPDF.class); + + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public ConvertWebsiteToPDF(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/url/pdf") @Operation( summary = "Convert a URL to a PDF", @@ -46,12 +60,12 @@ public ResponseEntity urlToPdf(@ModelAttribute UrlToPdfRequest request) } Path tempOutputFile = null; - byte[] pdfBytes; + PDDocument doc = null; try { // Prepare the output file path tempOutputFile = Files.createTempFile("output_", ".pdf"); - // Prepare the OCRmyPDF command + // Prepare the WeasyPrint command List command = new ArrayList<>(); command.add("weasyprint"); command.add(URL); @@ -61,16 +75,23 @@ public ResponseEntity urlToPdf(@ModelAttribute UrlToPdfRequest request) ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) .runCommandWithOutputHandling(command); - // Read the optimized PDF file - pdfBytes = Files.readAllBytes(tempOutputFile); + // Load the PDF using pdfDocumentFactory + doc = pdfDocumentFactory.load(tempOutputFile.toFile()); + + // Convert URL to a safe filename + String outputFilename = convertURLToFileName(URL); + + return WebResponseUtils.pdfDocToWebResponse(doc, outputFilename); } finally { - // Clean up the temporary files - Files.deleteIfExists(tempOutputFile); - } - // Convert URL to a safe filename - String outputFilename = convertURLToFileName(URL); - return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + if (tempOutputFile != null) { + try { + Files.deleteIfExists(tempOutputFile); + } catch (IOException e) { + logger.error("Error deleting temporary output file", e); + } + } + } } private String convertURLToFileName(String url) { diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractCSVController.java similarity index 98% rename from src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java rename to src/main/java/stirling/software/SPDF/controller/api/converters/ExtractCSVController.java index 6bd9d8b999..8cf8aa4fdb 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractCSVController.java @@ -30,13 +30,13 @@ @RestController @RequestMapping("/api/v1/convert") @Tag(name = "Convert", description = "Convert APIs") -public class ExtractController { +public class ExtractCSVController { private static final Logger logger = LoggerFactory.getLogger(CropController.class); @PostMapping(value = "/pdf/csv", consumes = "multipart/form-data") @Operation( - summary = "Extracts a PDF document to csv", + summary = "Extracts a CSV document from a PDF", description = "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO") public ResponseEntity PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception { diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java index d57a8bdaec..88d113ec6f 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java @@ -12,11 +12,11 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.rendering.PDFRenderer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -38,6 +38,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -49,6 +50,13 @@ public class AutoSplitPdfController { private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF"; private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF"; + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public AutoSplitPdfController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data") @Operation( summary = "Auto split PDF pages into separate documents", @@ -59,73 +67,93 @@ public ResponseEntity autoSplitPdf(@ModelAttribute AutoSplitPdfRequest r MultipartFile file = request.getFileInput(); boolean duplexMode = request.isDuplexMode(); - PDDocument document = Loader.loadPDF(file.getBytes()); - PDFRenderer pdfRenderer = new PDFRenderer(document); - pdfRenderer.setSubsamplingAllowed(true); + PDDocument document = null; List splitDocuments = new ArrayList<>(); - List splitDocumentsBoas = new ArrayList<>(); + Path zipFile = null; + byte[] data = null; - for (int page = 0; page < document.getNumberOfPages(); ++page) { - BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150); - String result = decodeQRCode(bim); - if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) { - splitDocuments.add(new PDDocument()); + try { + document = pdfDocumentFactory.load(file.getInputStream()); + PDFRenderer pdfRenderer = new PDFRenderer(document); + pdfRenderer.setSubsamplingAllowed(true); + + for (int page = 0; page < document.getNumberOfPages(); ++page) { + BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150); + String result = decodeQRCode(bim); + if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) { + splitDocuments.add(new PDDocument()); + } + + if (!splitDocuments.isEmpty() + && !QR_CONTENT.equals(result) + && !QR_CONTENT_OLD.equals(result)) { + splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page)); + } else if (page == 0) { + PDDocument firstDocument = new PDDocument(); + firstDocument.addPage(document.getPage(page)); + splitDocuments.add(firstDocument); + } + + // If duplexMode is true and current page is a divider, then skip next page + if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) { + page++; + } } - if (!splitDocuments.isEmpty() - && !QR_CONTENT.equals(result) - && !QR_CONTENT_OLD.equals(result)) { - splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page)); - } else if (page == 0) { - PDDocument firstDocument = new PDDocument(); - firstDocument.addPage(document.getPage(page)); - splitDocuments.add(firstDocument); - } + // Remove split documents that have no pages + splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0); - // If duplexMode is true and current page is a divider, then skip next page - if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) { - page++; - } - } + zipFile = Files.createTempFile("split_documents", ".zip"); + String filename = + Filenames.toSimpleFileName(file.getOriginalFilename()) + .replaceFirst("[.][^.]+$", ""); - // Remove split documents that have no pages - splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0); + try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { + for (int i = 0; i < splitDocuments.size(); i++) { + String fileName = filename + "_" + (i + 1) + ".pdf"; + PDDocument splitDocument = splitDocuments.get(i); - for (PDDocument splitDocument : splitDocuments) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - splitDocument.save(baos); - splitDocumentsBoas.add(baos); - splitDocument.close(); - } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + splitDocument.save(baos); + byte[] pdf = baos.toByteArray(); - document.close(); + ZipEntry pdfEntry = new ZipEntry(fileName); + zipOut.putNextEntry(pdfEntry); + zipOut.write(pdf); + zipOut.closeEntry(); + } + } - Path zipFile = Files.createTempFile("split_documents", ".zip"); - String filename = - Filenames.toSimpleFileName(file.getOriginalFilename()) - .replaceFirst("[.][^.]+$", ""); - byte[] data; + data = Files.readAllBytes(zipFile); - try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { - for (int i = 0; i < splitDocumentsBoas.size(); i++) { - String fileName = filename + "_" + (i + 1) + ".pdf"; - ByteArrayOutputStream baos = splitDocumentsBoas.get(i); - byte[] pdf = baos.toByteArray(); + return WebResponseUtils.bytesToWebResponse( + data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); + } finally { + // Clean up resources + if (document != null) { + try { + document.close(); + } catch (IOException e) { + logger.error("Error closing main PDDocument", e); + } + } - ZipEntry pdfEntry = new ZipEntry(fileName); - zipOut.putNextEntry(pdfEntry); - zipOut.write(pdf); - zipOut.closeEntry(); + for (PDDocument splitDoc : splitDocuments) { + try { + splitDoc.close(); + } catch (IOException e) { + logger.error("Error closing split PDDocument", e); + } } - } catch (Exception e) { - logger.error("exception", e); - } finally { - data = Files.readAllBytes(zipFile); - Files.deleteIfExists(zipFile); - } - return WebResponseUtils.bytesToWebResponse( - data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); + if (zipFile != null) { + try { + Files.deleteIfExists(zipFile); + } catch (IOException e) { + logger.error("Error deleting temporary zip file", e); + } + } + } } private static String decodeQRCode(BufferedImage bufferedImage) { diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java index 4c05035536..bf046d656a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java @@ -16,6 +16,7 @@ import org.apache.pdfbox.text.PDFTextStripper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -30,6 +31,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -40,6 +42,13 @@ public class BlankPageController { private static final Logger logger = LoggerFactory.getLogger(BlankPageController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public BlankPageController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/remove-blanks") @Operation( summary = "Remove blank pages from a PDF file", @@ -124,7 +133,7 @@ public ResponseEntity removeBlankPages(@ModelAttribute RemoveBlankPagesR public void createZipEntry(ZipOutputStream zos, List pages, String entryName) throws IOException { - try (PDDocument document = new PDDocument()) { + try (PDDocument document = pdfDocumentFactory.createNewDocument()) { for (PDPage page : pages) { document.addPage(page); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java index 2240dbed22..3f55a4f510 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java @@ -20,6 +20,7 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -32,6 +33,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.misc.OptimizePdfRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; @@ -44,6 +46,13 @@ public class CompressController { private static final Logger logger = LoggerFactory.getLogger(CompressController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public CompressController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/compress-pdf") @Operation( summary = "Optimize PDF file", @@ -258,7 +267,7 @@ public ResponseEntity optimizePdf(@ModelAttribute OptimizePdfRequest req } // Read the optimized PDF file pdfBytes = Files.readAllBytes(tempOutputFile); - + Path finalFile = tempOutputFile; // Check if optimized file is larger than the original if (pdfBytes.length > inputFileSize) { // Log the occurrence @@ -266,14 +275,15 @@ public ResponseEntity optimizePdf(@ModelAttribute OptimizePdfRequest req "Optimized file is larger than the original. Returning the original file instead."); // Read the original file again - pdfBytes = Files.readAllBytes(tempInputFile); + finalFile = tempInputFile; } // Return the optimized PDF as a response String outputFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename()) .replaceFirst("[.][^.]+$", "") + "_Optimized.pdf"; - return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + return WebResponseUtils.pdfDocToWebResponse( + pdfDocumentFactory.load(finalFile.toFile()), outputFilename); } finally { // Clean up the temporary files diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java index 266fbd35bb..3dff43e849 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java @@ -90,22 +90,35 @@ public ResponseEntity extractImages(@ModelAttribute PDFExtractImagesRequ // Iterate over each page for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) { PDPage page = document.getPage(pgNum); - int pageNum = document.getPages().indexOf(page) + 1; - // Submit a task for processing each page Future future = executor.submit( () -> { - extractImagesFromPage( - page, - format, - filename, - pageNum, - processedImages, - zos, - allowDuplicates); - return null; + // Use the page number directly from the iterator, so no need to + // calculate manually + int pageNum = document.getPages().indexOf(page) + 1; + + try { + // Call the image extraction method for each page + extractImagesFromPage( + page, + format, + filename, + pageNum, + processedImages, + zos, + allowDuplicates); + } catch (IOException e) { + // Log the error and continue processing other pages + logger.error( + "Error extracting images from page {}: {}", + pageNum, + e.getMessage()); + } + + return null; // Callable requires a return type }); + // Add the Future object to the list to track completion futures.add(future); } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/FlattenController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/FlattenController.java index 888d76702b..c99b5f730c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/FlattenController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/FlattenController.java @@ -14,6 +14,7 @@ import org.apache.pdfbox.rendering.PDFRenderer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -25,9 +26,8 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import stirling.software.SPDF.model.PdfMetadata; import stirling.software.SPDF.model.api.misc.FlattenRequest; -import stirling.software.SPDF.utils.PdfUtils; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -37,6 +37,13 @@ public class FlattenController { private static final Logger logger = LoggerFactory.getLogger(FlattenController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public FlattenController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/flatten") @Operation( summary = "Flatten PDF form fields or full page", @@ -46,7 +53,6 @@ public ResponseEntity flatten(@ModelAttribute FlattenRequest request) th MultipartFile file = request.getFileInput(); PDDocument document = Loader.loadPDF(file.getBytes()); - PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document); Boolean flattenOnlyForms = request.getFlattenOnlyForms(); if (Boolean.TRUE.equals(flattenOnlyForms)) { @@ -60,7 +66,8 @@ public ResponseEntity flatten(@ModelAttribute FlattenRequest request) th // flatten whole page aka convert each page to image and readd it (making text // unselectable) PDFRenderer pdfRenderer = new PDFRenderer(document); - PDDocument newDocument = new PDDocument(); + PDDocument newDocument = + pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document); int numPages = document.getNumberOfPages(); for (int i = 0; i < numPages; i++) { try { @@ -80,7 +87,6 @@ public ResponseEntity flatten(@ModelAttribute FlattenRequest request) th logger.error("exception", e); } } - PdfUtils.setMetadataToPdf(newDocument, metadata); return WebResponseUtils.pdfDocToWebResponse( newDocument, Filenames.toSimpleFileName(file.getOriginalFilename())); } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java index f3ccabf425..96cabb60d7 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java @@ -1,5 +1,6 @@ package stirling.software.SPDF.controller.api.misc; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -28,6 +29,7 @@ import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @@ -52,6 +54,13 @@ public List getAvailableTesseractLanguages() { .collect(Collectors.toList()); } + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public OCRController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf") @Operation( summary = "Process a PDF file with OCR", @@ -175,7 +184,7 @@ public ResponseEntity processPdfWithOCR( tempOutputFile = tempPdfWithoutImages; } // Read the OCR processed PDF file - byte[] pdfBytes = Files.readAllBytes(tempOutputFile); + byte[] pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile()); // Return the OCR processed PDF as a response String outputFilename = @@ -196,7 +205,13 @@ public ResponseEntity processPdfWithOCR( // Add PDF file to the zip ZipEntry pdfEntry = new ZipEntry(outputFilename); zipOut.putNextEntry(pdfEntry); - Files.copy(tempOutputFile, zipOut); + try (ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes)) { + byte[] buffer = new byte[1024]; + int length; + while ((length = pdfInputStream.read(buffer)) != -1) { + zipOut.write(buffer, 0, length); + } + } zipOut.closeEntry(); // Add text file to the zip diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java index c3fad45722..893fc1e5da 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -17,6 +18,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.misc.OverlayImageRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -27,6 +29,13 @@ public class OverlayImageController { private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public OverlayImageController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/add-image") @Operation( summary = "Overlay image onto a PDF file", @@ -41,7 +50,9 @@ public ResponseEntity overlayImage(@ModelAttribute OverlayImageRequest r try { byte[] pdfBytes = pdfFile.getBytes(); byte[] imageBytes = imageFile.getBytes(); - byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y, everyPage); + byte[] result = + PdfUtils.overlayImage( + pdfDocumentFactory, pdfBytes, imageBytes, x, y, everyPage); return WebResponseUtils.bytesToWebResponse( result, 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 index ba31dc83a6..e42998d143 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.util.List; -import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; @@ -13,6 +12,7 @@ import org.apache.pdfbox.pdmodel.font.Standard14Fonts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -26,6 +26,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -36,6 +37,13 @@ public class PageNumbersController { private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public PageNumbersController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data") @Operation( summary = "Add page numbers to a PDF document", @@ -52,7 +60,7 @@ public ResponseEntity addPageNumbers(@ModelAttribute AddPageNumbersReque String customText = request.getCustomText(); int pageNumber = startingNumber; byte[] fileBytes = file.getBytes(); - PDDocument document = Loader.loadPDF(fileBytes); + PDDocument document = pdfDocumentFactory.load(fileBytes); float font_size = request.getFontSize(); String font_type = request.getFontType(); float marginFactor; diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java index 06c652a279..be0827cc92 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java @@ -8,6 +8,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -20,6 +21,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.PDFFile; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @@ -31,6 +33,13 @@ public class RepairController { private static final Logger logger = LoggerFactory.getLogger(RepairController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public RepairController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/repair") @Operation( summary = "Repair a PDF file", @@ -58,7 +67,7 @@ public ResponseEntity repairPdf(@ModelAttribute PDFFile request) .runCommandWithOutputHandling(command); // Read the optimized PDF file - pdfBytes = Files.readAllBytes(tempOutputFile); + pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile()); // Return the optimized PDF as a response String outputFilename = 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 index 0a93bf1d4b..a608fde951 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ShowJavascript.java @@ -9,6 +9,7 @@ import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; 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; @@ -75,7 +76,8 @@ public ResponseEntity extractHeader(@ModelAttribute PDFFile request) thr return WebResponseUtils.bytesToWebResponse( script.getBytes(StandardCharsets.UTF_8), - Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js"); + Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js", + MediaType.TEXT_PLAIN); } } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java index ecf4155712..b15b76e7f9 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java @@ -12,7 +12,6 @@ import javax.imageio.ImageIO; import org.apache.commons.io.IOUtils; -import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; @@ -25,6 +24,7 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.util.Matrix; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -38,6 +38,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.misc.AddStampRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -45,6 +46,13 @@ @Tag(name = "Misc", description = "Miscellaneous APIs") public class StampController { + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public StampController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/add-stamp") @Operation( summary = "Add stamp to a PDF file", @@ -86,7 +94,7 @@ public ResponseEntity addStamp(@ModelAttribute AddStampRequest request) } // Load the input PDF - PDDocument document = Loader.loadPDF(pdfFile.getBytes()); + PDDocument document = pdfDocumentFactory.load(pdfFile); List pageNumbers = request.getPageNumbersList(document, true); diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java index 1a60441e1c..c3facb35e1 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java @@ -2,4 +2,6 @@ public interface UserServiceInterface { String getApiKeyForUser(String username); + + String getCurrentUsername(); } 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 6ff75ebd48..c37fcc9c6e 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 @@ -16,7 +16,6 @@ import java.security.cert.CertificateFactory; import java.util.Calendar; -import org.apache.pdfbox.Loader; import org.apache.pdfbox.examples.signature.CreateSignatureBase; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; @@ -35,6 +34,7 @@ import org.bouncycastle.pkcs.PKCSException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -47,6 +47,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -71,6 +72,13 @@ public CreateSignature(KeyStore keystore, char[] pin) } } + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public CertSignController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/cert-sign") @Operation( summary = "Sign PDF with a Digital Certificate", @@ -122,7 +130,7 @@ public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertReq CreateSignature createSignature = new CreateSignature(ks, password.toCharArray()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - sign(pdf.getBytes(), baos, createSignature, name, location, reason); + sign(pdfDocumentFactory, pdf.getBytes(), baos, createSignature, name, location, reason); return WebResponseUtils.boasToWebResponse( baos, Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "") @@ -130,13 +138,14 @@ public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertReq } private static void sign( + CustomPDDocumentFactory pdfDocumentFactory, byte[] input, OutputStream output, CreateSignature instance, String name, String location, String reason) { - try (PDDocument doc = Loader.loadPDF(input)) { + try (PDDocument doc = pdfDocumentFactory.load(input)) { PDSignature signature = new PDSignature(); signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); 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 84c4493314..d738ae795b 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 @@ -2,12 +2,12 @@ import java.io.IOException; -import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.encryption.AccessPermission; import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -21,6 +21,7 @@ import stirling.software.SPDF.model.api.security.AddPasswordRequest; import stirling.software.SPDF.model.api.security.PDFPasswordRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -30,6 +31,13 @@ public class PasswordController { private static final Logger logger = LoggerFactory.getLogger(PasswordController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public PasswordController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/remove-password") @Operation( summary = "Remove password from a PDF file", @@ -39,8 +47,7 @@ public ResponseEntity removePassword(@ModelAttribute PDFPasswordRequest throws IOException { MultipartFile fileInput = request.getFileInput(); String password = request.getPassword(); - - PDDocument document = Loader.loadPDF(fileInput.getBytes(), password); + PDDocument document = pdfDocumentFactory.load(fileInput, password); document.setAllSecurityToBeRemoved(true); return WebResponseUtils.pdfDocToWebResponse( document, @@ -69,7 +76,7 @@ public ResponseEntity addPassword(@ModelAttribute AddPasswordRequest req boolean canPrint = request.isCanPrint(); boolean canPrintFaithful = request.isCanPrintFaithful(); - PDDocument document = Loader.loadPDF(fileInput.getBytes()); + PDDocument document = pdfDocumentFactory.load(fileInput); AccessPermission ap = new AccessPermission(); ap.setCanAssembleDocument(!canAssembleDocument); ap.setCanExtractContent(!canExtractContent); 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 index b3eec2b11e..40dc2c75de 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java @@ -5,12 +5,12 @@ import java.io.IOException; import java.util.List; -import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -25,6 +25,7 @@ import stirling.software.SPDF.model.PDFText; import stirling.software.SPDF.model.api.security.RedactPdfRequest; import stirling.software.SPDF.pdf.TextFinder; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -35,6 +36,13 @@ public class RedactController { private static final Logger logger = LoggerFactory.getLogger(RedactController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public RedactController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(value = "/auto-redact", consumes = "multipart/form-data") @Operation( summary = "Redacts listOfText in a PDF document", @@ -52,8 +60,7 @@ public ResponseEntity redactPdf(@ModelAttribute RedactPdfRequest request System.out.println(listOfTextString); String[] listOfText = listOfTextString.split("\n"); - byte[] bytes = file.getBytes(); - PDDocument document = Loader.loadPDF(bytes); + PDDocument document = pdfDocumentFactory.load(file); Color redactColor; try { diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/RemoveCertSignController.java b/src/main/java/stirling/software/SPDF/controller/api/security/RemoveCertSignController.java index 6a131a98c1..bd4c8c3d22 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/RemoveCertSignController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/RemoveCertSignController.java @@ -1,10 +1,8 @@ package stirling.software.SPDF.controller.api.security; -import java.io.ByteArrayOutputStream; import java.util.List; import java.util.stream.Collectors; -import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; @@ -12,6 +10,7 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -24,6 +23,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.PDFFile; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -33,6 +33,13 @@ public class RemoveCertSignController { private static final Logger logger = LoggerFactory.getLogger(RemoveCertSignController.class); + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public RemoveCertSignController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/remove-cert-sign") @Operation( summary = "Remove digital signature from PDF", @@ -42,14 +49,8 @@ public ResponseEntity removeCertSignPDF(@ModelAttribute PDFFile request) throws Exception { MultipartFile pdf = request.getFileInput(); - // Convert MultipartFile to byte[] - byte[] pdfBytes = pdf.getBytes(); - - // Create a ByteArrayOutputStream to hold the resulting PDF - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - // Load the PDF document - PDDocument document = Loader.loadPDF(pdfBytes); + PDDocument document = pdfDocumentFactory.load(pdf); // Get the document catalog PDDocumentCatalog catalog = document.getDocumentCatalog(); @@ -67,14 +68,9 @@ public ResponseEntity removeCertSignPDF(@ModelAttribute PDFFile request) acroForm.flatten(fieldsToRemove, false); } } - - // Save the modified document to the ByteArrayOutputStream - document.save(baos); - document.close(); - // Return the modified PDF as a response - return WebResponseUtils.boasToWebResponse( - baos, + return WebResponseUtils.pdfDocToWebResponse( + document, Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "") + "_unsigned.pdf"); } 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 dd2c79da70..b034d45d66 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 @@ -2,7 +2,6 @@ import java.io.IOException; -import org.apache.pdfbox.Loader; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; @@ -21,6 +20,7 @@ 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.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -33,6 +33,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.security.SanitizePdfRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -40,6 +41,13 @@ @Tag(name = "Security", description = "Security APIs") public class SanitizeController { + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public SanitizeController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf") @Operation( summary = "Sanitize a PDF file", @@ -54,7 +62,7 @@ public ResponseEntity sanitizePDF(@ModelAttribute SanitizePdfRequest req boolean removeLinks = request.isRemoveLinks(); boolean removeFonts = request.isRemoveFonts(); - PDDocument document = Loader.loadPDF(inputFile.getBytes()); + PDDocument document = pdfDocumentFactory.load(inputFile); if (removeJavaScript) { sanitizeJavaScript(document); } 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 0b36cd5e02..de7efd713c 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 @@ -11,7 +11,6 @@ import javax.imageio.ImageIO; import org.apache.commons.io.IOUtils; -import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; @@ -23,6 +22,7 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.util.Matrix; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -36,6 +36,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.security.AddWatermarkRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.WebResponseUtils; @@ -44,6 +45,13 @@ @Tag(name = "Security", description = "Security APIs") public class WatermarkController { + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public WatermarkController(CustomPDDocumentFactory pdfDocumentFactory) { + this.pdfDocumentFactory = pdfDocumentFactory; + } + @PostMapping(consumes = "multipart/form-data", value = "/add-watermark") @Operation( summary = "Add watermark to a PDF file", @@ -64,7 +72,7 @@ public ResponseEntity addWatermark(@ModelAttribute AddWatermarkRequest r boolean convertPdfToImage = request.isConvertPDFToImage(); // Load the input PDF - PDDocument document = Loader.loadPDF(pdfFile.getBytes()); + PDDocument document = pdfDocumentFactory.load(pdfFile); // Create a page in the document for (PDPage page : document.getPages()) { diff --git a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java index 2f6b504209..9081164d28 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java @@ -51,7 +51,7 @@ public String login(HttpServletRequest request, Model model, Authentication auth Map providerList = new HashMap<>(); - OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); if (oauth != null) { if (oauth.isSettingsValid()) { providerList.put("oidc", oauth.getProvider()); @@ -82,7 +82,7 @@ public String login(HttpServletRequest request, Model model, Authentication auth model.addAttribute("loginMethod", applicationProperties.getSecurity().getLoginMethod()); model.addAttribute( - "oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled()); + "oAuth2Enabled", applicationProperties.getSecurity().getOauth2().getEnabled()); model.addAttribute("currentPage", "login"); @@ -345,7 +345,7 @@ public String account(HttpServletRequest request, Model model, Authentication au // Retrieve username and other attributes username = userDetails.getAttribute( - applicationProperties.getSecurity().getOAUTH2().getUseAsUsername()); + applicationProperties.getSecurity().getOauth2().getUseAsUsername()); // Add oAuth2 Login attributes to the model model.addAttribute("oAuth2Login", true); } diff --git a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java index 972d1fa903..d8f3e9b0cc 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java @@ -15,7 +15,7 @@ @Tag(name = "Convert", description = "Convert APIs") public class ConverterWebController { - @ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}") + @ConditionalOnExpression("${bookAndHtmlFormatsInstalled}") @GetMapping("/book-to-pdf") @Hidden public String convertBookToPdfForm(Model model) { @@ -60,7 +60,7 @@ public String convertToPdfForm(Model model) { // PDF TO...... - @ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}") + @ConditionalOnExpression("${bookAndHtmlFormatsInstalled}") @GetMapping("/pdf-to-book") @Hidden public String convertPdfToBookForm(Model model) { 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 155fcdbd3c..ff73fb2d3b 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java @@ -2,11 +2,7 @@ import java.time.Duration; import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; @@ -18,19 +14,20 @@ import org.springframework.web.bind.annotation.RestController; import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.MeterRegistry; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.config.StartupApplicationListener; import stirling.software.SPDF.model.ApplicationProperties; @RestController @RequestMapping("/api/v1/info") @Tag(name = "Info", description = "Info APIs") +@Slf4j public class MetricsController { @Autowired ApplicationProperties applicationProperties; @@ -46,6 +43,7 @@ public void init() { this.metricsEnabled = metricsEnabled; } + @Autowired public MetricsController(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } @@ -66,11 +64,11 @@ public ResponseEntity getStatus() { return ResponseEntity.ok(status); } - @GetMapping("/loads") + @GetMapping("/load") @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.") + "This endpoint returns the total count of GET requests for a specific endpoint or all endpoints.") public ResponseEntity getPageLoads( @RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional endpoint) { @@ -78,44 +76,33 @@ public ResponseEntity getPageLoads( return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } try { + double count = getRequestCount("GET", endpoint); + return ResponseEntity.ok(count); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } - double count = 0.0; - - for (Meter meter : meterRegistry.getMeters()) { - if (meter.getId().getName().equals("http.requests")) { - String method = meter.getId().getTag("method"); - if (method != null && "GET".equals(method)) { - - if (endpoint.isPresent() && !endpoint.get().isBlank()) { - if (!endpoint.get().startsWith("/")) { - endpoint = Optional.of("/" + endpoint.get()); - } - System.out.println( - "loads " - + endpoint.get() - + " vs " - + meter.getId().getTag("uri")); - if (endpoint.get().equals(meter.getId().getTag("uri"))) { - if (meter instanceof Counter) { - count += ((Counter) meter).count(); - } - } - } else { - if (meter instanceof Counter) { - count += ((Counter) meter).count(); - } - } - } - } - } - + @GetMapping("/load/unique") + @Operation( + summary = "Unique users count for GET requests", + description = + "This endpoint returns the count of unique users for GET requests for a specific endpoint or all endpoints.") + public ResponseEntity getUniquePageLoads( + @RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") + Optional endpoint) { + if (!metricsEnabled) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); + } + try { + double count = getUniqueUserCount("GET", endpoint); return ResponseEntity.ok(count); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } - @GetMapping("/loads/all") + @GetMapping("/load/all") @Operation( summary = "GET requests count for all endpoints", description = "This endpoint returns the count of GET requests for each endpoint.") @@ -124,59 +111,27 @@ public ResponseEntity getAllEndpointLoads() { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } try { - Map counts = new HashMap<>(); - - for (Meter meter : meterRegistry.getMeters()) { - if (meter.getId().getName().equals("http.requests")) { - String method = meter.getId().getTag("method"); - if (method != null && "GET".equals(method)) { - String uri = meter.getId().getTag("uri"); - if (uri != null) { - double currentCount = counts.getOrDefault(uri, 0.0); - if (meter instanceof Counter) { - currentCount += ((Counter) meter).count(); - } - counts.put(uri, currentCount); - } - } - } - } - - List results = - counts.entrySet().stream() - .map(entry -> new EndpointCount(entry.getKey(), entry.getValue())) - .sorted(Comparator.comparing(EndpointCount::getCount).reversed()) - .collect(Collectors.toList()); - + List results = getEndpointCounts("GET"); return ResponseEntity.ok(results); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } - public class EndpointCount { - private String endpoint; - private double count; - - public EndpointCount(String endpoint, double count) { - this.endpoint = endpoint; - this.count = count; - } - - public String getEndpoint() { - return endpoint; - } - - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; - } - - public double getCount() { - return count; + @GetMapping("/load/all/unique") + @Operation( + summary = "Unique users count for GET requests for all endpoints", + description = + "This endpoint returns the count of unique users for GET requests for each endpoint.") + public ResponseEntity getAllUniqueEndpointLoads() { + if (!metricsEnabled) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } - - public void setCount(double count) { - this.count = count; + try { + List results = getUniqueUserCounts("GET"); + return ResponseEntity.ok(results); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @@ -184,7 +139,7 @@ public void setCount(double count) { @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.") + "This endpoint returns the total count of POST requests for a specific endpoint or all endpoints.") public ResponseEntity getTotalRequests( @RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional endpoint) { @@ -192,29 +147,26 @@ public ResponseEntity getTotalRequests( return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } try { - double count = 0.0; - - for (Meter meter : meterRegistry.getMeters()) { - if (meter.getId().getName().equals("http.requests")) { - String method = meter.getId().getTag("method"); - if (method != null && "POST".equals(method)) { - if (endpoint.isPresent() && !endpoint.get().isBlank()) { - if (!endpoint.get().startsWith("/")) { - endpoint = Optional.of("/" + endpoint.get()); - } - if (endpoint.get().equals(meter.getId().getTag("uri"))) { - if (meter instanceof Counter) { - count += ((Counter) meter).count(); - } - } - } else { - if (meter instanceof Counter) { - count += ((Counter) meter).count(); - } - } - } - } - } + double count = getRequestCount("POST", endpoint); + return ResponseEntity.ok(count); + } catch (Exception e) { + return ResponseEntity.ok(-1); + } + } + + @GetMapping("/requests/unique") + @Operation( + summary = "Unique users count for POST requests", + description = + "This endpoint returns the count of unique users for POST requests for a specific endpoint or all endpoints.") + public ResponseEntity getUniqueTotalRequests( + @RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") + Optional endpoint) { + if (!metricsEnabled) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); + } + try { + double count = getUniqueUserCount("POST", endpoint); return ResponseEntity.ok(count); } catch (Exception e) { return ResponseEntity.ok(-1); @@ -230,36 +182,145 @@ public ResponseEntity getAllPostRequests() { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); } try { - Map counts = new HashMap<>(); - - for (Meter meter : meterRegistry.getMeters()) { - if (meter.getId().getName().equals("http.requests")) { - String method = meter.getId().getTag("method"); - if (method != null && "POST".equals(method)) { - String uri = meter.getId().getTag("uri"); - if (uri != null) { - double currentCount = counts.getOrDefault(uri, 0.0); - if (meter instanceof Counter) { - currentCount += ((Counter) meter).count(); - } - counts.put(uri, currentCount); - } - } - } - } - - List results = - counts.entrySet().stream() - .map(entry -> new EndpointCount(entry.getKey(), entry.getValue())) - .sorted(Comparator.comparing(EndpointCount::getCount).reversed()) - .collect(Collectors.toList()); + List results = getEndpointCounts("POST"); + return ResponseEntity.ok(results); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + @GetMapping("/requests/all/unique") + @Operation( + summary = "Unique users count for POST requests for all endpoints", + description = + "This endpoint returns the count of unique users for POST requests for each endpoint.") + public ResponseEntity getAllUniquePostRequests() { + if (!metricsEnabled) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); + } + try { + List results = getUniqueUserCounts("POST"); return ResponseEntity.ok(results); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } + private double getRequestCount(String method, Optional endpoint) { + log.info( + "Getting request count for method: {}, endpoint: {}", + method, + endpoint.orElse("all")); + double count = + meterRegistry.find("http.requests").tag("method", method).counters().stream() + .filter( + counter -> + !endpoint.isPresent() + || endpoint.get() + .equals(counter.getId().getTag("uri"))) + .mapToDouble(Counter::count) + .sum(); + log.info("Request count: {}", count); + return count; + } + + private List getEndpointCounts(String method) { + log.info("Getting endpoint counts for method: {}", method); + Map counts = new HashMap<>(); + meterRegistry + .find("http.requests") + .tag("method", method) + .counters() + .forEach( + counter -> { + String uri = counter.getId().getTag("uri"); + counts.merge(uri, counter.count(), Double::sum); + }); + + List result = + counts.entrySet().stream() + .map(entry -> new EndpointCount(entry.getKey(), entry.getValue())) + .sorted(Comparator.comparing(EndpointCount::getCount).reversed()) + .collect(Collectors.toList()); + log.info("Found {} endpoints with counts", result.size()); + return result; + } + + private double getUniqueUserCount(String method, Optional endpoint) { + log.info( + "Getting unique user count for method: {}, endpoint: {}", + method, + endpoint.orElse("all")); + Set uniqueUsers = new HashSet<>(); + meterRegistry.find("http.requests").tag("method", method).counters().stream() + .filter( + counter -> + !endpoint.isPresent() + || endpoint.get().equals(counter.getId().getTag("uri"))) + .forEach( + counter -> { + String session = counter.getId().getTag("session"); + if (session != null) { + uniqueUsers.add(session); + } + }); + log.info("Unique user count: {}", uniqueUsers.size()); + return uniqueUsers.size(); + } + + private List getUniqueUserCounts(String method) { + log.info("Getting unique user counts for method: {}", method); + Map> uniqueUsers = new HashMap<>(); + + meterRegistry + .find("http.requests") + .tag("method", method) + .counters() + .forEach( + counter -> { + String uri = counter.getId().getTag("uri"); + String session = counter.getId().getTag("session"); + if (uri != null && session != null) { + uniqueUsers.computeIfAbsent(uri, k -> new HashSet<>()).add(session); + } + }); + + List result = + uniqueUsers.entrySet().stream() + .map(entry -> new EndpointCount(entry.getKey(), entry.getValue().size())) + .sorted(Comparator.comparing(EndpointCount::getCount).reversed()) + .collect(Collectors.toList()); + + log.info("Found {} endpoints with unique user counts", result.size()); + return result; + } + + public static class EndpointCount { + private String endpoint; + private double count; + + public EndpointCount(String endpoint, double count) { + this.endpoint = endpoint; + this.count = count; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public double getCount() { + return count; + } + + public void setCount(double count) { + this.count = count; + } + } + @GetMapping("/uptime") public ResponseEntity getUptime() { if (!metricsEnabled) { diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index 266d4520f8..46c92633a4 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -12,6 +12,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import lombok.Data; +import lombok.ToString; import stirling.software.SPDF.config.YamlPropertySourceFactory; import stirling.software.SPDF.model.provider.GithubProvider; import stirling.software.SPDF.model.provider.GoogleProvider; @@ -21,225 +23,56 @@ @Configuration @ConfigurationProperties(prefix = "") @PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class) +@Data public class ApplicationProperties { - private Security security; - private System system; - private Ui ui; - private Endpoints endpoints; - private Metrics metrics; - private AutomaticallyGenerated automaticallyGenerated; - private AutoPipeline autoPipeline; - private static final Logger logger = LoggerFactory.getLogger(ApplicationProperties.class); - - 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 - + "]"; - } + private Legal legal = new Legal(); + private Security security = new Security(); + private System system = new System(); + private Ui ui = new Ui(); + private Endpoints endpoints = new Endpoints(); + private Metrics metrics = new Metrics(); + private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated(); + private EnterpriseEdition enterpriseEdition = new EnterpriseEdition(); + private AutoPipeline autoPipeline = new AutoPipeline(); + private static final Logger logger = LoggerFactory.getLogger(ApplicationProperties.class); + @Data 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 + "]"; - } + @Data + public static class Legal { + private String termsAndConditions; + private String privacyPolicy; + private String accessibilityStatement; + private String cookiePolicy; + private String impressum; } + @Data public static class Security { private Boolean enableLogin; private Boolean csrfDisabled; - private InitialLogin initialLogin; - private OAUTH2 oauth2; + private InitialLogin initialLogin = new InitialLogin(); + private OAUTH2 oauth2 = new OAUTH2(); private int loginAttemptCount; private long loginResetTimeMinutes; private String loginMethod = "all"; - public String getLoginMethod() { - return loginMethod; - } - - public void setLoginMethod(String loginMethod) { - this.loginMethod = loginMethod; - } - - public int getLoginAttemptCount() { - return loginAttemptCount; - } - - public void setLoginAttemptCount(int loginAttemptCount) { - this.loginAttemptCount = loginAttemptCount; - } - - public long getLoginResetTimeMinutes() { - return loginResetTimeMinutes; - } - - public void setLoginResetTimeMinutes(long loginResetTimeMinutes) { - this.loginResetTimeMinutes = loginResetTimeMinutes; - } - - public InitialLogin getInitialLogin() { - return initialLogin != null ? initialLogin : new InitialLogin(); - } - - public void setInitialLogin(InitialLogin initialLogin) { - this.initialLogin = initialLogin; - } - - public OAUTH2 getOAUTH2() { - return oauth2 != null ? oauth2 : new OAUTH2(); - } - - public void setOAUTH2(OAUTH2 oauth2) { - this.oauth2 = oauth2; - } - - 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 - + ", oauth2=" - + oauth2 - + ", initialLogin=" - + initialLogin - + ", csrfDisabled=" - + csrfDisabled - + ", loginMethod=" - + loginMethod - + "]"; - } - + @Data 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") - + "]"; - } + @ToString.Exclude private String password; } + @Data public static class OAUTH2 { private Boolean enabled = false; private String issuer; private String clientId; - private String clientSecret; + @ToString.Exclude private String clientSecret; private Boolean autoCreateUser = false; private Boolean blockRegistration = false; private String useAsUsername; @@ -247,74 +80,6 @@ public static class OAUTH2 { private String provider; private Client client = new Client(); - public Boolean getEnabled() { - return enabled; - } - - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } - - public String getIssuer() { - return issuer; - } - - public void setIssuer(String issuer) { - this.issuer = issuer; - } - - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - public String getClientSecret() { - return clientSecret; - } - - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - - public Boolean getAutoCreateUser() { - return autoCreateUser; - } - - public void setAutoCreateUser(Boolean autoCreateUser) { - this.autoCreateUser = autoCreateUser; - } - - public Boolean getBlockRegistration() { - return blockRegistration; - } - - public void setBlockRegistration(Boolean blockRegistration) { - this.blockRegistration = blockRegistration; - } - - public String getUseAsUsername() { - return useAsUsername; - } - - public void setUseAsUsername(String useAsUsername) { - this.useAsUsername = useAsUsername; - } - - public String getProvider() { - return provider; - } - - public void setProvider(String provider) { - this.provider = provider; - } - - public Collection getScopes() { - return scopes; - } - public void setScopes(String scopes) { List scopesList = Arrays.stream(scopes.split(",")) @@ -323,26 +88,12 @@ public void setScopes(String scopes) { this.scopes.addAll(scopesList); } - public Client getClient() { - return client; - } - - public void setClient(Client client) { - this.client = client; - } - protected boolean isValid(String value, String name) { - if (value != null && !value.trim().isEmpty()) { - return true; - } - return false; + return value != null && !value.trim().isEmpty(); } protected boolean isValid(Collection value, String name) { - if (value != null && !value.isEmpty()) { - return true; - } - return false; + return value != null && !value.isEmpty(); } public boolean isSettingsValid() { @@ -353,31 +104,7 @@ && isValid(this.getScopes(), "scopes") && isValid(this.getUseAsUsername(), "useAsUsername"); } - @Override - public String toString() { - return "OAUTH2 [enabled=" - + enabled - + ", issuer=" - + issuer - + ", clientId=" - + clientId - + ", clientSecret=" - + (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL") - + ", autoCreateUser=" - + autoCreateUser - + ", blockRegistration=" - + blockRegistration - + ", useAsUsername=" - + useAsUsername - + ", provider=" - + provider - + ", client=" - + client - + ", scopes=" - + scopes - + "]"; - } - + @Data public static class Client { private GoogleProvider google = new GoogleProvider(); private GithubProvider github = new GithubProvider(); @@ -392,50 +119,15 @@ public Provider get(String registrationId) throws UnsupportedProviderException { case "keycloak": return getKeycloak(); default: - break; + throw new UnsupportedProviderException( + "Logout from the provider is not supported? Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues"); } - throw new UnsupportedProviderException( - "Logout from the provider is not supported? Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues"); - } - - public GoogleProvider getGoogle() { - return google; - } - - public void setGoogle(GoogleProvider google) { - this.google = google; - } - - public GithubProvider getGithub() { - return github; - } - - public void setGithub(GithubProvider github) { - this.github = github; - } - - public KeycloakProvider getKeycloak() { - return keycloak; - } - - public void setKeycloak(KeycloakProvider keycloak) { - this.keycloak = keycloak; - } - - @Override - public String toString() { - return "Client [google=" - + google - + ", github=" - + github - + ", keycloak=" - + keycloak - + "]"; } } } } + @Data public static class System { private String defaultLocale; private Boolean googlevisibility; @@ -443,184 +135,67 @@ public static class System { private Boolean showUpdateOnlyAdmin; private boolean customHTMLFiles; private String tessdataDir; - - public String getTessdataDir() { - return tessdataDir; - } - - public void setTessdataDir(String tessdataDir) { - this.tessdataDir = tessdataDir; - } - - public boolean isCustomHTMLFiles() { - return customHTMLFiles; - } - - public void setCustomHTMLFiles(boolean customHTMLFiles) { - this.customHTMLFiles = customHTMLFiles; - } - - public boolean getShowUpdateOnlyAdmin() { - return showUpdateOnlyAdmin; - } - - public void setShowUpdateOnlyAdmin(boolean showUpdateOnlyAdmin) { - this.showUpdateOnlyAdmin = showUpdateOnlyAdmin; - } - - public boolean getShowUpdate() { - return showUpdate; - } - - public void setShowUpdate(boolean showUpdate) { - this.showUpdate = showUpdate; - } - private Boolean enableAlphaFunctionality; - - public Boolean getEnableAlphaFunctionality() { - return enableAlphaFunctionality; - } - - public void setEnableAlphaFunctionality(Boolean enableAlphaFunctionality) { - this.enableAlphaFunctionality = enableAlphaFunctionality; - } - - 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; - } - - @Override - public String toString() { - return "System [defaultLocale=" - + defaultLocale - + ", googlevisibility=" - + googlevisibility - + ", enableAlphaFunctionality=" - + enableAlphaFunctionality - + ", showUpdate=" - + showUpdate - + ", showUpdateOnlyAdmin=" - + showUpdateOnlyAdmin - + "]"; - } } + @Data 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; + return appName != null && appName.trim().length() > 0 ? appName : null; } public String getHomeDescription() { - if (homeDescription != null && homeDescription.trim().length() == 0) return null; - return homeDescription; - } - - public void setHomeDescription(String homeDescription) { - this.homeDescription = homeDescription; + return homeDescription != null && homeDescription.trim().length() > 0 + ? homeDescription + : null; } 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 - + "]"; + return appNameNavbar != null && appNameNavbar.trim().length() > 0 + ? appNameNavbar + : null; } } + @Data 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 + "]"; - } } + @Data 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 + "]"; - } } + @Data public static class AutomaticallyGenerated { - private String key; + @ToString.Exclude private String key; + } - public String getKey() { - return key; - } + @Data + public static class EnterpriseEdition { + @ToString.Exclude private String key; + private CustomMetadata customMetadata = new CustomMetadata(); - public void setKey(String key) { - this.key = key; - } + @Data + public static class CustomMetadata { + private boolean autoUpdateMetadata; + private String author; + private String creator; + private String producer; - @Override - public String toString() { - return "AutomaticallyGenerated [key=" - + (key != null && !key.isEmpty() ? "MASKED" : "NULL") - + "]"; + public String getCreator() { + return creator == null || creator.trim().isEmpty() ? "Stirling-PDF" : creator; + } + + public String getProducer() { + return producer == null || producer.trim().isEmpty() ? "Stirling-PDF" : producer; + } } } } diff --git a/src/main/java/stirling/software/SPDF/repository/UserRepository.java b/src/main/java/stirling/software/SPDF/repository/UserRepository.java index 4f231e0fae..0f5387f79b 100644 --- a/src/main/java/stirling/software/SPDF/repository/UserRepository.java +++ b/src/main/java/stirling/software/SPDF/repository/UserRepository.java @@ -16,7 +16,6 @@ public interface UserRepository extends JpaRepository { @Query("FROM User u LEFT JOIN FETCH u.settings where upper(u.username) = upper(:username)") Optional findByUsernameIgnoreCaseWithSettings(@Param("username") String username); - Optional findByUsername(String username); Optional findByApiKey(String apiKey); diff --git a/src/main/java/stirling/software/SPDF/service/CustomPDDocumentFactory.java b/src/main/java/stirling/software/SPDF/service/CustomPDDocumentFactory.java new file mode 100644 index 0000000000..b3f9eec7e3 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/service/CustomPDDocumentFactory.java @@ -0,0 +1,100 @@ +package stirling.software.SPDF.service; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import stirling.software.SPDF.config.PdfMetadataService; +import stirling.software.SPDF.model.PdfMetadata; +import stirling.software.SPDF.model.api.PDFFile; + +@Component +public class CustomPDDocumentFactory { + + private final PdfMetadataService pdfMetadataService; + + @Autowired + public CustomPDDocumentFactory(PdfMetadataService pdfMetadataService) { + this.pdfMetadataService = pdfMetadataService; + } + + public PDDocument createNewDocument() throws IOException { + PDDocument document = new PDDocument(); + pdfMetadataService.setMetadataToPdf(document, PdfMetadata.builder().build(), true); + return document; + } + + public PDDocument createNewDocumentBasedOnOldDocument(PDDocument oldDocument) + throws IOException { + PDDocument document = new PDDocument(); + pdfMetadataService.setMetadataToPdf( + document, pdfMetadataService.extractMetadataFromPdf(oldDocument), true); + return document; + } + + public byte[] loadToBytes(File file) throws IOException { + PDDocument document = load(file); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + // Close the document + document.close(); + return baos.toByteArray(); + } + + public byte[] loadToBytes(byte[] bytes) throws IOException { + PDDocument document = load(bytes); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + // Close the document + document.close(); + return baos.toByteArray(); + } + + // if loading from a file, assume the file has been made with Stirling-PDF + public PDDocument load(File file) throws IOException { + PDDocument document = Loader.loadPDF(file); + pdfMetadataService.setMetadataToPdf(document, PdfMetadata.builder().build(), true); + return document; + } + + public PDDocument load(InputStream input) throws IOException { + return load(input.readAllBytes()); + } + + public PDDocument load(byte[] input) throws IOException { + PDDocument document = Loader.loadPDF(input); + pdfMetadataService.setDefaultMetadata(document); + return document; + } + + public PDDocument load(PDFFile pdfFile) throws IOException { + return load(pdfFile.getFileInput()); + } + + public PDDocument load(MultipartFile pdfFile) throws IOException { + return load(pdfFile.getBytes()); + } + + public PDDocument load(String path) throws IOException { + return load(new File(path)); + } + + public PDDocument load(MultipartFile fileInput, String password) throws IOException { + return load(fileInput.getBytes(), password); + } + + private PDDocument load(byte[] bytes, String password) throws IOException { + PDDocument document = Loader.loadPDF(bytes, password); + pdfMetadataService.setDefaultMetadata(document); + return document; + } + + // Add other load methods as needed, following the same pattern +} diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index c158990295..3416dee6fe 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -6,7 +6,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.zip.ZipEntry; @@ -37,7 +36,7 @@ import io.github.pixee.security.Filenames; -import stirling.software.SPDF.model.PdfMetadata; +import stirling.software.SPDF.service.CustomPDDocumentFactory; public class PdfUtils { @@ -383,9 +382,13 @@ private static BufferedImage prepareImageForPdfToImage( } public static byte[] imageToPdf( - MultipartFile[] files, String fitOption, boolean autoRotate, String colorType) + MultipartFile[] files, + String fitOption, + boolean autoRotate, + String colorType, + CustomPDDocumentFactory pdfDocumentFactory) throws IOException { - try (PDDocument doc = new PDDocument()) { + try (PDDocument doc = pdfDocumentFactory.createNewDocument()) { for (MultipartFile file : files) { String contentType = file.getContentType(); String originalFilename = Filenames.toSimpleFileName(file.getOriginalFilename()); @@ -473,10 +476,15 @@ public static void addImageToDocument( } public static byte[] overlayImage( - byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) + CustomPDDocumentFactory pdfDocumentFactory, + byte[] pdfBytes, + byte[] imageBytes, + float x, + float y, + boolean everyPage) throws IOException { - PDDocument document = Loader.loadPDF(pdfBytes); + PDDocument document = pdfDocumentFactory.load(pdfBytes); // Get the first page of the PDF int pages = document.getNumberOfPages(); @@ -506,30 +514,6 @@ public static byte[] overlayImage( return baos.toByteArray(); } - public static PdfMetadata extractMetadataFromPdf(PDDocument pdf) { - return PdfMetadata.builder() - .author(pdf.getDocumentInformation().getAuthor()) - .producer(pdf.getDocumentInformation().getProducer()) - .title(pdf.getDocumentInformation().getTitle()) - .creator(pdf.getDocumentInformation().getCreator()) - .subject(pdf.getDocumentInformation().getSubject()) - .keywords(pdf.getDocumentInformation().getKeywords()) - .creationDate(pdf.getDocumentInformation().getCreationDate()) - .modificationDate(pdf.getDocumentInformation().getModificationDate()) - .build(); - } - - public static void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata) { - pdf.getDocumentInformation().setAuthor(pdfMetadata.getAuthor()); - pdf.getDocumentInformation().setProducer(pdfMetadata.getProducer()); - pdf.getDocumentInformation().setTitle(pdfMetadata.getTitle()); - pdf.getDocumentInformation().setCreator(pdfMetadata.getCreator()); - pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject()); - pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords()); - pdf.getDocumentInformation().setCreationDate(pdfMetadata.getCreationDate()); - pdf.getDocumentInformation().setModificationDate(Calendar.getInstance()); - } - /** Key for storing the dimensions of a rendered image in a map. */ private record PdfRenderSettingsKey(float mediaBoxWidth, float mediaBoxHeight, int rotation) {} diff --git a/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java b/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java index a9c404e1c2..7aeab8e177 100644 --- a/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java @@ -12,6 +12,7 @@ public static boolean isStaticResource(String contextPath, String requestURI) { return requestURI.startsWith(contextPath + "/css/") || requestURI.startsWith(contextPath + "/fonts/") || requestURI.startsWith(contextPath + "/js/") + || requestURI.endsWith(contextPath + "robots.txt") || requestURI.startsWith(contextPath + "/images/") || requestURI.startsWith(contextPath + "/public/") || requestURI.startsWith(contextPath + "/pdfjs/") @@ -22,4 +23,26 @@ public static boolean isStaticResource(String contextPath, String requestURI) { || requestURI.endsWith(".webmanifest") || requestURI.startsWith(contextPath + "/api/v1/info/status"); } + + public static boolean isTrackableResource(String requestURI) { + return isTrackableResource("", requestURI); + } + + public static boolean isTrackableResource(String contextPath, String requestURI) { + return !(requestURI.startsWith("/js") + || requestURI.startsWith("/v1/api-docs") + || requestURI.endsWith("robots.txt") + || requestURI.startsWith("/images") + || requestURI.endsWith(".png") + || requestURI.endsWith(".ico") + || requestURI.endsWith(".css") + || requestURI.endsWith(".map") + || requestURI.endsWith(".svg") + || requestURI.endsWith(".js") + || requestURI.contains("swagger") + || requestURI.startsWith("/api/v1/info") + || requestURI.startsWith("/site.webmanifest") + || requestURI.startsWith("/fonts") + || requestURI.startsWith("/pdfjs")); + } } diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index 4b19d9413b..284c435345 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -77,7 +77,11 @@ color=لون sponsor=راعٍ info=معلومات - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=تغيير #pdfToPDFA pdfToPDFA.title=PDF إلى PDF/A pdfToPDFA.header=PDF إلى PDF/A -pdfToPDFA.credit=تستخدم هذه الخدمة OCRmyPDF لتحويل PDF/A. +pdfToPDFA.credit=تستخدم هذه الخدمة ghostscript لتحويل PDF/A. pdfToPDFA.submit=تحويل pdfToPDFA.tip=لا يعمل حاليًا لمدخلات متعددة في وقت واحد pdfToPDFA.outputFormat=تنسيق الإخراج diff --git a/src/main/resources/messages_bg_BG.properties b/src/main/resources/messages_bg_BG.properties index 442f8789d4..e6df3a6c83 100644 --- a/src/main/resources/messages_bg_BG.properties +++ b/src/main/resources/messages_bg_BG.properties @@ -77,7 +77,11 @@ color=Цвят sponsor=Спонсор info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Промени #pdfToPDFA pdfToPDFA.title=PDF към PDF/A pdfToPDFA.header=PDF към PDF/A -pdfToPDFA.credit=Тази услуга използва OCRmyPDF за PDF/A преобразуване. +pdfToPDFA.credit=Тази услуга използва ghostscript за PDF/A преобразуване. pdfToPDFA.submit=Преобразуване pdfToPDFA.tip=В момента не работи за няколко входа наведнъж pdfToPDFA.outputFormat=Изходен формат diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index 4673d2d46b..43647bd832 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -77,7 +77,11 @@ color=Color sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Canvia #pdfToPDFA pdfToPDFA.title=PDF a PDF/A pdfToPDFA.header=PDF a PDF/A -pdfToPDFA.credit=Utilitza OCRmyPDF per la conversió a PDF/A +pdfToPDFA.credit=Utilitza ghostscript per la conversió a PDF/A pdfToPDFA.submit=Converteix pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_cs_CZ.properties b/src/main/resources/messages_cs_CZ.properties index a5396d3905..dba3c988dc 100644 --- a/src/main/resources/messages_cs_CZ.properties +++ b/src/main/resources/messages_cs_CZ.properties @@ -77,7 +77,11 @@ color=Barva sponsor=Sponzor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Změnit #pdfToPDFA pdfToPDFA.title=PDF na PDF/A pdfToPDFA.header=PDF na PDF/A -pdfToPDFA.credit=Tato služba používá OCRmyPDF pro konverzi do formátu PDF/A +pdfToPDFA.credit=Tato služba používá ghostscript pro konverzi do formátu PDF/A pdfToPDFA.submit=Převést pdfToPDFA.tip=V současné době nepracuje pro více vstupů najednou pdfToPDFA.outputFormat=Výstupní formát diff --git a/src/main/resources/messages_da_DK.properties b/src/main/resources/messages_da_DK.properties index 198c9d821b..4f914d7ede 100644 --- a/src/main/resources/messages_da_DK.properties +++ b/src/main/resources/messages_da_DK.properties @@ -77,7 +77,11 @@ color=Farve sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Ændre #pdfToPDFA pdfToPDFA.title=PDF Til PDF/A pdfToPDFA.header=PDF Til PDF/A -pdfToPDFA.credit=Denne tjeneste bruger OCRmyPDF til PDF/A-konvertering +pdfToPDFA.credit=Denne tjeneste bruger ghostscript til PDF/A-konvertering pdfToPDFA.submit=Konvertér pdfToPDFA.tip=Fungerer i øjeblikket ikke for flere input på én gang pdfToPDFA.outputFormat=Outputformat diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index a903444515..754745a299 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -77,7 +77,11 @@ color=Farbe sponsor=Sponsor info=Informationen - +legal.privacy=Datenschutz +legal.terms=AGB +legal.accessibility=Barrierefreiheit +legal.cookie=Cookie-Richtlinie +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Ändern #pdfToPDFA pdfToPDFA.title=PDF zu PDF/A pdfToPDFA.header=PDF zu PDF/A -pdfToPDFA.credit=Dieser Dienst verwendet OCRmyPDF für die PDF/A-Konvertierung +pdfToPDFA.credit=Dieser Dienst verwendet ghostscript für die PDF/A-Konvertierung pdfToPDFA.submit=Konvertieren pdfToPDFA.tip=Dieser Dienst kann nur einzelne Eingangsdateien verarbeiten. pdfToPDFA.outputFormat=Ausgabeformat diff --git a/src/main/resources/messages_el_GR.properties b/src/main/resources/messages_el_GR.properties index ba1c4e57bd..c48b8564d3 100644 --- a/src/main/resources/messages_el_GR.properties +++ b/src/main/resources/messages_el_GR.properties @@ -77,7 +77,11 @@ color=Χρώμα sponsor=Yποστηρικτής info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Αλλαγή #pdfToPDFA pdfToPDFA.title=PDF σε PDF/A pdfToPDFA.header=PDF σε PDF/A -pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί OCRmyPDF για PDF/A μετατροπή +pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί ghostscript για PDF/A μετατροπή pdfToPDFA.submit=Μετατροπή pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 98d76deef9..ecfec8207d 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -77,7 +77,11 @@ color=Color sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -115,7 +119,7 @@ navbar.darkmode=Dark Mode navbar.language=Languages navbar.settings=Settings navbar.allTools=Tools -navbar.multiTool=Multi Tools +navbar.multiTool=Multi Tool navbar.sections.organize=Organize navbar.sections.convertTo=Convert to PDF navbar.sections.convertFrom=Convert from PDF @@ -1019,7 +1023,7 @@ changeMetadata.submit=Change #pdfToPDFA pdfToPDFA.title=PDF To PDF/A pdfToPDFA.header=PDF To PDF/A -pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion +pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion pdfToPDFA.submit=Convert pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties index 936c4b1ec4..027530b227 100644 --- a/src/main/resources/messages_en_US.properties +++ b/src/main/resources/messages_en_US.properties @@ -77,7 +77,11 @@ color=Color sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -115,7 +119,7 @@ navbar.darkmode=Dark Mode navbar.language=Languages navbar.settings=Settings navbar.allTools=Tools -navbar.multiTool=Multi Tools +navbar.multiTool=Multi Tool navbar.sections.organize=Organize navbar.sections.convertTo=Convert to PDF navbar.sections.convertFrom=Convert from PDF @@ -1019,7 +1023,7 @@ changeMetadata.submit=Change #pdfToPDFA pdfToPDFA.title=PDF To PDF/A pdfToPDFA.header=PDF To PDF/A -pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion +pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion pdfToPDFA.submit=Convert pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index cd28db0803..7c0e8b0c3d 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -77,7 +77,11 @@ color=Color sponsor=Patrocinador info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Cambiar #pdfToPDFA pdfToPDFA.title=PDF a PDF/A pdfToPDFA.header=PDF a PDF/A -pdfToPDFA.credit=Este servicio usa OCRmyPDF para la conversión a PDF/A +pdfToPDFA.credit=Este servicio usa ghostscript para la conversión a PDF/A pdfToPDFA.submit=Convertir pdfToPDFA.tip=Actualmente no funciona para múltiples entrada a la vez pdfToPDFA.outputFormat=Formato de salida diff --git a/src/main/resources/messages_eu_ES.properties b/src/main/resources/messages_eu_ES.properties index 5a297eac7b..c8e3b8f3a8 100644 --- a/src/main/resources/messages_eu_ES.properties +++ b/src/main/resources/messages_eu_ES.properties @@ -77,7 +77,11 @@ color=Color sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Aldatu #pdfToPDFA pdfToPDFA.title=PDFa PDF/A bihurtu pdfToPDFA.header=PDFa PDF/A bihurtu -pdfToPDFA.credit=Zerbitzu honek OCRmyPDF erabiltzen du PDFak PDF/A bihurtzeko +pdfToPDFA.credit=Zerbitzu honek ghostscript erabiltzen du PDFak PDF/A bihurtzeko pdfToPDFA.submit=Bihurtu pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index f51e31d2b7..3e78e7a3f7 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -77,7 +77,11 @@ color=Couleur sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Modifier #pdfToPDFA 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.credit=Ce service utilise ghostscript pour la conversion en PDF/A. pdfToPDFA.submit=Convertir pdfToPDFA.tip=Ne fonctionne actuellement pas pour plusieurs entrées à la fois pdfToPDFA.outputFormat=Format de sortie diff --git a/src/main/resources/messages_ga_IE.properties b/src/main/resources/messages_ga_IE.properties index 65b90cfacc..07d728165c 100644 --- a/src/main/resources/messages_ga_IE.properties +++ b/src/main/resources/messages_ga_IE.properties @@ -77,7 +77,11 @@ color=Dath sponsor=Urraitheoir info=Eolas - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Athrú #pdfToPDFA pdfToPDFA.title=PDF Go PDF/A pdfToPDFA.header=PDF Go PDF/A -pdfToPDFA.credit=Úsáideann an tseirbhís seo OCRmyPDF chun PDF/A a thiontú +pdfToPDFA.credit=Úsáideann an tseirbhís seo ghostscript chun PDF/A a thiontú pdfToPDFA.submit=Tiontaigh pdfToPDFA.tip=Faoi láthair ní oibríonn sé le haghaidh ionchuir iolracha ag an am céanna pdfToPDFA.outputFormat=Formáid aschuir diff --git a/src/main/resources/messages_hi_IN.properties b/src/main/resources/messages_hi_IN.properties index 24e68fbd8a..1cda371282 100644 --- a/src/main/resources/messages_hi_IN.properties +++ b/src/main/resources/messages_hi_IN.properties @@ -77,7 +77,11 @@ color=Color sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=बदलें #pdfToPDFA pdfToPDFA.title=PDF से PDF/A में pdfToPDFA.header=PDF से PDF/A में -pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए OCRmyPDF का उपयोग किया जाता है। +pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए ghostscript का उपयोग किया जाता है। pdfToPDFA.submit=परिवर्तित करें pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_hr_HR.properties b/src/main/resources/messages_hr_HR.properties index d669f1f774..fd69ac86f5 100644 --- a/src/main/resources/messages_hr_HR.properties +++ b/src/main/resources/messages_hr_HR.properties @@ -77,7 +77,11 @@ color=Boja sponsor=Sponzor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Promijeniti #pdfToPDFA pdfToPDFA.title=PDF u PDF/A pdfToPDFA.header=PDF u PDF/A -pdfToPDFA.credit=Ova usluga koristi OCRmyPDF za PDF/A pretvorbu +pdfToPDFA.credit=Ova usluga koristi ghostscript za PDF/A pretvorbu pdfToPDFA.submit=Pretvoriti pdfToPDFA.tip=Trenutno ne radi za više unosa odjednom pdfToPDFA.outputFormat=Izlazni format diff --git a/src/main/resources/messages_hu_HU.properties b/src/main/resources/messages_hu_HU.properties index 417c39097c..c2c53163d5 100644 --- a/src/main/resources/messages_hu_HU.properties +++ b/src/main/resources/messages_hu_HU.properties @@ -77,7 +77,11 @@ color=Color sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Módosítás #pdfToPDFA pdfToPDFA.title=PDF >> PDF/A pdfToPDFA.header=PDF >> PDF/A -pdfToPDFA.credit=Ez a szolgáltatás az OCRmyPDF-t használja a PDF/A konverzióhoz +pdfToPDFA.credit=Ez a szolgáltatás az ghostscript-t használja a PDF/A konverzióhoz pdfToPDFA.submit=Konvertálás pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_id_ID.properties b/src/main/resources/messages_id_ID.properties index 109b57c0d7..fcf8e5e82d 100644 --- a/src/main/resources/messages_id_ID.properties +++ b/src/main/resources/messages_id_ID.properties @@ -77,7 +77,11 @@ color=Color sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Ganti #pdfToPDFA pdfToPDFA.title=PDF Ke PDF/A pdfToPDFA.header=PDF ke PDF/A -pdfToPDFA.credit=Layanan ini menggunakan OCRmyPDF untuk konversi PDF/A. +pdfToPDFA.credit=Layanan ini menggunakan ghostscript untuk konversi PDF/A. pdfToPDFA.submit=Konversi pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index 32ae8f33f0..6355ea65d4 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -32,7 +32,7 @@ selectFillter=-- Seleziona -- pageNum=Numero pagina sizes.small=Piccolo sizes.medium=Medio -sizes.large=Largo +sizes.large=Grande 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 @@ -50,7 +50,7 @@ WorkInProgess=Lavori in corso, potrebbe non funzionare o essere difettoso, segna poweredBy=Alimentato da yes=Si no=No -changedCredsMessage=Credenziali cambiate! +changedCredsMessage=Credenziali modificate! notAuthenticatedMessage=Utente non autenticato. userNotFoundMessage=Utente non trovato. incorrectPasswordMessage=La password attuale non è corretta. @@ -77,7 +77,11 @@ color=Colore sponsor=Sponsor info=Info - +legal.privacy=Informativa sulla privacy +legal.terms=Termini e Condizioni +legal.accessibility=Accessibilità +legal.cookie=Informativa sui cookie +legal.impressum=Informazioni legali ############### # Pipeline # @@ -190,7 +194,7 @@ adminUserSettings.extraApiUser=API utente limitato aggiuntivo adminUserSettings.webOnlyUser=Utente solo Web adminUserSettings.demoUser=Utente demo (nessuna impostazione personalizzata) adminUserSettings.internalApiUser=API utente interna -adminUserSettings.forceChange=Forza l'utente a cambiare nome username/password all'accesso +adminUserSettings.forceChange=Forza l'utente a cambiare nome utente/password all'accesso adminUserSettings.submit=Salva utente adminUserSettings.changeUserRole=Cambia il ruolo dell'utente adminUserSettings.authenticated=Autenticato @@ -222,7 +226,7 @@ database.failedImportFile=Importazione file non riuscita ############# # HOME-PAGE # ############# -home.desc=La tua pagina self-hostata per gestire qualsiasi PDF. +home.desc=La tua pagina auto-gestita per modificare qualsiasi PDF. home.searchBar=Cerca funzionalità... @@ -675,7 +679,7 @@ pageLayout.submit=Invia scalePages.title=Regola la scala della pagina scalePages.header=Regola la scala della pagina scalePages.pageSize=Dimensione di una pagina del documento. -scalePages.keepPageSize=Original Size +scalePages.keepPageSize=Dimensione originale scalePages.scaleFactor=Livello di zoom (ritaglio) di una pagina. scalePages.submit=Invia @@ -966,7 +970,7 @@ 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.selectText.10=Convert PDF to PDF-Image +watermark.selectText.10=Converti PDF in PDF-Immagine watermark.submit=Aggiungi Filigrana watermark.type.1=Testo watermark.type.2=Immagine @@ -1019,7 +1023,7 @@ changeMetadata.submit=Cambia proprietà #pdfToPDFA pdfToPDFA.title=Da PDF a PDF/A pdfToPDFA.header=Da PDF a PDF/A -pdfToPDFA.credit=Questo servizio utilizza OCRmyPDF per la conversione in PDF/A. +pdfToPDFA.credit=Questo servizio utilizza Ghostscript per la conversione in PDF/A. pdfToPDFA.submit=Converti pdfToPDFA.tip=Attualmente non funziona per più input contemporaneamente pdfToPDFA.outputFormat=Formato di output @@ -1137,14 +1141,14 @@ survey.dontShowAgain=Non mostrare più #error error.sorry=Ci scusiamo per il problema! error.needHelp=Hai bisogno di aiuto / trovato un problema? -error.contactTip=Se i problemi persistono, non esitare a contattarci per chiedere aiuto. Puoi inviare un ticket sulla nostra pagina GitHub o contattarci tramite Discord: +error.contactTip=Se i problemi persistono, non esitare a contattarci per chiedere aiuto. Puoi aprire un ticket sulla nostra pagina GitHub o contattarci tramite Discord: error.404.head=404 - Pagina non trovata | Spiacenti, siamo inciampati nel codice! error.404.1=Non riusciamo a trovare la pagina che stai cercando. error.404.2=Qualcosa è andato storto -error.github=Invia un ticket su GitHub +error.github=Apri un ticket su GitHub error.showStack=Mostra traccia dello stack error.copyStack=Copia traccia dello stack -error.githubSubmit=GitHub: invia un ticket +error.githubSubmit=GitHub: apri un ticket error.discordSubmit=Discord: invia post di supporto diff --git a/src/main/resources/messages_ja_JP.properties b/src/main/resources/messages_ja_JP.properties index b7e6425117..afb8ff548a 100644 --- a/src/main/resources/messages_ja_JP.properties +++ b/src/main/resources/messages_ja_JP.properties @@ -3,8 +3,8 @@ ########### # the direction that the language is written (ltr = left to right, rtl = right to left) language.direction=ltr -addPageNumbers.fontSize=Font Size -addPageNumbers.fontName=Font Name +addPageNumbers.fontSize=フォントサイズ +addPageNumbers.fontName=フォント名 pdfPrompt=PDFを選択 multiPdfPrompt=PDFを選択 (2つ以上) multiPdfDropPrompt=PDFを選択 (又はドラッグ&ドロップ) @@ -77,7 +77,11 @@ color=色 sponsor=スポンサー info=Info - +legal.privacy=プライバシーポリシー +legal.terms=利用規約 +legal.accessibility=アクセシビリティ +legal.cookie=Cookieポリシー +legal.impressum=著作権利者情報 ############### # Pipeline # @@ -194,13 +198,13 @@ adminUserSettings.forceChange=ログイン時にユーザー名/パスワード adminUserSettings.submit=ユーザーの保存 adminUserSettings.changeUserRole=ユーザーの役割を変更する adminUserSettings.authenticated=認証済 -adminUserSettings.editOwnProfil=Edit own profile -adminUserSettings.enabledUser=enabled user -adminUserSettings.disabledUser=disabled user -adminUserSettings.activeUsers=Active Users: -adminUserSettings.disabledUsers=Disabled Users: -adminUserSettings.totalUsers=Total Users: -adminUserSettings.lastRequest=Last Request +adminUserSettings.editOwnProfil=プロフィールの編集 +adminUserSettings.enabledUser=有効なユーザー +adminUserSettings.disabledUser=無効なユーザー +adminUserSettings.activeUsers=アクティブユーザー: +adminUserSettings.disabledUsers=無効なユーザー: +adminUserSettings.totalUsers=ユーザー合計: +adminUserSettings.lastRequest=最後のリクエスト database.title=データベースのインポート/エクスポート @@ -492,21 +496,21 @@ login.locked=あなたのアカウントはロックされています。 login.signinTitle=サインインしてください login.ssoSignIn=シングルサインオンでログイン login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効 -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oauth2AdminBlockedUser=現在、未登録ユーザーの登録またはログインはブロックされています。管理者にお問い合わせください。 login.oauth2RequestNotFound=認証リクエストが見つかりません login.oauth2InvalidUserInfoResponse=無効なユーザー情報の応答 login.oauth2invalidRequest=無効なリクエスト login.oauth2AccessDenied=アクセス拒否 login.oauth2InvalidTokenResponse=無効なトークン応答 login.oauth2InvalidIdToken=無効なIDトークン -login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. +login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。 #auto-redact autoRedact.title=自動塗りつぶし autoRedact.header=自動塗りつぶし autoRedact.colorLabel=カラー -autoRedact.textsToRedactLabel=編集するテキスト (line-separated) +autoRedact.textsToRedactLabel=編集するテキスト(行区切り) autoRedact.textsToRedactPlaceholder=例 \n機密 \n極秘 autoRedact.useRegexLabel=正規表現を使用する autoRedact.wholeWordSearchLabel=単語単位の検索 @@ -675,7 +679,7 @@ pageLayout.submit=送信 scalePages.title=ページの縮尺の調整 scalePages.header=ページの縮尺の調整 scalePages.pageSize=1ページのサイズ -scalePages.keepPageSize=Original Size +scalePages.keepPageSize=元のサイズ scalePages.scaleFactor=1ページの拡大レベル (トリミング)。 scalePages.submit=送信 @@ -724,8 +728,8 @@ removeAnnotations.submit=削除 #compare compare.title=比較 compare.header=PDFの比較 -compare.highlightColor.1=Highlight Color 1: -compare.highlightColor.2=Highlight Color 2: +compare.highlightColor.1=ハイライトカラー 1: +compare.highlightColor.2=ハイライトカラー 2: compare.document.1=ドキュメント 1 compare.document.2=ドキュメント 2 compare.submit=比較 @@ -777,7 +781,7 @@ ScannerImageSplit.selectText.7=最小輪郭面積: ScannerImageSplit.selectText.8=画像の最小の輪郭面積のしきい値を設定。 ScannerImageSplit.selectText.9=境界線サイズ: ScannerImageSplit.selectText.10=出力に白い縁取りが出ないように追加・削除される境界線の大きさを設定 (初期値:1)。 -ScannerImageSplit.info=Python is not installed. It is required to run. +ScannerImageSplit.info=Pythonがインストールされていません。実行する必要があります。 #OCR @@ -804,7 +808,7 @@ ocr.submit=OCRでPDFを処理する extractImages.title=画像の抽出 extractImages.header=画像の抽出 extractImages.selectText=抽出した画像のフォーマットを選択 -extractImages.allowDuplicates=Save duplicate images +extractImages.allowDuplicates=重複した画像を保存する extractImages.submit=抽出 @@ -860,7 +864,7 @@ pdfOrganiser.mode.6=奇数-偶数分割 pdfOrganiser.mode.7=最初に削除 pdfOrganiser.mode.8=最後を削除 pdfOrganiser.mode.9=最初と最後を削除 -pdfOrganiser.mode.10=Odd-Even Merge +pdfOrganiser.mode.10=奇数-偶数の結合 pdfOrganiser.placeholder=(例:1,3,2または4-8,2,10-12または2n-1) @@ -929,7 +933,7 @@ pdfToImage.color=カラー pdfToImage.grey=グレースケール pdfToImage.blackwhite=白黒 (データが失われる可能性があります!) pdfToImage.submit=変換 -pdfToImage.info=Python is not installed. Required for WebP conversion. +pdfToImage.info=Pythonがインストールされていません。WebPの変換に必要です。 #addPassword @@ -966,7 +970,7 @@ watermark.selectText.6=高さスペース (各透かし間の垂直方向のス watermark.selectText.7=不透明度 (0% - 100%): watermark.selectText.8=透かしの種類: watermark.selectText.9=透かしの画像: -watermark.selectText.10=Convert PDF to PDF-Image +watermark.selectText.10=PDFをPDFイメージに変換する watermark.submit=透かしを追加 watermark.type.1=テキスト watermark.type.2=画像 @@ -1019,7 +1023,7 @@ changeMetadata.submit=変更 #pdfToPDFA pdfToPDFA.title=PDFをPDF/Aに変換 pdfToPDFA.header=PDFをPDF/Aに変換 -pdfToPDFA.credit=本サービスはPDF/Aの変換にOCRmyPDFを使用しています。 +pdfToPDFA.credit=本サービスはPDF/Aの変換にghostscriptを使用しています。 pdfToPDFA.submit=変換 pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index 3110922ad6..02d5c3da2f 100644 --- a/src/main/resources/messages_ko_KR.properties +++ b/src/main/resources/messages_ko_KR.properties @@ -77,7 +77,11 @@ color=색상 sponsor=스폰서 info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=변경 #pdfToPDFA pdfToPDFA.title=PDF를 PDF/A로 pdfToPDFA.header=PDF 문서를 PDF/A로 변환 -pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 OCRmyPDF 문서를 사용합니다. +pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 ghostscript 문서를 사용합니다. pdfToPDFA.submit=변환 pdfToPDFA.tip=현재 한 번에 여러 입력에 대해 작동하지 않습니다. pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_nl_NL.properties b/src/main/resources/messages_nl_NL.properties index b0ed1ca8cd..391c7e6962 100644 --- a/src/main/resources/messages_nl_NL.properties +++ b/src/main/resources/messages_nl_NL.properties @@ -77,7 +77,11 @@ color=Kleur sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ 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.credit=Deze service gebruikt ghostscript voor PDF/A-conversie pdfToPDFA.submit=Converteren pdfToPDFA.tip=Werkt momenteel niet voor meerdere inputs tegelijkertijd. pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_no_NB.properties b/src/main/resources/messages_no_NB.properties index a9794fbb6f..1592c3e13e 100644 --- a/src/main/resources/messages_no_NB.properties +++ b/src/main/resources/messages_no_NB.properties @@ -77,7 +77,11 @@ color=Farge sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Endre #pdfToPDFA pdfToPDFA.title=PDF til PDF/A pdfToPDFA.header=PDF til PDF/A -pdfToPDFA.credit=Denne tjenesten bruker OCRmyPDF for PDF/A-konvertering +pdfToPDFA.credit=Denne tjenesten bruker ghostscript for PDF/A-konvertering pdfToPDFA.submit=Konverter pdfToPDFA.tip=Fungere for øyeblikket ikke for flere innganger samtidig pdfToPDFA.outputFormat=Utdataformat diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index 7c4556abb9..76a98ad365 100755 --- a/src/main/resources/messages_pl_PL.properties +++ b/src/main/resources/messages_pl_PL.properties @@ -77,7 +77,11 @@ color=kolor sponsor=sponsor info=informacje - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Zmień #pdfToPDFA pdfToPDFA.title=PDF na PDF/A pdfToPDFA.header=PDF na PDF/A -pdfToPDFA.credit=Ta usługa używa OCRmyPDF do konwersji PDF/A +pdfToPDFA.credit=Ta usługa używa ghostscript do konwersji PDF/A pdfToPDFA.submit=Konwertuj pdfToPDFA.tip=Tylko jeden plik na raz pdfToPDFA.outputFormat=Format wyjściowy: diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index 4f3804c0cf..61a6d7fa0a 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -3,8 +3,8 @@ ########### # the direction that the language is written (ltr = left to right, rtl = right to left) language.direction=ltr -addPageNumbers.fontSize=Font Size -addPageNumbers.fontName=Font Name +addPageNumbers.fontSize=Tamanho da fonte +addPageNumbers.fontName=Nome da fonte pdfPrompt=Selecione PDF(s) multiPdfPrompt=Selecione PDFs (2+) multiPdfDropPrompt=Selecione (ou arraste e solte) todos os PDFs necessários @@ -77,7 +77,11 @@ color=Cor sponsor=Patrocine info=Informações - +legal.privacy=Política de Privacidade +legal.terms=Termos e Condições +legal.accessibility=Acessibilidade +legal.cookie=Política de Cookies +legal.impressum=Informações legais ############### # Pipeline # @@ -503,12 +507,12 @@ login.userIsDisabled=O usuário está desativado, o login está atualmente bloqu #auto-redact -autoRedact.title=Auto ocultar -autoRedact.header=Auto ocultar +autoRedact.title=Redigir automaticamente +autoRedact.header=Redigir automaticamente autoRedact.colorLabel=Cor -autoRedact.textsToRedactLabel=Text para ocultar (separado por linha) +autoRedact.textsToRedactLabel=Texto para redigir (separado por linha) autoRedact.textsToRedactPlaceholder=por exemplo: \nConfidencial \nSecreto -autoRedact.useRegexLabel=Usar Regex (Regular Expressions) +autoRedact.useRegexLabel=Usar Regex (expressão regular) autoRedact.wholeWordSearchLabel=Pesquisa de palavras inteiras autoRedact.customPaddingLabel=Preenchimento extra personalizado autoRedact.convertPDFToImageLabel=Converter PDF em imagem PDF (Usado para remover o texto atrás da caixa) @@ -675,7 +679,7 @@ pageLayout.submit=Enviar scalePages.title=Ajustar Tamanho/Escala da Página scalePages.header=Ajustar Tamanho/Escala da Página scalePages.pageSize=Tamanho de uma página do documento. -scalePages.keepPageSize=Original Size +scalePages.keepPageSize=Tamanho original scalePages.scaleFactor=Fator de zoom (corte) de uma página. scalePages.submit=Enviar @@ -777,7 +781,7 @@ ScannerImageSplit.selectText.7=Área mínima de contorno: ScannerImageSplit.selectText.8=Define o limite mínimo da área de contorno para uma foto ScannerImageSplit.selectText.9=Tamanho da borda: ScannerImageSplit.selectText.10=Define o tamanho da borda adicionada e removida para evitar bordas brancas na saída (padrão: 1). -ScannerImageSplit.info=Python is not installed. It is required to run. +ScannerImageSplit.info=Python não está instalado. É necessário para executar. #OCR @@ -804,7 +808,7 @@ ocr.submit=Processar PDF com OCR extractImages.title=Extrair imagens extractImages.header=Extrair imagens extractImages.selectText=Selecione o formato de imagem para converter as imagens extraídas -extractImages.allowDuplicates=Save duplicate images +extractImages.allowDuplicates=Salvar imagens duplicadas extractImages.submit=Extrair @@ -929,7 +933,7 @@ pdfToImage.color=Colorida pdfToImage.grey=Escala de Cinza pdfToImage.blackwhite=Preto e Branco (pode perder de dados!) pdfToImage.submit=Converter -pdfToImage.info=Python is not installed. Required for WebP conversion. +pdfToImage.info=Python não está instalado. Necessário para conversão WebP. #addPassword @@ -1019,7 +1023,7 @@ changeMetadata.submit=Alterar #pdfToPDFA pdfToPDFA.title=PDF para PDF/A pdfToPDFA.header=PDF para PDF/A -pdfToPDFA.credit=Este serviço usa OCRmyPDF para conversão de PDF/A +pdfToPDFA.credit=Este serviço usa ghostscript para conversão de PDF/A pdfToPDFA.submit=Converter pdfToPDFA.tip=Atualmente não funciona para múltiplas entradas ao mesmo tempo pdfToPDFA.outputFormat=Formato de saída diff --git a/src/main/resources/messages_pt_PT.properties b/src/main/resources/messages_pt_PT.properties index 9d13fd99d5..a5f9f48b26 100644 --- a/src/main/resources/messages_pt_PT.properties +++ b/src/main/resources/messages_pt_PT.properties @@ -77,7 +77,11 @@ color=Color sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Mudar #pdfToPDFA pdfToPDFA.title=PDF para PDF/A pdfToPDFA.header=PDF para PDF/A -pdfToPDFA.credit=Este serviço usa OCRmyPDF para Conversão de PDF/A +pdfToPDFA.credit=Este serviço usa ghostscript para Conversão de PDF/A pdfToPDFA.submit=Converter pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties index e4d96c2551..e035ee7fa9 100644 --- a/src/main/resources/messages_ro_RO.properties +++ b/src/main/resources/messages_ro_RO.properties @@ -77,7 +77,11 @@ color=Culoare sponsor=Sponsor info=Informații - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Schimbă #pdfToPDFA pdfToPDFA.title=PDF către PDF/A pdfToPDFA.header=PDF către PDF/A -pdfToPDFA.credit=Acest serviciu utilizează OCRmyPDF pentru conversia în PDF/A +pdfToPDFA.credit=Acest serviciu utilizează ghostscript pentru conversia în PDF/A pdfToPDFA.submit=Convertește pdfToPDFA.tip=În prezent nu funcționează pentru mai multe intrări simultan pdfToPDFA.outputFormat=Format de ieșire diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index 4fd23c09ff..a4001a498f 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -77,7 +77,11 @@ color=Цвет sponsor=Спонсор info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Изменить #pdfToPDFA pdfToPDFA.title=PDF в PDF/A pdfToPDFA.header=PDF в PDF/A -pdfToPDFA.credit=Этот сервис использует OCRmyPDF для преобразования PDF/A +pdfToPDFA.credit=Этот сервис использует ghostscript для преобразования PDF/A pdfToPDFA.submit=Конвертировать pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_sk_SK.properties b/src/main/resources/messages_sk_SK.properties index 3452d73231..1b9bb358ef 100644 --- a/src/main/resources/messages_sk_SK.properties +++ b/src/main/resources/messages_sk_SK.properties @@ -77,7 +77,11 @@ color=Farba sponsor=Sponzorovať info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Zmeniť #pdfToPDFA pdfToPDFA.title=PDF na PDF/A pdfToPDFA.header=PDF na PDF/A -pdfToPDFA.credit=Táto služba používa OCRmyPDF na konverziu PDF/A +pdfToPDFA.credit=Táto služba používa ghostscript na konverziu PDF/A pdfToPDFA.submit=Konvertovať pdfToPDFA.tip=Momentálne nefunguje pre viacero vstupov naraz pdfToPDFA.outputFormat=Výstupný formát diff --git a/src/main/resources/messages_sr_LATN_RS.properties b/src/main/resources/messages_sr_LATN_RS.properties index 53226f250f..900edc0798 100644 --- a/src/main/resources/messages_sr_LATN_RS.properties +++ b/src/main/resources/messages_sr_LATN_RS.properties @@ -77,7 +77,11 @@ color=Color sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Promeni #pdfToPDFA pdfToPDFA.title=PDF u PDF/A pdfToPDFA.header=PDF u PDF/A -pdfToPDFA.credit=Ova usluga koristi OCRmyPDF za konverziju u PDF/A format +pdfToPDFA.credit=Ova usluga koristi ghostscript za konverziju u PDF/A format pdfToPDFA.submit=Konvertuj pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.outputFormat=Output format diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index 3d159d3a5b..d131649427 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -77,7 +77,11 @@ color=Färg sponsor=Sponsor info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Ändra #pdfToPDFA pdfToPDFA.title=PDF till PDF/A pdfToPDFA.header=PDF till PDF/A -pdfToPDFA.credit=Denna tjänst använder OCRmyPDF för PDF/A-konvertering +pdfToPDFA.credit=Denna tjänst använder ghostscript för PDF/A-konvertering pdfToPDFA.submit=Konvertera pdfToPDFA.tip=Fungerar för närvarande inte för flera inmatningar samtidigt pdfToPDFA.outputFormat=Utdataformat diff --git a/src/main/resources/messages_th_TH.properties b/src/main/resources/messages_th_TH.properties index ae82a70fa1..bcccaad86f 100644 --- a/src/main/resources/messages_th_TH.properties +++ b/src/main/resources/messages_th_TH.properties @@ -77,7 +77,11 @@ color=สี sponsor=ผู้สนับสนุน info=ข้อมูล - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=เปลี่ยน #pdfToPDFA pdfToPDFA.title=PDF เป็น PDF/A pdfToPDFA.header=PDF เป็น PDF/A -pdfToPDFA.credit=บริการนี้ใช้ OCRmyPDF สำหรับการแปลง PDF/A +pdfToPDFA.credit=บริการนี้ใช้ ghostscript สำหรับการแปลง PDF/A pdfToPDFA.submit=แปลง pdfToPDFA.tip=ปัจจุบันไม่ทำงานสำหรับการป้อนข้อมูลหลายรายการพร้อมกัน pdfToPDFA.outputFormat=รูปแบบผลลัพธ์ diff --git a/src/main/resources/messages_tr_TR.properties b/src/main/resources/messages_tr_TR.properties index c5d72ccf8e..2958f9dbe1 100644 --- a/src/main/resources/messages_tr_TR.properties +++ b/src/main/resources/messages_tr_TR.properties @@ -77,7 +77,11 @@ color=Renk sponsor=Bağış info=Bilgi - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Değiştir #pdfToPDFA pdfToPDFA.title=PDF'den PDF/A'ya pdfToPDFA.header=PDF'den PDF/A'ya -pdfToPDFA.credit=Bu hizmet PDF/A dönüşümü için OCRmyPDF kullanır +pdfToPDFA.credit=Bu hizmet PDF/A dönüşümü için ghostscript kullanır pdfToPDFA.submit=Dönüştür pdfToPDFA.tip=Şu anda aynı anda birden fazla giriş için çalışmıyor pdfToPDFA.outputFormat=Çıkış formatı diff --git a/src/main/resources/messages_uk_UA.properties b/src/main/resources/messages_uk_UA.properties index 41ca003859..8a06a720fd 100644 --- a/src/main/resources/messages_uk_UA.properties +++ b/src/main/resources/messages_uk_UA.properties @@ -77,7 +77,11 @@ color=Колір sponsor=Спонсор info=Інформація - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Змінити #pdfToPDFA pdfToPDFA.title=PDF в PDF/A pdfToPDFA.header=PDF в PDF/A -pdfToPDFA.credit=Цей сервіс використовує OCRmyPDF для перетворення у формат PDF/A +pdfToPDFA.credit=Цей сервіс використовує ghostscript для перетворення у формат PDF/A pdfToPDFA.submit=Конвертувати pdfToPDFA.tip=Наразі не працює для кількох вхідних файлів одночасно pdfToPDFA.outputFormat=Вихідний формат diff --git a/src/main/resources/messages_vi_VN.properties b/src/main/resources/messages_vi_VN.properties index 28ef3fa182..245129e98d 100644 --- a/src/main/resources/messages_vi_VN.properties +++ b/src/main/resources/messages_vi_VN.properties @@ -77,7 +77,11 @@ color=Màu sắc sponsor=Nhà tài trợ info=Thông tin - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=Thay đổi #pdfToPDFA pdfToPDFA.title=PDF sang PDF/A pdfToPDFA.header=PDF sang PDF/A -pdfToPDFA.credit=Dịch vụ này sử dụng OCRmyPDF để chuyển đổi PDF/A +pdfToPDFA.credit=Dịch vụ này sử dụng ghostscript để chuyển đổi PDF/A pdfToPDFA.submit=Chuyển đổi pdfToPDFA.tip=Hiện tại không hoạt động với nhiều đầu vào cùng lúc pdfToPDFA.outputFormat=Định dạng đầu ra diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index 979b8b447f..80e9e0cf5b 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -56,12 +56,12 @@ userNotFoundMessage=未找到用户。 incorrectPasswordMessage=当前密码不正确。 usernameExistsMessage=新用户名已存在。 invalidUsernameMessage=用户名无效,用户名只能包含字母、数字和以下特殊字符@._+- 或必须是有效的电子邮件地址。 -invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end. +invalidPasswordMessage=密码不能为空且开头和结尾不能有空格。 confirmPasswordErrorMessage=两次密码不一致。 deleteCurrentUserMessage=无法删除当前登录的用户。 deleteUsernameExistsMessage=用户名不存在,无法删除。 downgradeCurrentUserMessage=无法降级当前用户的角色 -disabledCurrentUserMessage=The current user cannot be disabled +disabledCurrentUserMessage=无法禁用当前用户。 downgradeCurrentUserLongMessage=无法降级当前用户的角色。因此,当前用户将不会显示。 userAlreadyExistsOAuthMessage=该用户已作为OAuth2用户存在。 userAlreadyExistsWebMessage=该用户已作为Web用户存在。 @@ -77,7 +77,11 @@ color=颜色 sponsor=赞助 info=信息 - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -180,7 +184,7 @@ adminUserSettings.user=用户 adminUserSettings.addUser=添加新用户 adminUserSettings.deleteUser=删除用户 adminUserSettings.confirmDeleteUser=确认删除该用户? -adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled? +adminUserSettings.confirmChangeUserStatus=是否应禁用/启用该用户? adminUserSettings.usernameInfo=用户名只能包含字母、数字和以下特殊字符@._+-,或者必须是有效的电子邮件地址。 adminUserSettings.roles=角色 adminUserSettings.role=角色 @@ -194,13 +198,13 @@ adminUserSettings.forceChange=强制用户在登录时更改用户名/密码 adminUserSettings.submit=保存用户 adminUserSettings.changeUserRole=更改用户角色 adminUserSettings.authenticated=已验证 -adminUserSettings.editOwnProfil=Edit own profile -adminUserSettings.enabledUser=enabled user -adminUserSettings.disabledUser=disabled user -adminUserSettings.activeUsers=Active Users: -adminUserSettings.disabledUsers=Disabled Users: -adminUserSettings.totalUsers=Total Users: -adminUserSettings.lastRequest=Last Request +adminUserSettings.editOwnProfil=编辑个人资料 +adminUserSettings.enabledUser=启用用户 +adminUserSettings.disabledUser=禁用用户 +adminUserSettings.activeUsers=激活用户: +adminUserSettings.disabledUsers=禁用用户: +adminUserSettings.totalUsers=总用户: +adminUserSettings.lastRequest=最后登录 database.title=数据库 导入/导出 @@ -472,9 +476,9 @@ home.BookToPDF.title=电子书转PDF home.BookToPDF.desc=使用Calibre将电子书/漫画转换成PDF BookToPDF.tags=电子书、漫画、Calibre、转换、日本漫画、亚马逊、kindle -home.removeImagePdf.title=Remove image -home.removeImagePdf.desc=Remove image from PDF to reduce file size -removeImagePdf.tags=Remove Image,Page operations,Back end,server side +home.removeImagePdf.title=删除图像 +home.removeImagePdf.desc=删除图像减少PDF大小 +removeImagePdf.tags=删除图像, 页面操作, 后端, 服务端 ########################### @@ -492,14 +496,14 @@ login.locked=您的账户已被锁定。 login.signinTitle=请登录 login.ssoSignIn=通过单点登录登录 login.oauth2AutoCreateDisabled=OAuth2自动创建用户已禁用 -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oauth2AdminBlockedUser=目前已阻止未注册用户的注册或登录。请联系管理员。 login.oauth2RequestNotFound=找不到验证请求 login.oauth2InvalidUserInfoResponse=无效的用户信息响应 login.oauth2invalidRequest=无效请求 login.oauth2AccessDenied=拒绝访问 login.oauth2InvalidTokenResponse=无效的Token响应 login.oauth2InvalidIdToken=无效的Token -login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. +login.userIsDisabled=用户被禁用,登录已被阻止。请联系管理员。 #auto-redact @@ -724,8 +728,8 @@ removeAnnotations.submit=删除 #compare compare.title=比较 compare.header=比较PDF -compare.highlightColor.1=Highlight Color 1: -compare.highlightColor.2=Highlight Color 2: +compare.highlightColor.1=高亮颜色 1: +compare.highlightColor.2=高亮颜色 2: compare.document.1=文档 1 compare.document.2=文档 2 compare.submit=比较 @@ -777,7 +781,7 @@ ScannerImageSplit.selectText.7=最小轮廓面积: ScannerImageSplit.selectText.8=设置照片的最小轮廓面积阈值。 ScannerImageSplit.selectText.9=边框尺寸: ScannerImageSplit.selectText.10=设置添加和删除的边框大小,以防止输出中出现白边(默认值:1)。 -ScannerImageSplit.info=Python is not installed. It is required to run. +ScannerImageSplit.info=此功能需要安装Python #OCR @@ -804,7 +808,7 @@ ocr.submit=用OCR处理PDF extractImages.title=提取图像 extractImages.header=提取图像 extractImages.selectText=选择图像格式,将提取的图像转换为 -extractImages.allowDuplicates=Save duplicate images +extractImages.allowDuplicates=保存重复图像 extractImages.submit=提取 @@ -929,7 +933,7 @@ pdfToImage.color=颜色 pdfToImage.grey=灰度 pdfToImage.blackwhite=黑白(可能会丢失数据!)。 pdfToImage.submit=转换 -pdfToImage.info=Python is not installed. Required for WebP conversion. +pdfToImage.info=WebP转换需要安装Python #addPassword @@ -966,7 +970,7 @@ watermark.selectText.6=垂直间距(每个水印之间的垂直距离): watermark.selectText.7=透明度(0% - 100%): watermark.selectText.8=水印类型: watermark.selectText.9=水印图片: -watermark.selectText.10=Convert PDF to PDF-Image +watermark.selectText.10=将PDF转换为PDF-Image watermark.submit=添加水印 watermark.type.1=文字 watermark.type.2=图片 @@ -1019,7 +1023,7 @@ changeMetadata.submit=更改 #pdfToPDFA pdfToPDFA.title=PDF转PDF/A pdfToPDFA.header=将PDF转换为PDF/A -pdfToPDFA.credit=此服务使用OCRmyPDF进行PDF/A转换 +pdfToPDFA.credit=此服务使用ghostscript进行PDF/A转换 pdfToPDFA.submit=转换 pdfToPDFA.tip=目前不支持上传多个 pdfToPDFA.outputFormat=输出格式 @@ -1149,7 +1153,7 @@ error.discordSubmit=Discord - 提交支持帖子 #remove-image -removeImage.title=Remove image -removeImage.header=Remove image -removeImage.removeImage=Remove image -removeImage.submit=Remove image +removeImage.title=删除图像 +removeImage.header=删除图像 +removeImage.removeImage=删除图像 +removeImage.submit=删除图像 diff --git a/src/main/resources/messages_zh_TW.properties b/src/main/resources/messages_zh_TW.properties index 096a562537..221e46af41 100644 --- a/src/main/resources/messages_zh_TW.properties +++ b/src/main/resources/messages_zh_TW.properties @@ -77,7 +77,11 @@ color=顏色 sponsor=贊助 info=Info - +legal.privacy=Privacy Policy +legal.terms=Terms and Conditions +legal.accessibility=Accessibility +legal.cookie=Cookie Policy +legal.impressum=Impressum ############### # Pipeline # @@ -1019,7 +1023,7 @@ changeMetadata.submit=變更 #pdfToPDFA pdfToPDFA.title=PDF 轉 PDF/A pdfToPDFA.header=PDF 轉 PDF/A -pdfToPDFA.credit=此服務使用 OCRmyPDF 進行 PDF/A 轉換 +pdfToPDFA.credit=此服務使用 ghostscript 進行 PDF/A 轉換 pdfToPDFA.submit=轉換 pdfToPDFA.tip=目前不支援上傳多個 pdfToPDFA.outputFormat=輸出格式 diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index 2e06ea9f85..2a5400b601 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -48,6 +48,22 @@ security: scopes: openid, profile, email # Specify the scopes for which the application will request permissions provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak' +# Enterprise edition settings unused for now please ignore! +EnterpriseEdition: + key: 00000000-0000-0000-0000-000000000000 + CustomMetadata: + autoUpdateMetadata: true # set to 'true' to automatically update metadata with below values + author: username # Supports text such as 'John Doe' or types such as username + creator: Stirling-PDF # Supports text such as 'Company-PDF' + producer: Stirling-PDF # Supports text such as 'Company-PDF' + +legal: + termsAndConditions: '' # URL to the terms and conditions of your application (e.g. https://example.com/terms) Empty string to disable or filename to load from local file in static folder + privacyPolicy: '' # URL to the privacy policy of your application (e.g. https://example.com/privacy) Empty string to disable or filename to load from local file in static folder + accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility) Empty string to disable or filename to load from local file in static folder + cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie) Empty string to disable or filename to load from local file in static folder + impressum: '' # URL to the impressum of your application (e.g. https://example.com/impressum) Empty string to disable or filename to load from local file in static folder + 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 diff --git a/src/main/resources/static/3rdPartyLicenses.json b/src/main/resources/static/3rdPartyLicenses.json index 942e3927e3..703702db97 100644 --- a/src/main/resources/static/3rdPartyLicenses.json +++ b/src/main/resources/static/3rdPartyLicenses.json @@ -86,7 +86,7 @@ }, { "moduleName": "com.fathzer:javaluator", - "moduleVersion": "3.0.4", + "moduleVersion": "3.0.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -286,7 +286,7 @@ { "moduleName": "io.micrometer:micrometer-core", "moduleUrl": "https://github.com/micrometer-metrics/micrometer", - "moduleVersion": "1.13.3", + "moduleVersion": "1.13.4", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -573,13 +573,13 @@ }, { "moduleName": "org.commonmark:commonmark", - "moduleVersion": "0.22.0", + "moduleVersion": "0.23.0", "moduleLicense": "BSD 2-Clause License", "moduleLicenseUrl": "https://opensource.org/licenses/BSD-2-Clause" }, { "moduleName": "org.commonmark:commonmark-ext-gfm-tables", - "moduleVersion": "0.22.0", + "moduleVersion": "0.23.0", "moduleLicense": "BSD 2-Clause License", "moduleLicenseUrl": "https://opensource.org/licenses/BSD-2-Clause" }, @@ -1143,7 +1143,7 @@ { "moduleName": "org.springframework:spring-webmvc", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.9", + "moduleVersion": "6.1.13", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, diff --git a/src/main/resources/static/css/home.css b/src/main/resources/static/css/home.css index 07a1350fe0..12b69d94c4 100644 --- a/src/main/resources/static/css/home.css +++ b/src/main/resources/static/css/home.css @@ -10,6 +10,26 @@ outline-color: var(--md-sys-color-outline-variant); } +#filtersContainer { + display: flex; + width: 100%; + align-items: center; + justify-content: center; + gap: 10px; +} + +.filter-button { + color: var(--md-sys-color-secondary); + user-select: none; + cursor: pointer; + transition: transform 0.3s; + transform-origin: center center; +} + +.filter-button:hover { + transform: scale(1.08); +} + .search-icon { position: absolute; margin: 0.75rem 1rem; @@ -17,9 +37,50 @@ } .features-container { + display: flex; + flex-direction: column; + gap: 30px; +} + +.feature-group { + display: flex; + flex-direction: column; +} + +.feature-group-header { + display: flex; + align-items: center; + justify-content: flex-start; + color: var(--md-sys-color-on-surface); + margin-bottom: 15px; + user-select: none; + cursor: pointer; + gap: 10px; +} + +.feature-group-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr)); gap: 30px 30px; + transition: 0.5s all; + overflow: hidden; + margin: -20px; + padding: 20px; +} + +.feature-group.collapsed>.feature-group-container { + max-height: 0 !important; + margin: 0; + padding: 0; +} + +.header-expand-button { + transition: 0.5s all; + transform: rotate(90deg); +} + +.header-expand-button.collapsed { + transform: rotate(0deg); } .feature-card { @@ -96,6 +157,14 @@ filter: brightness(0) invert(var(--md-theme-filter-color)); } +.favorite-icon:hover .material-symbols-rounded { + transform: scale(1.2); +} + +.favorite-icon .material-symbols-rounded.fill{ + color: #f5c000; +} + .jumbotron { padding: 3rem 3rem; /* Reduce vertical padding */ @@ -151,5 +220,5 @@ } .hidden { - visibility: hidden; + visibility: hidden; } diff --git a/src/main/resources/static/css/navbar.css b/src/main/resources/static/css/navbar.css index a8845f297c..96255e0c8a 100644 --- a/src/main/resources/static/css/navbar.css +++ b/src/main/resources/static/css/navbar.css @@ -128,6 +128,34 @@ span.icon-text::after { color: var(--md-sys-color-on-surface-variant); } +.nav-item { + position: relative; +} + +.tooltip-text { + visibility: hidden; + background-color: rgb(71 67 67 / 80%); + color: #fff; + text-align: center; + border-radius: 4px; + padding: 5px; + position: absolute; + z-index: 1; + top: 100%; + left: 50%; + transform: translateX(-50%); + opacity: 0; + transition: opacity 0.3s; + font-size: 12px; + white-space: nowrap; + margin-top: 5px; +} + +.nav-item:hover .tooltip-text { + visibility: visible; + opacity: 1; +} + .dropdown-menu.scrollable-y { overflow-y: scroll; height: 360px; @@ -136,7 +164,7 @@ span.icon-text::after { /* Dropdown Scrollbar*/ .scrollable-y { overflow-y: scroll; - height: 360px; + height: 190px; } .scrollable-y::-webkit-scrollbar { @@ -289,4 +317,4 @@ span.icon-text::after { .icon-hide { display: none; } -} \ No newline at end of file +} diff --git a/src/main/resources/static/css/theme/componentes.css b/src/main/resources/static/css/theme/componentes.css index 7e3f744e9c..d2c700cea7 100644 --- a/src/main/resources/static/css/theme/componentes.css +++ b/src/main/resources/static/css/theme/componentes.css @@ -82,16 +82,22 @@ td { .modal-body, .modal-footer { background-color: var(--md-sys-color-surface-5); - padding: 1.5rem 2rem; + border: none; } .modal-header { border-radius: 2rem 2rem 0rem 0rem; + padding: 1.5rem 2rem 0.5rem; +} + +.modal-body{ + padding: 0.5rem 2rem; } .modal-footer { border-radius: 0rem 0rem 2rem 2rem; + padding: 0.5rem 2rem 1.5rem; } /* Icon fill */ @@ -538,6 +544,9 @@ fieldset:disabled .btn { } /* Range Slider */ +.form-range{ + margin-top: 0.25rem; +} .form-range:focus::-webkit-slider-thumb { box-shadow: 0 0 0 1px var(--md-sys-color-surface), 0 0 0 .25rem var(--md-sys-color-primary) } @@ -792,6 +801,23 @@ textarea.form-control { box-shadow: 0 0 0 0.25rem var(--md-sys-color-outline-variant); } +.form-control-color { + padding: 0; + height: 2.4rem; + width: 2.4rem; +} + +.form-control input[type="color"] { + opacity: 0; + height: 2.4rem; + width: 2.4rem; + box-sizing: border-box; + } + + .form-control input[type="color"]:hover{ + cursor: pointer; + } + /* Navbar Components */ .navbar-brand { color: var(--md-sys-color-on-surface) !important; @@ -916,4 +942,4 @@ textarea.form-control { color: var(--md-sys-color-on-error-container); background-color: var(--md-sys-color-error-container); border-color: transparent; -} \ No newline at end of file +} diff --git a/src/main/resources/static/css/theme/font.css b/src/main/resources/static/css/theme/font.css index f245dd32d0..547db7ca17 100644 --- a/src/main/resources/static/css/theme/font.css +++ b/src/main/resources/static/css/theme/font.css @@ -19,4 +19,4 @@ direction: ltr; -webkit-font-feature-settings: 'liga'; -webkit-font-smoothing: antialiased; -} \ No newline at end of file +} diff --git a/src/main/resources/static/css/theme/theme.css b/src/main/resources/static/css/theme/theme.css index 4f7be33e83..2e736ede7c 100644 --- a/src/main/resources/static/css/theme/theme.css +++ b/src/main/resources/static/css/theme/theme.css @@ -31,4 +31,4 @@ .no-fill { /* font-variation-settings: var(--md-sys-icon-fill-0); */ -} \ No newline at end of file +} diff --git a/src/main/resources/static/js/homecard.js b/src/main/resources/static/js/homecard.js index 818e62e986..4cdecdb7e9 100644 --- a/src/main/resources/static/js/homecard.js +++ b/src/main/resources/static/js/homecard.js @@ -22,6 +22,26 @@ function filterCards() { } } +function updateFavoritesSection() { + const favoritesContainer = document.getElementById("groupFavorites").querySelector(".feature-group-container"); + favoritesContainer.innerHTML = ""; + const cards = Array.from(document.querySelectorAll(".feature-card")); + let favoritesAmount = 0; + cards.forEach(card => { + if (localStorage.getItem(card.id) === "favorite") { + const duplicate = card.cloneNode(true); + favoritesContainer.appendChild(duplicate); + favoritesAmount++; + } + }); + if (favoritesAmount === 0) { + document.getElementById("groupFavorites").style.display = "none"; + } else { + document.getElementById("groupFavorites").style.display = "flex"; + }; + reorderCards(favoritesContainer); +}; + function toggleFavorite(element) { var span = element.querySelector("span.material-symbols-rounded"); var card = element.closest(".feature-card"); @@ -37,15 +57,17 @@ function toggleFavorite(element) { card.classList.remove("favorite"); localStorage.removeItem(cardId); } - reorderCards(); + reorderCards(card.parentNode); + updateFavoritesSection(); updateFavoritesDropdown(); filterCards(); } - -function reorderCards() { - var container = document.querySelector(".features-container"); - var cards = Array.from(container.getElementsByClassName("feature-card")); +function reorderCards(container) { + var cards = Array.from(container.querySelectorAll(".feature-card")); + cards.forEach(function (card) { + container.removeChild(card); + }); cards.sort(function (a, b) { var aIsFavorite = localStorage.getItem(a.id) === "favorite"; var bIsFavorite = localStorage.getItem(b.id) === "favorite"; @@ -55,19 +77,29 @@ function reorderCards() { if (b.id === "update-link") { return 1; } + if (aIsFavorite && !bIsFavorite) { return -1; } - if (!aIsFavorite && bIsFavorite) { + else if (!aIsFavorite && bIsFavorite) { return 1; } - return 0; + else { + return a.id > b.id; + } }); cards.forEach(function (card) { container.appendChild(card); }); } +function reorderAllCards() { + const containers = Array.from(document.querySelectorAll(".feature-group-container")); + containers.forEach(function (container) { + reorderCards(container); + }) +} + function initializeCards() { var cards = document.querySelectorAll(".feature-card"); cards.forEach(function (card) { @@ -79,21 +111,107 @@ function initializeCards() { card.classList.add("favorite"); } }); - reorderCards(); + reorderAllCards(); + updateFavoritesSection(); updateFavoritesDropdown(); filterCards(); } +function showFavoritesOnly() { + const groups = Array.from(document.querySelectorAll(".feature-group")); + if (localStorage.getItem("favoritesOnly") === "true") { + groups.forEach((group) => { + if (group.id !== "groupFavorites") { + group.style.display = "none"; + }; + }) + } else { + groups.forEach((group) => { + if (group.id !== "groupFavorites") { + group.style.display = "flex"; + }; + }) + }; +} + +function toggleFavoritesOnly() { + if (localStorage.getItem("favoritesOnly") === "true") { + localStorage.setItem("favoritesOnly", "false"); + } else { + localStorage.setItem("favoritesOnly", "true"); + } + showFavoritesOnly(); +} + +// Expands a feature group on true, collapses it on false and toggles state on null. +function expandCollapseToggle(group, expand = null) { + if (expand === null) { + group.classList.toggle("collapsed"); + group.querySelector(".header-expand-button").classList.toggle("collapsed"); + } else if (expand) { + group.classList.remove("collapsed"); + group.querySelector(".header-expand-button").classList.remove("collapsed"); + } else { + group.classList.add("collapsed"); + group.querySelector(".header-expand-button").classList.add("collapsed"); + } + + const collapsed = localStorage.getItem("collapsedGroups") ? JSON.parse(localStorage.getItem("collapsedGroups")) : []; + const groupIndex = collapsed.indexOf(group.id); + + if (group.classList.contains("collapsed")) { + if (groupIndex === -1) { + collapsed.push(group.id); + } + } else { + if (groupIndex !== -1) { + collapsed.splice(groupIndex, 1); + } + } + + localStorage.setItem("collapsedGroups", JSON.stringify(collapsed)); +} + +function expandCollapseAll(expandAll) { + const groups = Array.from(document.querySelectorAll(".feature-group")); + groups.forEach((group) => { + expandCollapseToggle(group, expandAll); + }) +} + window.onload = initializeCards; - document.addEventListener("DOMContentLoaded", function() { - const materialIcons = new FontFaceObserver('Material Symbols Rounded'); - - materialIcons.load().then(() => { - document.querySelectorAll('.feature-card.hidden').forEach(el => { - el.classList.remove('hidden'); - }); - }).catch(() => { - console.error('Material Symbols Rounded font failed to load.'); +document.addEventListener("DOMContentLoaded", function () { + const materialIcons = new FontFaceObserver('Material Symbols Rounded'); + + materialIcons.load().then(() => { + document.querySelectorAll('.feature-card.hidden').forEach(el => { + el.classList.remove('hidden'); }); + }).catch(() => { + console.error('Material Symbols Rounded font failed to load.'); }); + + Array.from(document.querySelectorAll(".feature-group-header")).forEach(header => { + const parent = header.parentNode; + const container = header.parentNode.querySelector(".feature-group-container"); + if (parent.id !== "groupFavorites") { + container.style.maxHeight = container.clientHeight + "px"; + } else { + container.style.maxHeight = "500px"; + } + header.onclick = () => { + expandCollapseToggle(parent); + }; + }) + + const collapsed = localStorage.getItem("collapsedGroups") ? JSON.parse(localStorage.getItem("collapsedGroups")) : []; + + Array.from(document.querySelectorAll(".feature-group")).forEach(group => { + if (collapsed.indexOf(group.id) !== -1) { + expandCollapseToggle(group, false); + } + }) + + showFavoritesOnly(); +}); diff --git a/src/main/resources/static/js/multitool/ImageHighlighter.js b/src/main/resources/static/js/multitool/ImageHighlighter.js index 7fc53209ed..cf5d161a37 100644 --- a/src/main/resources/static/js/multitool/ImageHighlighter.js +++ b/src/main/resources/static/js/multitool/ImageHighlighter.js @@ -29,6 +29,7 @@ class ImageHighlighter { imageClickEvent.stopPropagation(); }; bigImg.src = highlightEvent.target.src; + bigImg.style.rotate = highlightEvent.target.style.rotate; this.imageHighlighter.appendChild(bigImg); } diff --git a/src/main/resources/static/js/multitool/PdfContainer.js b/src/main/resources/static/js/multitool/PdfContainer.js index 0654cac930..55635c69e8 100644 --- a/src/main/resources/static/js/multitool/PdfContainer.js +++ b/src/main/resources/static/js/multitool/PdfContainer.js @@ -446,7 +446,7 @@ function detectImageType(uint8Array) { // Check for TIFF signature (little-endian and big-endian) if ((uint8Array[0] === 73 && uint8Array[1] === 73 && uint8Array[2] === 42 && uint8Array[3] === 0) || - (uint8Array[0] === 77 && uint8Array[1] === 77 && uint8Array[2] === 0 && uint8Array[3] === 42)) { + (uint8Array[0] === 77 && uint8Array[1] === 77 && uint8Array[2] === 0 && uint8Array[3] === 42)) { return 'TIFF'; } diff --git a/src/main/resources/static/pdfjs-legacy/images/toolBarButton-home.svg b/src/main/resources/static/pdfjs-legacy/images/toolbarButton-home.svg similarity index 100% rename from src/main/resources/static/pdfjs-legacy/images/toolBarButton-home.svg rename to src/main/resources/static/pdfjs-legacy/images/toolbarButton-home.svg diff --git a/src/main/resources/templates/convert/file-to-pdf.html b/src/main/resources/templates/convert/file-to-pdf.html index 23b88b8fa0..062abda3a0 100644 --- a/src/main/resources/templates/convert/file-to-pdf.html +++ b/src/main/resources/templates/convert/file-to-pdf.html @@ -41,9 +41,10 @@ https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html -
- - +
+
+ +
diff --git a/src/main/resources/templates/convert/img-to-pdf.html b/src/main/resources/templates/convert/img-to-pdf.html index 755f6b5a5d..758e832894 100644 --- a/src/main/resources/templates/convert/img-to-pdf.html +++ b/src/main/resources/templates/convert/img-to-pdf.html @@ -40,7 +40,6 @@ -
@@ -49,7 +48,7 @@
-

+
- + @@ -34,12 +34,6 @@ - - - - - - @@ -58,17 +52,21 @@ - + + + + + - + - + diff --git a/src/main/resources/templates/fragments/featureGroupHeader.html b/src/main/resources/templates/fragments/featureGroupHeader.html new file mode 100644 index 0000000000..0a8f7e9b12 --- /dev/null +++ b/src/main/resources/templates/fragments/featureGroupHeader.html @@ -0,0 +1,6 @@ +
+ + + chevron_right + +
\ No newline at end of file diff --git a/src/main/resources/templates/fragments/footer.html b/src/main/resources/templates/fragments/footer.html index 7d0c1a5484..40f64419a3 100644 --- a/src/main/resources/templates/fragments/footer.html +++ b/src/main/resources/templates/fragments/footer.html @@ -5,6 +5,11 @@ diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index 4e8e7219a8..b852569d7d 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -316,7 +316,7 @@ dark_mode - + @@ -415,7 +415,7 @@
- +
diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index 30c295a514..f8f85440ce 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -28,9 +28,26 @@

search + +
+ + star + + + expand_all + + + collapse_all + + +
+
-