mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01:00 
			
		
		
		
	Merge branch 'main' into fix-multitool-filemixing
This commit is contained in:
		
						commit
						20c74dac60
					
				
							
								
								
									
										37
									
								
								.github/workflows/swagger.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								.github/workflows/swagger.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -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 }}
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -14,6 +14,7 @@ local.properties
 | 
			
		||||
.recommenders
 | 
			
		||||
.classpath
 | 
			
		||||
.project
 | 
			
		||||
version.properties
 | 
			
		||||
 | 
			
		||||
# Gradle
 | 
			
		||||
.gradle
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								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 {
 | 
			
		||||
 | 
			
		||||
@ -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";
 | 
			
		||||
	@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."));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
        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."));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<byte[]> 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<byte[]> 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<Integer> pagesToRemove = GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
 | 
			
		||||
 | 
			
		||||
        List<Integer> 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<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) {
 | 
			
		||||
        List<Integer> 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<Integer> removeFirst(int totalPages) {
 | 
			
		||||
		if (totalPages <= 1)
 | 
			
		||||
			return new ArrayList<>();
 | 
			
		||||
		List<Integer> newPageOrder = new ArrayList<>();
 | 
			
		||||
		for (int i = 2; i <= totalPages; i++) {
 | 
			
		||||
			newPageOrder.add(i - 1);
 | 
			
		||||
		}
 | 
			
		||||
		return newPageOrder;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
        return newPageOrder;
 | 
			
		||||
    }
 | 
			
		||||
	private List<Integer> removeLast(int totalPages) {
 | 
			
		||||
		if (totalPages <= 1)
 | 
			
		||||
			return new ArrayList<>();
 | 
			
		||||
		List<Integer> 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<Integer> removeFirstAndLast(int totalPages) {
 | 
			
		||||
		if (totalPages <= 2)
 | 
			
		||||
			return new ArrayList<>();
 | 
			
		||||
		List<Integer> newPageOrder = new ArrayList<>();
 | 
			
		||||
		for (int i = 2; i < totalPages; i++) {
 | 
			
		||||
			newPageOrder.add(i - 1);
 | 
			
		||||
		}
 | 
			
		||||
		return newPageOrder;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    private List<Integer> removeFirst(int totalPages) {
 | 
			
		||||
        if (totalPages <= 1) return new ArrayList<>();
 | 
			
		||||
        List<Integer> newPageOrder = new ArrayList<>();
 | 
			
		||||
        for (int i = 2; i <= totalPages; i++) {
 | 
			
		||||
            newPageOrder.add(i - 1);
 | 
			
		||||
        }
 | 
			
		||||
        return newPageOrder;
 | 
			
		||||
    }
 | 
			
		||||
	private List<Integer> reverseOrder(int totalPages) {
 | 
			
		||||
		List<Integer> newPageOrder = new ArrayList<>();
 | 
			
		||||
		for (int i = totalPages; i >= 1; i--) {
 | 
			
		||||
			newPageOrder.add(i - 1);
 | 
			
		||||
		}
 | 
			
		||||
		return newPageOrder;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    private List<Integer> removeLast(int totalPages) {
 | 
			
		||||
        if (totalPages <= 1) return new ArrayList<>();
 | 
			
		||||
        List<Integer> newPageOrder = new ArrayList<>();
 | 
			
		||||
        for (int i = 1; i < totalPages; i++) {
 | 
			
		||||
            newPageOrder.add(i - 1);
 | 
			
		||||
        }
 | 
			
		||||
        return newPageOrder;
 | 
			
		||||
    }
 | 
			
		||||
	private List<Integer> duplexSort(int totalPages) {
 | 
			
		||||
		List<Integer> 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<Integer> removeFirstAndLast(int totalPages) {
 | 
			
		||||
        if (totalPages <= 2) return new ArrayList<>();
 | 
			
		||||
        List<Integer> newPageOrder = new ArrayList<>();
 | 
			
		||||
        for (int i = 2; i < totalPages; i++) {
 | 
			
		||||
            newPageOrder.add(i - 1);
 | 
			
		||||
        }
 | 
			
		||||
        return newPageOrder;
 | 
			
		||||
    }
 | 
			
		||||
	private List<Integer> bookletSort(int totalPages) {
 | 
			
		||||
		List<Integer> newPageOrder = new ArrayList<>();
 | 
			
		||||
		for (int i = 0; i < totalPages / 2; i++) {
 | 
			
		||||
			newPageOrder.add(i);
 | 
			
		||||
			newPageOrder.add(totalPages - i - 1);
 | 
			
		||||
		}
 | 
			
		||||
		return newPageOrder;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private List<Integer> oddEvenSplit(int totalPages) {
 | 
			
		||||
		List<Integer> 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<Integer> reverseOrder(int totalPages) {
 | 
			
		||||
        List<Integer> newPageOrder = new ArrayList<>();
 | 
			
		||||
        for (int i = totalPages; i >= 1; i--) {
 | 
			
		||||
            newPageOrder.add(i - 1);
 | 
			
		||||
        }
 | 
			
		||||
        return newPageOrder;
 | 
			
		||||
    }
 | 
			
		||||
	private List<Integer> 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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    private List<Integer> duplexSort(int totalPages) {
 | 
			
		||||
        List<Integer> 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;
 | 
			
		||||
    }
 | 
			
		||||
	@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<byte[]> 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());
 | 
			
		||||
 | 
			
		||||
			// 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<Integer> newPageOrder;
 | 
			
		||||
			if (customMode != null && customMode.length() > 0) {
 | 
			
		||||
				newPageOrder = processCustomMode(customMode, totalPages);
 | 
			
		||||
			} else {
 | 
			
		||||
				newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
    private List<Integer> bookletSort(int totalPages) {
 | 
			
		||||
        List<Integer> newPageOrder = new ArrayList<>();
 | 
			
		||||
        for (int i = 0; i < totalPages / 2; i++) {
 | 
			
		||||
            newPageOrder.add(i);
 | 
			
		||||
            newPageOrder.add(totalPages - i - 1);
 | 
			
		||||
        }
 | 
			
		||||
        return newPageOrder;
 | 
			
		||||
    }
 | 
			
		||||
			// Create a new list to hold the pages in the new order
 | 
			
		||||
			List<PDPage> newPages = new ArrayList<>();
 | 
			
		||||
			for (int i = 0; i < newPageOrder.size(); i++) {
 | 
			
		||||
				newPages.add(document.getPage(newPageOrder.get(i)));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
    private List<Integer> oddEvenSplit(int totalPages) {
 | 
			
		||||
        List<Integer> 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;
 | 
			
		||||
    }
 | 
			
		||||
			// Remove all the pages from the original document
 | 
			
		||||
			for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
 | 
			
		||||
				document.removePage(i);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
    private List<Integer> 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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
			// Add the pages in the new order
 | 
			
		||||
			for (PDPage page : newPages) {
 | 
			
		||||
				document.addPage(page);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
    @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<byte[]> 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());
 | 
			
		||||
			return WebResponseUtils.pdfDocToWebResponse(document,
 | 
			
		||||
					pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf");
 | 
			
		||||
		} catch (IOException e) {
 | 
			
		||||
			logger.error("Failed rearranging documents", e);
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
            // 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<Integer> 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<PDPage> 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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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,49 +49,52 @@ 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<byte[]> 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<String, PageSize> 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);
 | 
			
		||||
		// 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");
 | 
			
		||||
	    }
 | 
			
		||||
		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));
 | 
			
		||||
		PdfDocument pdfDoc = new PdfDocument(reader);
 | 
			
		||||
@ -87,8 +103,6 @@ public class ScalePagesController {
 | 
			
		||||
		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<byte[]> 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<Rectangle> 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<EventType> getSupportedEvents() {
 | 
			
		||||
			return Collections.singleton(EventType.RENDER_TEXT);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<String> 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<Integer> 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<ByteArrayOutputStream> 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();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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<byte[]> 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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<Integer> parsePageList(String[] pageOrderArr, int totalPages) {
 | 
			
		||||
	    List<Integer> 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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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=다른 형ì‹<C3AC>으로 ì<>¸ì‡„ ë°©ì§
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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=é˜²æ¢æ‰“å<EFBFBD>°ä¸<EFBFBD>å<EFBFBD>Œçš„æ ¼å¼
 | 
			
		||||
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<6C>
 | 
			
		||||
addPassword.submit=加密
 | 
			
		||||
 | 
			
		||||
#watermark
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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("<div>" + fileName + "</div>");
 | 
			
		||||
	});
 | 
			
		||||
	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) {
 | 
			
		||||
 | 
			
		||||
@ -91,6 +91,9 @@
 | 
			
		||||
</th:block>
 | 
			
		||||
 | 
			
		||||
<th:block th:fragment="fileSelector(name, multiple)"  th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: 'true', notRequired=${notRequired} ?: false">
 | 
			
		||||
    <script th:inline="javascript">
 | 
			
		||||
		const pdfPasswordPrompt =/*[[#{error.pdfPassword}]]*/ '';
 | 
			
		||||
	</script>
 | 
			
		||||
    <script src="js/downloader.js"></script>
 | 
			
		||||
 | 
			
		||||
    <div class="custom-file-chooser">
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										31
									
								
								src/main/resources/templates/other/auto-crop.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/main/resources/templates/other/auto-crop.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
 | 
			
		||||
 | 
			
		||||
<th:block th:insert="~{fragments/common :: head(title=#{autoCrop.title})}"></th:block>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <th:block th:insert="~{fragments/common :: game}"></th:block>
 | 
			
		||||
    <div id="page-container">
 | 
			
		||||
        <div id="content-wrap">
 | 
			
		||||
            <div th:insert="~{fragments/navbar.html :: navbar}"></div>
 | 
			
		||||
            <br> <br>
 | 
			
		||||
            <div class="container">
 | 
			
		||||
                <div class="row justify-content-center">
 | 
			
		||||
                    <div class="col-md-6">
 | 
			
		||||
                        <h2 th:text="#{autoCrop.header}"></h2>
 | 
			
		||||
                        <form method="post" enctype="multipart/form-data" th:action="@{auto-crop}">
 | 
			
		||||
                            <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
 | 
			
		||||
                            <br>
 | 
			
		||||
                            <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{autoCrop.submit}"></button>
 | 
			
		||||
                        </form>
 | 
			
		||||
                        <p class="mt-3" th:text="#{autoCrop.credit}"></p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
        <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
@ -31,7 +31,7 @@
 | 
			
		||||
						    </div>
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label for="pageOrder" th:text="#{pageOrderPrompt}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="pageOrder" name="pageOrder" placeholder="(e.g. 1,3,2 or 4-8,2,10-12)" required>
 | 
			
		||||
                                <input type="text" class="form-control" id="pageOrder" name="pageOrder" placeholder="(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)" required>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            
 | 
			
		||||
                            <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user