diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml new file mode 100644 index 00000000..c1e6774e --- /dev/null +++ b/.github/workflows/swagger.yml @@ -0,0 +1,37 @@ +name: Update Swagger + +on: + workflow_dispatch: + push: + branches: + - master +jobs: + push: + + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v3.5.2 + + - name: Set up JDK 17 + uses: actions/setup-java@v3.11.0 + with: + java-version: '17' + distribution: 'temurin' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Generate Swagger documentation + run: ./gradlew generateOpenApiDocs + + - name: Upload Swagger Documentation to SwaggerHub + run: ./gradlew swaggerhubUpload + env: + SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} + + - name: Set API version as published and default on SwaggerHub + run: | + curl -X PUT -H "Authorization: ${SWAGGERHUB_API_KEY}" "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}/settings/lifecycle" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"published\":true,\"default\":true}" + env: + SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3c11770c..0a5cd198 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ local.properties .recommenders .classpath .project +version.properties # Gradle .gradle diff --git a/build.gradle b/build.gradle index ea16d563..5e77d666 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,8 @@ plugins { id 'java' id 'org.springframework.boot' version '3.1.0' id 'io.spring.dependency-management' version '1.1.0' + id 'org.springdoc.openapi-gradle-plugin' version '1.6.0' + id "io.swagger.swaggerhub" version "1.1.0" } group = 'stirling.software' @@ -12,6 +14,12 @@ repositories { mavenCentral() } +openApi { + apiDocsUrl = "http://localhost:8080/v3/api-docs" + outputDir = file("$projectDir") + outputFileName = "SwaggerDoc.json" +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.0' @@ -34,6 +42,25 @@ dependencies { } +task writeVersion { + def propsFile = file('src/main/resources/version.properties') + def props = new Properties() + props.setProperty('version', version) + props.store(propsFile.newWriter(), null) +} + +swaggerhubUpload { + //dependsOn generateOpenApiDocs // Depends on your task generating Swagger docs + api 'Stirling-PDF' // The name of your API on SwaggerHub + owner 'Frooodle' // Your SwaggerHub username (or organization name) + version project.version // The version of your API + inputFile './SwaggerDoc.json' // The path to your Swagger docs + token "${System.getenv('SWAGGERHUB_API_KEY')}" // Your SwaggerHub API key, passed as an environment variable + oas '3.0.0' // The version of the OpenAPI Specification you're using +} + + + jar { enabled = false manifest { diff --git a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java index 0ab63952..d7495aca 100644 --- a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java +++ b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java @@ -1,5 +1,9 @@ package stirling.software.SPDF.config; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -10,13 +14,23 @@ import io.swagger.v3.oas.models.info.Info; @Configuration public class OpenApiConfig { - @Bean - public OpenAPI customOpenAPI() { - String version = getClass().getPackage().getImplementationVersion(); - version = (version != null) ? version : "1.0.0"; - - return new OpenAPI().components(new Components()).info( - new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); - } + @Bean + public OpenAPI customOpenAPI() { + String version = getClass().getPackage().getImplementationVersion(); + if (version == null) { + Properties props = new Properties(); + try (InputStream input = getClass().getClassLoader().getResourceAsStream("version.properties")) { + props.load(input); + version = props.getProperty("version"); + } catch (IOException ex) { + ex.printStackTrace(); + version = "1.0.0"; // default version if all else fails + } + } + + return new OpenAPI().components(new Components()).info( + new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); + } + } 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 718f8520..c1a0c892 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java @@ -1,9 +1,6 @@ package stirling.software.SPDF.controller.api; import java.io.IOException; -import io.swagger.v3.oas.annotations.media.Schema; -import stirling.software.SPDF.utils.WebResponseUtils; - import java.util.ArrayList; import java.util.List; @@ -20,232 +17,191 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import stirling.software.SPDF.utils.GeneralUtils; +import stirling.software.SPDF.utils.WebResponseUtils; @RestController public class RearrangePagesPDFController { - private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); + private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); + @PostMapping(consumes = "multipart/form-data", value = "/remove-pages") + @Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete.") + public ResponseEntity deletePages( + @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file from which pages will be removed") MultipartFile pdfFile, + @RequestParam("pagesToDelete") @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") String pagesToDelete) + throws IOException { - @PostMapping(consumes = "multipart/form-data", value = "/remove-pages") - @Operation(summary = "Remove pages from a PDF file", - description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete.") - public ResponseEntity deletePages( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file from which pages will be removed") - MultipartFile pdfFile, - @RequestParam("pagesToDelete") - @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") - String pagesToDelete) throws IOException { + PDDocument document = PDDocument.load(pdfFile.getBytes()); - PDDocument document = PDDocument.load(pdfFile.getBytes()); + // Split the page order string into an array of page numbers or range of numbers + String[] pageOrderArr = pagesToDelete.split(","); - // Split the page order string into an array of page numbers or range of numbers - String[] pageOrderArr = pagesToDelete.split(","); + List pagesToRemove = GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); - List pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages()); + for (int i = pagesToRemove.size() - 1; i >= 0; i--) { + int pageIndex = pagesToRemove.get(i); + document.removePage(pageIndex); + } + return WebResponseUtils.pdfDocToWebResponse(document, + pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf"); - for (int i = pagesToRemove.size() - 1; i >= 0; i--) { - int pageIndex = pagesToRemove.get(i); - document.removePage(pageIndex); - } - return WebResponseUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf"); + } - } + private enum CustomMode { + REVERSE_ORDER, DUPLEX_SORT, BOOKLET_SORT, ODD_EVEN_SPLIT, REMOVE_FIRST, REMOVE_LAST, REMOVE_FIRST_AND_LAST, + } - private List pageOrderToString(String[] pageOrderArr, int totalPages) { - List newPageOrder = new ArrayList<>(); - // loop through the page order array - for (String element : pageOrderArr) { - // check if the element contains a range of pages - if (element.contains("-")) { - // split the range into start and end page - String[] range = element.split("-"); - int start = Integer.parseInt(range[0]); - int end = Integer.parseInt(range[1]); - // check if the end page is greater than total pages - if (end > totalPages) { - end = totalPages; - } - // loop through the range of pages - for (int j = start; j <= end; j++) { - // print the current index - newPageOrder.add(j - 1); - } - } else { - // if the element is a single page - newPageOrder.add(Integer.parseInt(element) - 1); - } - } + private List removeFirst(int totalPages) { + if (totalPages <= 1) + return new ArrayList<>(); + List newPageOrder = new ArrayList<>(); + for (int i = 2; i <= totalPages; i++) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - return newPageOrder; - } + private List removeLast(int totalPages) { + if (totalPages <= 1) + return new ArrayList<>(); + List newPageOrder = new ArrayList<>(); + for (int i = 1; i < totalPages; i++) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - private enum CustomMode { - REVERSE_ORDER, - DUPLEX_SORT, - BOOKLET_SORT, - ODD_EVEN_SPLIT, - REMOVE_FIRST, - REMOVE_LAST, - REMOVE_FIRST_AND_LAST, - } + private List removeFirstAndLast(int totalPages) { + if (totalPages <= 2) + return new ArrayList<>(); + List newPageOrder = new ArrayList<>(); + for (int i = 2; i < totalPages; i++) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - private List removeFirst(int totalPages) { - if (totalPages <= 1) return new ArrayList<>(); - List newPageOrder = new ArrayList<>(); - for (int i = 2; i <= totalPages; i++) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } + private List reverseOrder(int totalPages) { + List newPageOrder = new ArrayList<>(); + for (int i = totalPages; i >= 1; i--) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - private List removeLast(int totalPages) { - if (totalPages <= 1) return new ArrayList<>(); - List newPageOrder = new ArrayList<>(); - for (int i = 1; i < totalPages; i++) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } + private List duplexSort(int totalPages) { + List newPageOrder = new ArrayList<>(); + int half = (totalPages + 1) / 2; // This ensures proper behavior with odd numbers of pages + for (int i = 1; i <= half; i++) { + newPageOrder.add(i - 1); + if (i <= totalPages - half) { // Avoid going out of bounds + newPageOrder.add(totalPages - i); + } + } + return newPageOrder; + } - private List removeFirstAndLast(int totalPages) { - if (totalPages <= 2) return new ArrayList<>(); - List newPageOrder = new ArrayList<>(); - for (int i = 2; i < totalPages; i++) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } + private List bookletSort(int totalPages) { + List newPageOrder = new ArrayList<>(); + for (int i = 0; i < totalPages / 2; i++) { + newPageOrder.add(i); + newPageOrder.add(totalPages - i - 1); + } + return newPageOrder; + } - - private List reverseOrder(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = totalPages; i >= 1; i--) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } + private List oddEvenSplit(int totalPages) { + List newPageOrder = new ArrayList<>(); + for (int i = 1; i <= totalPages; i += 2) { + newPageOrder.add(i - 1); + } + for (int i = 2; i <= totalPages; i += 2) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - private List duplexSort(int totalPages) { - List newPageOrder = new ArrayList<>(); - int half = (totalPages + 1) / 2; // This ensures proper behavior with odd numbers of pages - for (int i = 1; i <= half; i++) { - newPageOrder.add(i - 1); - if (i <= totalPages - half) { // Avoid going out of bounds - newPageOrder.add(totalPages - i); - } - } - return newPageOrder; - } + private List processCustomMode(String customMode, int totalPages) { + try { + CustomMode mode = CustomMode.valueOf(customMode.toUpperCase()); + switch (mode) { + case REVERSE_ORDER: + return reverseOrder(totalPages); + case DUPLEX_SORT: + return duplexSort(totalPages); + case BOOKLET_SORT: + return bookletSort(totalPages); + case ODD_EVEN_SPLIT: + return oddEvenSplit(totalPages); + case REMOVE_FIRST: + return removeFirst(totalPages); + case REMOVE_LAST: + return removeLast(totalPages); + case REMOVE_FIRST_AND_LAST: + return removeFirstAndLast(totalPages); + default: + throw new IllegalArgumentException("Unsupported custom mode"); + } + } catch (IllegalArgumentException e) { + logger.error("Unsupported custom mode", e); + return null; + } + } + @PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages") + @Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode.") + public ResponseEntity rearrangePages( + @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to rearrange pages") MultipartFile pdfFile, + @RequestParam(required = false, value = "pageOrder") @Parameter(description = "The new page order as a comma-separated list of page numbers, page ranges (e.g., '1,3,5-7'), or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')") String pageOrder, + @RequestParam(required = false, value = "customMode") @Parameter(schema = @Schema(implementation = CustomMode.class, description = "The custom mode for page rearrangement. " + + "Valid values are:\n" + "REVERSE_ORDER: Reverses the order of all pages.\n" + + "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). " + + "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n" + + "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n" + + "REMOVE_FIRST: Removes the first page.\n" + "REMOVE_LAST: Removes the last page.\n" + + "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n")) String customMode) { + try { + // Load the input PDF + PDDocument document = PDDocument.load(pdfFile.getInputStream()); - private List bookletSort(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = 0; i < totalPages / 2; i++) { - newPageOrder.add(i); - newPageOrder.add(totalPages - i - 1); - } - return newPageOrder; - } + // Split the page order string into an array of page numbers or range of numbers + String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; + int totalPages = document.getNumberOfPages(); + System.out.println("pageOrder=" + pageOrder); + System.out.println("customMode length =" + customMode.length()); + List newPageOrder; + if (customMode != null && customMode.length() > 0) { + newPageOrder = processCustomMode(customMode, totalPages); + } else { + newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages); + } - private List oddEvenSplit(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = 1; i <= totalPages; i += 2) { - newPageOrder.add(i - 1); - } - for (int i = 2; i <= totalPages; i += 2) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } + // Create a new list to hold the pages in the new order + List newPages = new ArrayList<>(); + for (int i = 0; i < newPageOrder.size(); i++) { + newPages.add(document.getPage(newPageOrder.get(i))); + } - private List processCustomMode(String customMode, int totalPages) { - try { - CustomMode mode = CustomMode.valueOf(customMode.toUpperCase()); - switch (mode) { - case REVERSE_ORDER: - return reverseOrder(totalPages); - case DUPLEX_SORT: - return duplexSort(totalPages); - case BOOKLET_SORT: - return bookletSort(totalPages); - case ODD_EVEN_SPLIT: - return oddEvenSplit(totalPages); - case REMOVE_FIRST: - return removeFirst(totalPages); - case REMOVE_LAST: - return removeLast(totalPages); - case REMOVE_FIRST_AND_LAST: - return removeFirstAndLast(totalPages); - default: - throw new IllegalArgumentException("Unsupported custom mode"); - } - } catch (IllegalArgumentException e) { - logger.error("Unsupported custom mode", e); - return null; - } - } + // Remove all the pages from the original document + for (int i = document.getNumberOfPages() - 1; i >= 0; i--) { + document.removePage(i); + } - @PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages") - @Operation(summary = "Rearrange pages in a PDF file", - description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode.") - public ResponseEntity rearrangePages( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to rearrange pages") - MultipartFile pdfFile, - @RequestParam(required = false, value = "pageOrder") - @Parameter(description = "The new page order as a comma-separated list of page numbers or page ranges (e.g., '1,3,5-7')") - String pageOrder, - @RequestParam(required = false, value = "customMode") - @Parameter(schema = @Schema(implementation = CustomMode.class, description = "The custom mode for page rearrangement. " + - "Valid values are:\n" + - "REVERSE_ORDER: Reverses the order of all pages.\n" + - "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). " + - "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n" + - "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n" + - "REMOVE_FIRST: Removes the first page.\n" + - "REMOVE_LAST: Removes the last page.\n" + - "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n")) - String customMode) { - try { - // Load the input PDF - PDDocument document = PDDocument.load(pdfFile.getInputStream()); + // Add the pages in the new order + for (PDPage page : newPages) { + document.addPage(page); + } - // Split the page order string into an array of page numbers or range of numbers - String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; - int totalPages = document.getNumberOfPages(); - System.out.println("pageOrder=" + pageOrder); - System.out.println("customMode length =" + customMode.length()); - List newPageOrder; - if(customMode != null && customMode.length() > 0) { - newPageOrder = processCustomMode(customMode, totalPages); - } else { - newPageOrder = pageOrderToString(pageOrderArr, totalPages); - } - - // Create a new list to hold the pages in the new order - List newPages = new ArrayList<>(); - for (int i = 0; i < newPageOrder.size(); i++) { - newPages.add(document.getPage(newPageOrder.get(i))); - } - - // Remove all the pages from the original document - for (int i = document.getNumberOfPages() - 1; i >= 0; i--) { - document.removePage(i); - } - - // Add the pages in the new order - for (PDPage page : newPages) { - document.addPage(page); - } - - return WebResponseUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf"); - } catch (IOException e) { - logger.error("Failed rearranging documents", e); - return null; - } - } + return WebResponseUtils.pdfDocToWebResponse(document, + pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf"); + } catch (IOException e) { + logger.error("Failed rearranging documents", e); + return null; + } + } + } 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 508d4fc3..fc743d0c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java @@ -3,10 +3,18 @@ package stirling.software.SPDF.controller.api; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -20,14 +28,19 @@ import com.itextpdf.kernel.pdf.PdfPage; import com.itextpdf.kernel.pdf.PdfReader; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.kernel.pdf.canvas.PdfCanvas; +import com.itextpdf.kernel.pdf.canvas.parser.EventType; +import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor; +import com.itextpdf.kernel.pdf.canvas.parser.data.IEventData; +import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo; +import com.itextpdf.kernel.pdf.canvas.parser.listener.IEventListener; import com.itextpdf.kernel.pdf.xobject.PdfFormXObject; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import stirling.software.SPDF.utils.WebResponseUtils; -import java.util.HashMap; -import java.util.Map; + @RestController public class ScalePagesController { @@ -36,48 +49,51 @@ public class ScalePagesController { @PostMapping(value = "/scale-pages", consumes = "multipart/form-data") @Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file.") public ResponseEntity scalePages( - @Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file, - @Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "String", allowableValues = { "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "LETTER", "TABLOID", "LEDGER", "LEGAL", "EXECUTIVE" })) @RequestParam("pageSize") String targetPageSize, - @Parameter(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.", required = true, schema = @Schema(type = "float")) @RequestParam("scaleFactor") float scaleFactor) - throws IOException { + @Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file, + @Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "String", allowableValues = { + "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "B0", "B1", "B2", "B3", "B4", + "B5", "B6", "B7", "B8", "B9", "LETTER", "TABLOID", "LEDGER", "LEGAL", + "EXECUTIVE" })) @RequestParam("pageSize") String targetPageSize, + @Parameter(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.", required = true, schema = @Schema(type = "float")) @RequestParam("scaleFactor") float scaleFactor) + throws IOException { Map sizeMap = new HashMap<>(); - // Add A0 - A10 - sizeMap.put("A0", PageSize.A0); - sizeMap.put("A1", PageSize.A1); - sizeMap.put("A2", PageSize.A2); - sizeMap.put("A3", PageSize.A3); - sizeMap.put("A4", PageSize.A4); - sizeMap.put("A5", PageSize.A5); - sizeMap.put("A6", PageSize.A6); - sizeMap.put("A7", PageSize.A7); - sizeMap.put("A8", PageSize.A8); - sizeMap.put("A9", PageSize.A9); - sizeMap.put("A10", PageSize.A10); - // Add B0 - B9 - sizeMap.put("B0", PageSize.B0); - sizeMap.put("B1", PageSize.B1); - sizeMap.put("B2", PageSize.B2); - sizeMap.put("B3", PageSize.B3); - sizeMap.put("B4", PageSize.B4); - sizeMap.put("B5", PageSize.B5); - sizeMap.put("B6", PageSize.B6); - sizeMap.put("B7", PageSize.B7); - sizeMap.put("B8", PageSize.B8); - sizeMap.put("B9", PageSize.B9); - // Add other sizes - sizeMap.put("LETTER", PageSize.LETTER); - sizeMap.put("TABLOID", PageSize.TABLOID); - sizeMap.put("LEDGER", PageSize.LEDGER); - sizeMap.put("LEGAL", PageSize.LEGAL); - sizeMap.put("EXECUTIVE", PageSize.EXECUTIVE); - - if (!sizeMap.containsKey(targetPageSize)) { - throw new IllegalArgumentException("Invalid pageSize. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10"); - } - + // Add A0 - A10 + sizeMap.put("A0", PageSize.A0); + sizeMap.put("A1", PageSize.A1); + sizeMap.put("A2", PageSize.A2); + sizeMap.put("A3", PageSize.A3); + sizeMap.put("A4", PageSize.A4); + sizeMap.put("A5", PageSize.A5); + sizeMap.put("A6", PageSize.A6); + sizeMap.put("A7", PageSize.A7); + sizeMap.put("A8", PageSize.A8); + sizeMap.put("A9", PageSize.A9); + sizeMap.put("A10", PageSize.A10); + // Add B0 - B9 + sizeMap.put("B0", PageSize.B0); + sizeMap.put("B1", PageSize.B1); + sizeMap.put("B2", PageSize.B2); + sizeMap.put("B3", PageSize.B3); + sizeMap.put("B4", PageSize.B4); + sizeMap.put("B5", PageSize.B5); + sizeMap.put("B6", PageSize.B6); + sizeMap.put("B7", PageSize.B7); + sizeMap.put("B8", PageSize.B8); + sizeMap.put("B9", PageSize.B9); + // Add other sizes + sizeMap.put("LETTER", PageSize.LETTER); + sizeMap.put("TABLOID", PageSize.TABLOID); + sizeMap.put("LEDGER", PageSize.LEDGER); + sizeMap.put("LEGAL", PageSize.LEGAL); + sizeMap.put("EXECUTIVE", PageSize.EXECUTIVE); + + if (!sizeMap.containsKey(targetPageSize)) { + throw new IllegalArgumentException( + "Invalid pageSize. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10"); + } + PageSize pageSize = sizeMap.get(targetPageSize); - byte[] bytes = file.getBytes(); PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes)); @@ -86,9 +102,7 @@ public class ScalePagesController { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PdfWriter writer = new PdfWriter(baos); PdfDocument outputPdf = new PdfDocument(writer); - - - + int totalPages = pdfDoc.getNumberOfPages(); for (int i = 1; i <= totalPages; i++) { @@ -100,7 +114,7 @@ public class ScalePagesController { float scaleWidth = pageSize.getWidth() / rect.getWidth(); float scaleHeight = pageSize.getHeight() / rect.getHeight(); float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor; - System.out.println("Scale: " + scale); + System.out.println("Scale: " + scale); PdfFormXObject formXObject = pdfDoc.getPage(i).copyAsFormXObject(outputPdf); float x = (pageSize.getWidth() - rect.getWidth() * scale) / 2; // Center Page @@ -117,6 +131,111 @@ public class ScalePagesController { outputPdf.close(); byte[] pdfContent = baos.toByteArray(); pdfDoc.close(); - return WebResponseUtils.bytesToWebResponse(pdfContent, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf"); + return WebResponseUtils.bytesToWebResponse(pdfContent, + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf"); } + + //TODO + @Hidden + @PostMapping(value = "/auto-crop", consumes = "multipart/form-data") + public ResponseEntity cropPdf(@RequestParam("fileInput") MultipartFile file) throws IOException { + byte[] bytes = file.getBytes(); + PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes)); + PdfDocument pdfDoc = new PdfDocument(reader); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfWriter writer = new PdfWriter(baos); + PdfDocument outputPdf = new PdfDocument(writer); + + int totalPages = pdfDoc.getNumberOfPages(); + for (int i = 1; i <= totalPages; i++) { + PdfPage page = pdfDoc.getPage(i); + Rectangle originalMediaBox = page.getMediaBox(); + + Rectangle contentBox = determineContentBox(page); + + // Make sure we don't go outside the original media box. + Rectangle intersection = originalMediaBox.getIntersection(contentBox); + page.setCropBox(intersection); + + // Copy page to the new document + outputPdf.addPage(page.copyTo(outputPdf)); + } + + outputPdf.close(); + byte[] pdfContent = baos.toByteArray(); + pdfDoc.close(); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf\"") + .contentType(MediaType.APPLICATION_PDF).body(pdfContent); + } + + private Rectangle determineContentBox(PdfPage page) { + // Extract the text from the page and find the bounding box. + TextBoundingRectangleFinder finder = new TextBoundingRectangleFinder(); + PdfCanvasProcessor processor = new PdfCanvasProcessor(finder); + processor.processPageContent(page); + return finder.getBoundingBox(); + } + + private static class TextBoundingRectangleFinder implements IEventListener { + private List allTextBoxes = new ArrayList<>(); + + public Rectangle getBoundingBox() { + // Sort the text boxes based on their vertical position + allTextBoxes.sort(Comparator.comparingDouble(Rectangle::getTop)); + + // Consider a box an outlier if its top is more than 1.5 times the IQR above the + // third quartile. + int q1Index = allTextBoxes.size() / 4; + int q3Index = 3 * allTextBoxes.size() / 4; + double iqr = allTextBoxes.get(q3Index).getTop() - allTextBoxes.get(q1Index).getTop(); + double threshold = allTextBoxes.get(q3Index).getTop() + 1.5 * iqr; + + // Initialize boundingBox to the first non-outlier box + int i = 0; + while (i < allTextBoxes.size() && allTextBoxes.get(i).getTop() > threshold) { + i++; + } + if (i == allTextBoxes.size()) { + // If all boxes are outliers, just return the first one + return allTextBoxes.get(0); + } + Rectangle boundingBox = allTextBoxes.get(i); + + // Extend the bounding box to include all non-outlier boxes + for (; i < allTextBoxes.size(); i++) { + Rectangle textBoundingBox = allTextBoxes.get(i); + if (textBoundingBox.getTop() > threshold) { + // This box is an outlier, skip it + continue; + } + float left = Math.min(boundingBox.getLeft(), textBoundingBox.getLeft()); + float bottom = Math.min(boundingBox.getBottom(), textBoundingBox.getBottom()); + float right = Math.max(boundingBox.getRight(), textBoundingBox.getRight()); + float top = Math.max(boundingBox.getTop(), textBoundingBox.getTop()); + + // Add a small padding around the bounding box + float padding = 10; + boundingBox = new Rectangle(left - padding, bottom - padding, right - left + 2 * padding, + top - bottom + 2 * padding); + } + return boundingBox; + } + + @Override + public void eventOccurred(IEventData data, EventType type) { + if (type == EventType.RENDER_TEXT) { + TextRenderInfo renderInfo = (TextRenderInfo) data; + allTextBoxes.add(renderInfo.getBaseline().getBoundingRectangle()); + } + } + + @Override + public Set getSupportedEvents() { + return Collections.singleton(EventType.RENDER_TEXT); + } + } + } diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java index 99152e69..04301b96 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java @@ -6,7 +6,6 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.zip.ZipEntry; @@ -29,6 +28,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import stirling.software.SPDF.utils.GeneralUtils; @RestController public class SplitPDFController { @@ -58,39 +58,28 @@ public class SplitPDFController { pageNumbers.add(i); } } else { - List pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(","))); - if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) { - String lastpage = String.valueOf(document.getNumberOfPages()); - pageNumbersStr.add(lastpage); - } - for (String page : pageNumbersStr) { - if (page.contains("-")) { - String[] range = page.split("-"); - int start = Integer.parseInt(range[0]); - int end = Integer.parseInt(range[1]); - for (int i = start; i <= end; i++) { - pageNumbers.add(i); - } - } else { - pageNumbers.add(Integer.parseInt(page)); - } + String[] splitPoints = pages.split(","); + for (String splitPoint : splitPoints) { + List orderedPages = GeneralUtils.parsePageList(new String[] {splitPoint}, document.getNumberOfPages()); + pageNumbers.addAll(orderedPages); } + // Add the last page as a split point + pageNumbers.add(document.getNumberOfPages() - 1); } logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(","))); // split the document List splitDocumentsBoas = new ArrayList<>(); - int currentPage = 0; - for (int pageNumber : pageNumbers) { + int previousPageNumber = 0; + for (int splitPoint : pageNumbers) { try (PDDocument splitDocument = new PDDocument()) { - for (int i = currentPage; i < pageNumber; i++) { + for (int i = previousPageNumber; i <= splitPoint; i++) { PDPage page = document.getPage(i); splitDocument.addPage(page); logger.debug("Adding page {} to split document", i); } - currentPage = pageNumber; - logger.debug("Setting current page to {}", currentPage); + previousPageNumber = splitPoint + 1; ByteArrayOutputStream baos = new ByteArrayOutputStream(); splitDocument.save(baos); @@ -102,6 +91,7 @@ public class SplitPDFController { } } + // closing the original document document.close(); diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanController.java b/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanController.java new file mode 100644 index 00000000..a4876ad0 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanController.java @@ -0,0 +1,161 @@ +package stirling.software.SPDF.controller.api.other; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.itextpdf.io.source.ByteArrayOutputStream; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.WebResponseUtils; +//Required for PDF manipulation +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; +import org.apache.pdfbox.rendering.ImageType; +import org.apache.pdfbox.rendering.PDFRenderer; + + +//Required for image manipulation +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.RescaleOp; +import java.awt.image.AffineTransformOp; +import java.awt.image.ConvolveOp; +import java.awt.image.Kernel; +import java.awt.Color; +import java.awt.geom.AffineTransform; + +//Required for image input/output +import javax.imageio.ImageIO; + +//Required for file input/output +import java.io.File; + +//Other required classes +import java.util.Random; + +import io.swagger.v3.oas.annotations.Hidden; + +@RestController +public class FakeScanController { + + private static final Logger logger = LoggerFactory.getLogger(FakeScanController.class); + + //TODO + @Hidden + @PostMapping(consumes = "multipart/form-data", value = "/fakeScan") + @Operation( + summary = "Repair a PDF file", + description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response." + ) + public ResponseEntity repairPdf( + @RequestPart(required = true, value = "fileInput") + @Parameter(description = "The input PDF file to be repaired", required = true) + MultipartFile inputFile) throws IOException, InterruptedException { + + PDDocument document = PDDocument.load(inputFile.getBytes()); + PDFRenderer pdfRenderer = new PDFRenderer(document); + for (int page = 0; page < document.getNumberOfPages(); ++page) + { + BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB); + ImageIO.write(image, "png", new File("scanned-" + (page+1) + ".png")); + } + document.close(); + + // Constants + int scannedness = 90; // Value between 0 and 100 + int dirtiness = 0; // Value between 0 and 100 + + // Load the source image + BufferedImage sourceImage = ImageIO.read(new File("scanned-1.png")); + + // Create the destination image + BufferedImage destinationImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), sourceImage.getType()); + + // Apply a brightness and contrast effect based on the "scanned-ness" + float scaleFactor = 1.0f + (scannedness / 100.0f) * 0.5f; // Between 1.0 and 1.5 + float offset = scannedness * 1.5f; // Between 0 and 150 + BufferedImageOp op = new RescaleOp(scaleFactor, offset, null); + op.filter(sourceImage, destinationImage); + + // Apply a rotation effect + double rotationRequired = Math.toRadians((new Random().nextInt(3 - 1) + 1)); // Random angle between 1 and 3 degrees + double locationX = destinationImage.getWidth() / 2; + double locationY = destinationImage.getHeight() / 2; + AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY); + AffineTransformOp rotateOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR); + destinationImage = rotateOp.filter(destinationImage, null); + + // Apply a blur effect based on the "scanned-ness" + float blurIntensity = scannedness / 100.0f * 0.2f; // Between 0.0 and 0.2 + float[] matrix = { + blurIntensity, blurIntensity, blurIntensity, + blurIntensity, blurIntensity, blurIntensity, + blurIntensity, blurIntensity, blurIntensity + }; + BufferedImageOp blurOp = new ConvolveOp(new Kernel(3, 3, matrix), ConvolveOp.EDGE_NO_OP, null); + destinationImage = blurOp.filter(destinationImage, null); + + // Add noise to the image based on the "dirtiness" + Random random = new Random(); + for (int y = 0; y < destinationImage.getHeight(); y++) { + for (int x = 0; x < destinationImage.getWidth(); x++) { + if (random.nextInt(100) < dirtiness) { + // Change the pixel color to black randomly based on the "dirtiness" + destinationImage.setRGB(x, y, Color.BLACK.getRGB()); + } + } + } + + // Save the image + ImageIO.write(destinationImage, "PNG", new File("scanned-1.png")); + + + + + + + + PDDocument documentOut = new PDDocument(); + for (int page = 1; page <= document.getNumberOfPages(); ++page) + { + BufferedImage bim = ImageIO.read(new File("scanned-" + page + ".png")); + + // Adjust the dimensions of the page + PDPage pdPage = new PDPage(new PDRectangle(bim.getWidth() - 1, bim.getHeight() - 1)); + documentOut.addPage(pdPage); + + PDImageXObject pdImage = LosslessFactory.createFromImage(documentOut, bim); + PDPageContentStream contentStream = new PDPageContentStream(documentOut, pdPage); + + // Draw the image with a slight offset and enlarged dimensions + contentStream.drawImage(pdImage, -1, -1, bim.getWidth() + 2, bim.getHeight() + 2); + contentStream.close(); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + documentOut.save(baos); + documentOut.close(); + + // Return the optimized PDF as a response + String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scanned.pdf"; + return WebResponseUtils.boasToWebResponse(baos, outputFilename); + } + +} 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 bd2914bf..9f9dddd8 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 @@ -1,9 +1,6 @@ package stirling.software.SPDF.controller.api.security; import java.io.ByteArrayInputStream; -import io.swagger.v3.oas.annotations.media.Schema; -import stirling.software.SPDF.utils.WebResponseUtils; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -53,6 +50,8 @@ import com.itextpdf.signatures.SignatureUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import stirling.software.SPDF.utils.WebResponseUtils; @RestController public class CertSignController { diff --git a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java index 95cef9e7..5a9e93c0 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java @@ -122,4 +122,11 @@ public class OtherWebController { return "other/scale-pages"; } + @GetMapping("/auto-crop") + @Hidden + public String autoCropForm(Model model) { + model.addAttribute("currentPage", "auto-crop"); + return "other/auto-crop"; + } + } diff --git a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java index 0d8325e4..1e101d09 100644 --- a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java @@ -1,5 +1,8 @@ package stirling.software.SPDF.utils; +import java.util.ArrayList; +import java.util.List; + public class GeneralUtils { public static Long convertSizeToBytes(String sizeStr) { @@ -27,4 +30,62 @@ public class GeneralUtils { return null; } + public static List parsePageList(String[] pageOrderArr, int totalPages) { + List newPageOrder = new ArrayList<>(); + + // loop through the page order array + for (String element : pageOrderArr) { + // check if the element contains a range of pages + if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) { + // Handle page order as a function + int coefficient = 0; + int constant = 0; + boolean coefficientExists = false; + boolean constantExists = false; + + if (element.contains("n")) { + String[] parts = element.split("n"); + if (!parts[0].equals("") && parts[0] != null) { + coefficient = Integer.parseInt(parts[0]); + coefficientExists = true; + } + if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { + constant = Integer.parseInt(parts[1]); + constantExists = true; + } + } else if (element.contains("+")) { + constant = Integer.parseInt(element.replace("+", "")); + constantExists = true; + } + + for (int i = 1; i <= totalPages; i++) { + int pageNum = coefficientExists ? coefficient * i : i; + pageNum += constantExists ? constant : 0; + + if (pageNum <= totalPages && pageNum > 0) { + newPageOrder.add(pageNum - 1); + } + } + } else if (element.contains("-")) { + // split the range into start and end page + String[] range = element.split("-"); + int start = Integer.parseInt(range[0]); + int end = Integer.parseInt(range[1]); + // check if the end page is greater than total pages + if (end > totalPages) { + end = totalPages; + } + // loop through the range of pages + for (int j = start; j <= end; j++) { + // print the current index + newPageOrder.add(j - 1); + } + } else { + // if the element is a single page + newPageOrder.add(Integer.parseInt(element) - 1); + } + } + + return newPageOrder; + } } diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 3956a4a1..e5d35c08 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -9,12 +9,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index 8db6cc95..1b52ba8a 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -129,15 +129,37 @@ home.repair.desc = يحاول إصلاح ملف PDF تالف / معطل home.removeBlanks.title = إزالة الصفحات الفارغة home.removeBlanks.desc = يكتشف ويزيل الصفحات الفارغة من المستند +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title = قارن home.compare.desc = يقارن ويظهر الاختلافات بين 2 من مستندات PDF +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf = تنزيل PDF text=نص font=الخط selectFillter = - حدد - pageNum = رقم الصفحة +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title = توقيع الشهادة certSign.header = قم بتوقيع ملف PDF بشهادتك (العمل قيد التقدم) certSign.selectPDF = حدد ملف PDF للتوقيع: @@ -339,6 +361,9 @@ addPassword.selectText.10=منع التعديل addPassword.selectText.11=منع تعديل التعليقات التوضيحية addPassword.selectText.12=منع الطباعة addPassword.selectText.13=منع طباعة تنسيقات مختلفة +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=تشفير #watermark diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index 119ed479..0d785b2a 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -122,15 +122,37 @@ home.repair.desc=Intenta reparar un PDF danyat o trencat home.removeBlanks.title=Elimina les pàgines en blanc home.removeBlanks.desc=Detecta i elimina les pàgines en blanc d'un document +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title=Compara home.compare.desc=Compara i mostra les diferències entre 2 documents PDF +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Descarregueu PDF text=Text font=Tipus de lletra selectFillter=-- Selecciona -- pageNum=Número de pàgina +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Significació del certificat certSign.header=Firmar un PDF amb el vostre certificat (Treball en curs) certSign.selectPDF=Seleccioneu un fitxer PDF per signar: @@ -337,6 +359,9 @@ addPassword.selectText.10=Evita modificacions addPassword.selectText.11=Evita modificacions d'annotacions addPassword.selectText.12=Evita impressió addPassword.selectText.13=Evita impressió de diferents formats +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Encripta #watermark diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index 7abe0546..47872c91 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -121,15 +121,37 @@ home.repair.desc=Versucht, ein beschädigtes/kaputtes PDF zu reparieren home.removeBlanks.title=Leere Seiten entfernen home.removeBlanks.desc=Erkennt und entfernt leere Seiten aus einem Dokument +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title=Vergleichen home.compare.desc=Vergleicht und zeigt die Unterschiede zwischen zwei PDF-Dokumenten an +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=PDF herunterladen text=Text font=Schriftart selectFillter=-- Auswählen -- pageNum=Seitenzahl +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Zertifikatsignierung certSign.header=Signieren Sie ein PDF mit Ihrem Zertifikat (in Arbeit) certSign.selectPDF=Wählen Sie eine PDF-Datei zum Signieren aus: @@ -334,6 +356,9 @@ addPassword.selectText.10=Modifizierung verhindern addPassword.selectText.11=Ändern von Kommentaren verhindern addPassword.selectText.12=Drucken verhindern addPassword.selectText.13=Drucken verschiedener Formate verhindern +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Verschlüsseln #watermark diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 5eee2506..c8f1ae13 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -10,7 +10,7 @@ multiPdfDropPrompt=Select (or drag & drop) all PDFs you require imgPrompt=Select Image(s) genericSubmit=Submit processTimeWarning=Warning: This process can take up to a minute depending on file-size -pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers) : +pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers or Functions like 2n+1) : goToPage=Go true=True false=False @@ -54,14 +54,11 @@ home.pdfOrganiser.title=Organise home.pdfOrganiser.desc=Remove/Rearrange pages in any order home.addImage.title=Add image -home.addImage.desc=Adds a image onto a set location on the PDF (Work in progress) +home.addImage.desc=Adds a image onto a set location on the PDF home.watermark.title=Add Watermark home.watermark.desc=Add a custom watermark to your PDF document. -home.remove-watermark.title=Remove Watermark -home.remove-watermark.desc=Remove watermarks from your PDF document. - home.permissions.title=Change Permissions home.permissions.desc=Change the permissions of your PDF document @@ -131,8 +128,8 @@ home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) home.pageLayout.title=Multi-Page Layout home.pageLayout.desc=Merge multiple pages of a PDF document into a single page -home.scalePages.title=Adjust page-scale -home.scalePages.desc=Change the size of page contents while maintaining a set page-size +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of a page and/or its contents. error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index 524283a5..dc639f71 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -121,15 +121,37 @@ home.repair.desc=Intenta reparar un PDF corrupto/roto home.removeBlanks.title=Eliminar páginas en blanco home.removeBlanks.descdetecta y elimina páginas en blanco de un documento +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title=Comparar home.compare.desc=Compara y muestra las diferencias entre 2 documentos PDF +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Descargar PDF text=Texto font=Fuente selectFilter=-- Seleccionar -- pageNum=Número de página +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Firma de certificado certSign.header=Firme un PDF con su certificado (Trabajo en progreso) certSign.selectPDF=Seleccione un archivo PDF para firmar: @@ -335,6 +357,9 @@ addPassword.selectText.10=Impedir modificación addPassword.selectText.11=Impedir modificación de anotaciones addPassword.selectText.12=Impedir imprimir addPassword.selectText.13=Impedir imprimir diferentes formatos +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Encriptar #watermark diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index 4038c78b..fdc3a81a 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -127,15 +127,37 @@ home.repair.desc=Essaye de réparer un PDF corrompu/cassé home.removeBlanks.title=Supprimer les pages vierges home.removeBlanks.desc=Détecte et supprime les pages vierges d'un document +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title=Comparer home.compare.desc=Compare et affiche les différences entre 2 documents PDF +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Télécharger le PDF text=Texte font=Police selectFilter=-- Sélectionner -- pageNum=numéro de page +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Signature du certificat certSign.header=Signer un PDF avec votre certificat (Travail en cours) certSign.selectPDF=Sélectionnez un fichier PDF à signer : @@ -334,6 +356,9 @@ addPassword.selectText.10=Empêcher la modification addPassword.selectText.11=Empêcher la modification des annotations addPassword.selectText.12=Empêcher l'impression addPassword.selectText.13=Empêcher l'impression de différents formats +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Crypter #watermark diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index c888ab68..7512a29b 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -122,15 +122,37 @@ home.repair.desc=Prova a riparare un PDF corrotto. home.removeBlanks.title=Rimuovi pagine vuote home.removeBlanks.desc=Trova e rimuovi pagine vuote da un PDF. +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title=Compara home.compare.desc=Vedi e compara le differenze tra due PDF. +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Scarica PDF text=Testo font=Font selectFillter=-- Seleziona -- pageNum=Numero pagina +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Firma del certificato certSign.header=Firma un PDF con il tuo certificato (Lavoro in corso) certSign.selectPDF=Seleziona un file PDF per la firma: @@ -337,6 +359,9 @@ addPassword.selectText.10=Previeni modifiche addPassword.selectText.11=Previeni annotazioni addPassword.selectText.12=Previeni stampa addPassword.selectText.13=Previeni stampa in diversi formati +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Crittografa #watermark diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index 5e34defa..b978ab2d 100644 --- a/src/main/resources/messages_ko_KR.properties +++ b/src/main/resources/messages_ko_KR.properties @@ -128,6 +128,13 @@ home.compare.desc=2개의 PDF 문서를 비교하고 차이를 표시합니다. home.certSign.title=인증서로 서명 home.certSign.desc=PDF에 인증서/키로 서명합니다. (PEM/P12) +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect downloadPdf=PDF 다운로드 text=텍스트 @@ -135,6 +142,17 @@ font=폰트 selectFillter=-- 선택 -- pageNum=페이지 번호 +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=인증서로 서명 certSign.header=PDF에 당신의 인증서로 서명하세요 (개발 중) certSign.selectPDF=서명할 PDF를 선택하세요: @@ -368,7 +386,10 @@ addPassword.selectText.9=양식 작성 방지 addPassword.selectText.10=수정 방지 addPassword.selectText.11=주석 수정 방지 addPassword.selectText.12=인쇄 방지 -addPassword.selectText.13=다른 형식으로 인쇄 방지 +addPassword.selectText.13=다른 형식으로 인쇄 방 +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=암호화 #watermark diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index 2cca082f..19244f53 100644 --- a/src/main/resources/messages_pl_PL.properties +++ b/src/main/resources/messages_pl_PL.properties @@ -359,6 +359,9 @@ addPassword.selectText.10=Zablokuj modyfikacje addPassword.selectText.11=Zablokuj modyfikacje adnotacji addPassword.selectText.12=Zablokuj drukowanie addPassword.selectText.13=Zablokuj drukowanie różnych formatów +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Zablokuj #watermark diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index 822b313e..c09b4ab3 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -128,6 +128,13 @@ home.compare.desc=Compara e mostra as diferenças entre 2 documentos PDF home.certSign.title=Assinar com certificado home.certSign.desc=Assina um PDF com um Certificado/Chave (PEM/P12) +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect downloadPdf=baixar PDF text=Texto @@ -135,6 +142,17 @@ font=Fonte selectFillter=-- Selecione -- pageNum=Número de página +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Assinatura de certificado certSign.header=Assine um PDF com seu certificado (Trabalho em andamento) certSign.selectPDF=Selecione um arquivo PDF para assinatura: @@ -345,6 +363,9 @@ addPassword.selectText.10=Impedir modificação addPassword.selectText.11=Impedir a modificação da anotação addPassword.selectText.12=Impedir a impressão addPassword.selectText.13=Impedir a impressão de formatos diferentes +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=criptografar #watermark diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties index ffa6bd89..3ea7829a 100644 --- a/src/main/resources/messages_ro_RO.properties +++ b/src/main/resources/messages_ro_RO.properties @@ -109,12 +109,31 @@ home.compare.desc=Compară și arată diferențele dintre 2 documente PDF. home.certSign.title=Semnare cu certificat home.certSign.desc=Semnează un PDF cu un certificat/cheie (PEM/P12) +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Descarcă PDF text=Text font=Font selectFillter=-- Selectează -- pageNum=Numărul paginii +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title = Semnare certificat certSign.header = Semnează un fișier PDF cu certificatul tău (În curs de desfășurare) certSign.selectPDF = Selectează un fișier PDF pentru semnare: @@ -316,6 +335,9 @@ addPassword.selectText.10=Preveniți modificarea addPassword.selectText.11=Preveniți modificarea adnotărilor addPassword.selectText.12=Preveniți tipărirea addPassword.selectText.13=Preveniți tipărirea în formate diferite +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Criptare #watermark diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index b4d87bde..c7617c45 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -125,12 +125,34 @@ home.removeBlanks.desc=Обнаруживает и удаляет пустые home.compare.title=Сравнение home.compare.desc=Сравнивает и показывает различия между двумя PDF-документами +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Скачать PDF text=Текст font=Шрифт selectFillter=-- Выбрать -- pageNum=номер страницы +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Подписание сертификата certSign.header=Подпишите PDF своим сертификатом (работа в процессе) certSign.selectPDF=Выберите файл PDF для подписи: @@ -338,6 +360,9 @@ addPassword.selectText.10=Предотвратить модификацию addPassword.selectText.11=Запретить модификацию аннотаций addPassword.selectText.12=Запретить печать addPassword.selectText.13=Запретить печать разных форматов +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Шифровать #watermark diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index 38b300ee..224fc41b 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -125,12 +125,34 @@ home.removeBlanks.desc=Känner av och tar bort tomma sidor från ett dokument home.compare.title=Jämför home.compare.desc=Jämför och visar skillnaderna mellan 2 PDF-dokument +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Ladda ner PDF text=Text font=Teckensnitt selectFillter=-- Välj -- pageNum=Sidnummer +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Certifikatsignering certSign.header=Skriv under en PDF med ditt certifikat (Pågående arbete) certSign.selectPDF=Välj en PDF-fil för signering: @@ -338,6 +360,9 @@ addPassword.selectText.10=Förhindra modifiering addPassword.selectText.11=Förhindra anteckningsändring addPassword.selectText.12=Förhindra utskrift addPassword.selectText.13=Förhindra utskrift av olika format +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Kryptera #vattenstämpel diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index b2e6ec9f..6ae7df00 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -125,12 +125,34 @@ home.removeBlanks.desc=\u68C0\u6D4B\u5E76\u5220\u9664\u6587\u6863\u4E2D\u7684\u7 home.compare.title=\u6BD4\u8F83 home.compare.desc=\u6BD4\u8F83\u5E76\u663E\u793A 2 \u4E2A PDF \u6587\u6863\u4E4B\u95F4\u7684\u5DEE\u5F02 +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=\u4E0B\u8F7DPDF text=\u6587\u672C font=\u5B57\u4F53 selectFillter=-- 选择-- pageNum=页码 +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=证书签名 certSign.header=使用您的证书签署 PDF(进行中) certSign.selectPDF=选择要签名的 PDF 文件: @@ -336,7 +358,10 @@ addPassword.selectText.9=防止填写表格 addPassword.selectText.10=防止修改 addPassword.selectText.11=防止修改注释 addPassword.selectText.12=防止打印 -addPassword.selectText.13=防止打印不同的格式 +addPassword.selectText.13=防止打印不同的格 +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=加密 #watermark diff --git a/src/main/resources/static/js/downloader.js b/src/main/resources/static/js/downloader.js index 5dc1912f..17cca631 100644 --- a/src/main/resources/static/js/downloader.js +++ b/src/main/resources/static/js/downloader.js @@ -5,73 +5,38 @@ function showErrorBanner(message, stackTrace) { document.querySelector("#errorContainer p").textContent = message; document.querySelector("#traceContent").textContent = stackTrace; } +let firstErrorOccurred = false; $(document).ready(function() { $('form').submit(async function(event) { event.preventDefault(); - + firstErrorOccurred = false; const url = this.action; const files = $('#fileInput-input')[0].files; const formData = new FormData(this); const override = $('#override').val() || ''; - + const originalButtonText = $('#submitBtn').text(); $('#submitBtn').text('Processing...'); try { if (override === 'multi' || files.length > 1 && override !== 'single') { - // Show the progress bar - $('#progressBarContainer').show(); - // Initialize the progress bar - //let progressBar = $('#progressBar'); - //progressBar.css('width', '0%'); - //progressBar.attr('aria-valuenow', 0); - //progressBar.attr('aria-valuemax', files.length); - await submitMultiPdfForm(url, files); } else { - const downloadDetails = await handleSingleDownload(url, formData); - const downloadOption = localStorage.getItem('downloadOption'); - - // Handle the download action according to the selected option - //handleDownloadAction(downloadOption, downloadDetails.blob, downloadDetails.filename); - - // Update the progress bar - //updateProgressBar(progressBar, 1); - + await handleSingleDownload(url, formData); } - $('#submitBtn').text('Submit'); + $('#submitBtn').text(originalButtonText); } catch (error) { handleDownloadError(error); - $('#submitBtn').text('Submit'); + $('#submitBtn').text(originalButtonText); console.error(error); } }); }); -function handleDownloadAction(downloadOption, blob, filename) { - const url = URL.createObjectURL(blob); - switch (downloadOption) { - case 'sameWindow': - // Open the file in the same window - window.location.href = url; - break; - case 'newWindow': - // Open the file in a new window - window.open(url, '_blank'); - break; - default: - // Download the file - const link = document.createElement('a'); - link.href = url; - link.download = filename; - link.click(); - break; - } -} -async function handleSingleDownload(url, formData) { +async function handleSingleDownload(url, formData, isMulti = false) { try { const response = await fetch(url, { method: 'POST', body: formData }); const contentType = response.headers.get('content-type'); @@ -90,7 +55,7 @@ async function handleSingleDownload(url, formData) { const blob = await response.blob(); if (contentType.includes('application/pdf') || contentType.includes('image/')) { - return handleResponse(blob, filename, true); + return handleResponse(blob, filename, !isMulti); } else { return handleResponse(blob, filename); } @@ -118,8 +83,11 @@ function getFilenameFromContentDisposition(contentDisposition) { async function handleJsonResponse(response) { const json = await response.json(); const errorMessage = JSON.stringify(json, null, 2); - if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided')) { - alert('[[#{error.pdfPassword}]]'); + if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided') || errorMessage.toLowerCase().includes('PDF contains an encryption dictionary')) { + if (!firstErrorOccurred) { + firstErrorOccurred = true; + alert(pdfPasswordPrompt); + } } else { showErrorBanner(json.error + ':' + json.message, json.trace); } @@ -173,10 +141,15 @@ async function submitMultiPdfForm(url, files) { const zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4; const zipFiles = files.length > zipThreshold; let jszip = null; - //let progressBar = $('#progressBar'); - //progressBar.css('width', '0%'); - //progressBar.attr('aria-valuenow', 0); - //progressBar.attr('aria-valuemax', Array.from(files).length); + // Show the progress bar + $('#progressBarContainer').show(); + // Initialize the progress bar + + let progressBar = $('#progressBar'); + progressBar.css('width', '0%'); + progressBar.attr('aria-valuenow', 0); + progressBar.attr('aria-valuemax', files.length); + if (zipFiles) { jszip = new JSZip(); } @@ -202,20 +175,21 @@ async function submitMultiPdfForm(url, files) { } try { - const downloadDetails = await handleSingleDownload(url, fileFormData); + const downloadDetails = await handleSingleDownload(url, fileFormData, true); console.log(downloadDetails); if (zipFiles) { jszip.file(downloadDetails.filename, downloadDetails.blob); } else { downloadFile(downloadDetails.blob, downloadDetails.filename); } - //updateProgressBar(progressBar, Array.from(files).length); + updateProgressBar(progressBar, Array.from(files).length); } catch (error) { handleDownloadError(error); console.error(error); } }); await Promise.all(promises); + } if (zipFiles) { @@ -226,6 +200,8 @@ async function submitMultiPdfForm(url, files) { console.error('Error generating ZIP file: ' + error); } } + progressBar.css('width', '100%'); + progressBar.attr('aria-valuenow', Array.from(files).length); } diff --git a/src/main/resources/static/js/fileInput.js b/src/main/resources/static/js/fileInput.js index 16111d24..87454277 100644 --- a/src/main/resources/static/js/fileInput.js +++ b/src/main/resources/static/js/fileInput.js @@ -1,6 +1,5 @@ document.addEventListener('DOMContentLoaded', function() { const fileInput = document.getElementById(elementID); - // Prevent default behavior for drag events ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { fileInput.addEventListener(eventName, preventDefaults, false); @@ -22,7 +21,7 @@ document.addEventListener('DOMContentLoaded', function() { } }); -$(elementID).on("change", function() { +$("#"+elementID).on("change", function() { handleFileInputChange(this); }); @@ -34,7 +33,6 @@ function handleFileInputChange(inputElement) { fileNames.forEach(fileName => { selectedFilesContainer.append("
" + fileName + "
"); }); - console.log("fileNames.length=" + fileNames.length) if (fileNames.length === 1) { $(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]); } else if (fileNames.length > 1) { diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html index e08f7b04..92f6f84a 100644 --- a/src/main/resources/templates/fragments/common.html +++ b/src/main/resources/templates/fragments/common.html @@ -91,6 +91,9 @@ +
diff --git a/src/main/resources/templates/other/auto-crop.html b/src/main/resources/templates/other/auto-crop.html new file mode 100644 index 00000000..a7ba407a --- /dev/null +++ b/src/main/resources/templates/other/auto-crop.html @@ -0,0 +1,31 @@ + + + + + + + + +
+
+
+

+
+
+
+

+
+
+
+ +
+

+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/pdf-organizer.html b/src/main/resources/templates/pdf-organizer.html index 580f88a0..6accc45f 100644 --- a/src/main/resources/templates/pdf-organizer.html +++ b/src/main/resources/templates/pdf-organizer.html @@ -31,7 +31,7 @@
- +