mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01:00 
			
		
		
		
	Merge pull request #256 from Frooodle/pipeline
Pipeline changes, fonts, and watermark fixes
This commit is contained in:
		
						commit
						182231a183
					
				@ -5,6 +5,12 @@ FROM frooodle/stirling-pdf-base:latest
 | 
			
		||||
RUN mkdir /scripts
 | 
			
		||||
COPY ./scripts/* /scripts/
 | 
			
		||||
 | 
			
		||||
#Install fonts
 | 
			
		||||
RUN mkdir /usr/share/fonts/opentype/noto/
 | 
			
		||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
 | 
			
		||||
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
 | 
			
		||||
RUN fc-cache -f -v
 | 
			
		||||
 | 
			
		||||
# Copy the application JAR file
 | 
			
		||||
COPY build/libs/*.jar app.jar
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -119,7 +119,7 @@ services:
 | 
			
		||||
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
 | 
			
		||||
 | 
			
		||||
## Want to add your own language?
 | 
			
		||||
Stirling PDF currently supports
 | 
			
		||||
Stirling PDF currently supports 16!
 | 
			
		||||
- English (English) (en_GB)
 | 
			
		||||
- Arabic (العربية) (ar_AR)
 | 
			
		||||
- German (Deutsch) (de_DE)
 | 
			
		||||
@ -132,6 +132,10 @@ Stirling PDF currently supports
 | 
			
		||||
- Polish (Polski) (pl_PL)
 | 
			
		||||
- Romanian (Română) (ro_RO)
 | 
			
		||||
- Korean (한국어) (ko_KR)
 | 
			
		||||
- Portuguese Brazilian (Português) (pt_BR)
 | 
			
		||||
- Russian (Русский) (ru_RU)
 | 
			
		||||
- Basque (Euskara) (eu_ES)
 | 
			
		||||
- Japanese (日本語) (ja_JP)
 | 
			
		||||
 | 
			
		||||
If you want to add your own language to Stirling-PDF please refer
 | 
			
		||||
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,15 @@
 | 
			
		||||
package stirling.software.SPDF;
 | 
			
		||||
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.boot.SpringApplication;
 | 
			
		||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
import org.springframework.core.env.Environment;
 | 
			
		||||
import org.springframework.scheduling.annotation.EnableScheduling;
 | 
			
		||||
 | 
			
		||||
import jakarta.annotation.PostConstruct;
 | 
			
		||||
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.core.env.Environment;
 | 
			
		||||
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableScheduling
 | 
			
		||||
public class SPdfApplication {
 | 
			
		||||
	
 | 
			
		||||
	@Autowired
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,6 @@
 | 
			
		||||
package stirling.software.SPDF.config;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
@ -7,39 +9,71 @@ import org.springframework.web.servlet.ModelAndView;
 | 
			
		||||
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
 | 
			
		||||
import org.springframework.web.servlet.HandlerInterceptor;
 | 
			
		||||
import org.springframework.web.servlet.ModelAndView;
 | 
			
		||||
 | 
			
		||||
public class CleanUrlInterceptor implements HandlerInterceptor {
 | 
			
		||||
 | 
			
		||||
    private static final Pattern LANG_PATTERN = Pattern.compile("&?lang=([^&]+)");
 | 
			
		||||
	private static final List<String> ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints");
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 | 
			
		||||
        String queryString = request.getQueryString();
 | 
			
		||||
        if (queryString != null && !queryString.isEmpty()) {
 | 
			
		||||
            String requestURI = request.getRequestURI();
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
 | 
			
		||||
			throws Exception {
 | 
			
		||||
		String queryString = request.getQueryString();
 | 
			
		||||
		if (queryString != null && !queryString.isEmpty()) {
 | 
			
		||||
			String requestURI = request.getRequestURI();
 | 
			
		||||
 | 
			
		||||
            // Keep the lang parameter if it exists
 | 
			
		||||
            Matcher langMatcher = LANG_PATTERN.matcher(queryString);
 | 
			
		||||
            String langQueryString = langMatcher.find() ? "lang=" + langMatcher.group(1) : "";
 | 
			
		||||
			Map<String, String> parameters = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
            // Check if there are any other query parameters besides the lang parameter
 | 
			
		||||
            String remainingQueryString = queryString.replaceAll(LANG_PATTERN.pattern(), "").replaceAll("&+", "&").replaceAll("^&|&$", "");
 | 
			
		||||
			// Keep only the allowed parameters
 | 
			
		||||
			String[] queryParameters = queryString.split("&");
 | 
			
		||||
			for (String param : queryParameters) {
 | 
			
		||||
				String[] keyValue = param.split("=");
 | 
			
		||||
				if (keyValue.length != 2) {
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
				if (ALLOWED_PARAMS.contains(keyValue[0])) {
 | 
			
		||||
					parameters.put(keyValue[0], keyValue[1]);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
            if (!remainingQueryString.isEmpty()) {
 | 
			
		||||
                // Redirect to the URL without other query parameters
 | 
			
		||||
                String redirectUrl = requestURI + (langQueryString.isEmpty() ? "" : "?" + langQueryString);
 | 
			
		||||
                response.sendRedirect(redirectUrl);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
			// If there are any parameters that are not allowed
 | 
			
		||||
			if (parameters.size() != queryParameters.length) {
 | 
			
		||||
				// Construct new query string
 | 
			
		||||
				StringBuilder newQueryString = new StringBuilder();
 | 
			
		||||
				for (Map.Entry<String, String> entry : parameters.entrySet()) {
 | 
			
		||||
					if (newQueryString.length() > 0) {
 | 
			
		||||
						newQueryString.append("&");
 | 
			
		||||
					}
 | 
			
		||||
					newQueryString.append(entry.getKey()).append("=").append(entry.getValue());
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
 | 
			
		||||
    }
 | 
			
		||||
				// Redirect to the URL with only allowed query parameters
 | 
			
		||||
				String redirectUrl = requestURI + "?" + newQueryString;
 | 
			
		||||
				response.sendRedirect(redirectUrl);
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
 | 
			
		||||
    }
 | 
			
		||||
	@Override
 | 
			
		||||
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
 | 
			
		||||
			ModelAndView modelAndView) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
 | 
			
		||||
			Exception ex) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,8 @@ public class MetricsFilter extends OncePerRequestFilter {
 | 
			
		||||
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 | 
			
		||||
            throws ServletException, IOException {
 | 
			
		||||
        String uri = request.getRequestURI();
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        //System.out.println("uri="+uri + ", method=" + request.getMethod() );
 | 
			
		||||
        // Ignore static resources
 | 
			
		||||
        if (!(uri.startsWith("/js") || uri.startsWith("/images") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) {
 | 
			
		||||
            Counter counter = Counter.builder("http.requests")
 | 
			
		||||
@ -36,6 +37,7 @@ public class MetricsFilter extends OncePerRequestFilter {
 | 
			
		||||
                    .register(meterRegistry);
 | 
			
		||||
 | 
			
		||||
            counter.increment();
 | 
			
		||||
            //System.out.println("Counted");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        filterChain.doFilter(request, response);
 | 
			
		||||
 | 
			
		||||
@ -17,9 +17,11 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "General", description = "General APIs")
 | 
			
		||||
public class MergeController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
 | 
			
		||||
@ -47,7 +49,7 @@ public class MergeController {
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
 | 
			
		||||
    @Operation(
 | 
			
		||||
        summary = "Merge multiple PDF files into one",
 | 
			
		||||
        description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided."
 | 
			
		||||
        description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO"
 | 
			
		||||
    )
 | 
			
		||||
    public ResponseEntity<byte[]> mergePdfs(
 | 
			
		||||
        @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
 | 
			
		||||
@ -24,15 +24,17 @@ import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "General", description = "General APIs")
 | 
			
		||||
public class MultiPageLayoutController {
 | 
			
		||||
 | 
			
		||||
	private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
 | 
			
		||||
 | 
			
		||||
	@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
 | 
			
		||||
	@Operation(summary = "Merge multiple pages of a PDF document into a single page", description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file.")
 | 
			
		||||
	@Operation(summary = "Merge multiple pages of a PDF document into a single page", description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO")
 | 
			
		||||
	public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
 | 
			
		||||
			@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
 | 
			
		||||
			@Parameter(description = "The number of pages to fit onto a single sheet in the output PDF. Acceptable values are 2, 3, 4, 9, 16.", required = true, schema = @Schema(type = "integer", allowableValues = {
 | 
			
		||||
 | 
			
		||||
@ -18,16 +18,18 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.GeneralUtils;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "General", description = "General APIs")
 | 
			
		||||
public class RearrangePagesPDFController {
 | 
			
		||||
 | 
			
		||||
	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.")
 | 
			
		||||
	@Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete. Input:PDF Output:PDF Type:SISO")
 | 
			
		||||
	public ResponseEntity<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)
 | 
			
		||||
@ -151,7 +153,7 @@ public class RearrangePagesPDFController {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
 | 
			
		||||
	@Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode.")
 | 
			
		||||
	@Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode. Input:PDF Output:PDF")
 | 
			
		||||
	public ResponseEntity<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,
 | 
			
		||||
 | 
			
		||||
@ -16,9 +16,11 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "General", description = "General APIs")
 | 
			
		||||
public class RotationController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
 | 
			
		||||
@ -26,7 +28,7 @@ public class RotationController {
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
 | 
			
		||||
    @Operation(
 | 
			
		||||
        summary = "Rotate a PDF file",
 | 
			
		||||
        description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90."
 | 
			
		||||
        description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90. Input:PDF Output:PDF Type:SISO"
 | 
			
		||||
    )
 | 
			
		||||
    public ResponseEntity<byte[]> rotatePDF(
 | 
			
		||||
        @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
 | 
			
		||||
@ -39,15 +39,17 @@ import io.swagger.v3.oas.annotations.Hidden;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "General", description = "General APIs")
 | 
			
		||||
public class ScalePagesController {
 | 
			
		||||
 | 
			
		||||
	private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
 | 
			
		||||
 | 
			
		||||
	@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.")
 | 
			
		||||
	@Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file. Input:PDF Output:PDF Type:SISO")
 | 
			
		||||
	public ResponseEntity<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 = {
 | 
			
		||||
 | 
			
		||||
@ -15,9 +15,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.PDPage;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.core.io.ByteArrayResource;
 | 
			
		||||
import org.springframework.core.io.Resource;
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		||||
@ -28,17 +25,19 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.GeneralUtils;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "General", description = "General APIs")
 | 
			
		||||
public class SplitPDFController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/split-pages")
 | 
			
		||||
    @Operation(summary = "Split a PDF file into separate documents",
 | 
			
		||||
            description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page.")
 | 
			
		||||
            description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
 | 
			
		||||
    public ResponseEntity<byte[]> splitPdf(
 | 
			
		||||
            @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
            @Parameter(description = "The input PDF file to be split")
 | 
			
		||||
 | 
			
		||||
@ -20,16 +20,18 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.PdfUtils;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Convert", description = "Convert APIs")
 | 
			
		||||
public class ConvertImgPDFController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-img")
 | 
			
		||||
    @Operation(summary = "Convert PDF to image(s)",
 | 
			
		||||
            description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images.")
 | 
			
		||||
            description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images.  Input:PDF Output:Image Type:SI-Conditional")
 | 
			
		||||
    public ResponseEntity<Resource> convertToImage(
 | 
			
		||||
            @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
            @Parameter(description = "The input PDF file to be converted")
 | 
			
		||||
@ -83,7 +85,7 @@ public class ConvertImgPDFController {
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/img-to-pdf")
 | 
			
		||||
    @Operation(summary = "Convert images to a PDF file",
 | 
			
		||||
            description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images.")
 | 
			
		||||
            description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:SISO?")
 | 
			
		||||
    public ResponseEntity<byte[]> convertToPdf(
 | 
			
		||||
            @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
            @Parameter(description = "The input images to be converted to a PDF file")
 | 
			
		||||
 | 
			
		||||
@ -17,10 +17,12 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.ProcessExecutor;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Convert", description = "Convert APIs")
 | 
			
		||||
public class ConvertOfficeController {
 | 
			
		||||
 | 
			
		||||
    public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
 | 
			
		||||
@ -57,8 +59,8 @@ public class ConvertOfficeController {
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/file-to-pdf")
 | 
			
		||||
    @Operation(
 | 
			
		||||
        summary = "Convert a file to a PDF using OCR",
 | 
			
		||||
        description = "This endpoint converts a given file to a PDF using Optical Character Recognition (OCR). The filename of the resulting PDF will be the original filename with '_convertedToPDF.pdf' appended."
 | 
			
		||||
        summary = "Convert a file to a PDF using LibreOffice",
 | 
			
		||||
        description = "This endpoint converts a given file to a PDF using LibreOffice API  Input:Any Output:PDF Type:SISO"
 | 
			
		||||
    )
 | 
			
		||||
    public ResponseEntity<byte[]> processPdfWithOCR(
 | 
			
		||||
        @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
 | 
			
		||||
@ -12,13 +12,15 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.PDFToFile;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Convert", description = "Convert APIs")
 | 
			
		||||
public class ConvertPDFToOffice {
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-html")
 | 
			
		||||
	@Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format.")
 | 
			
		||||
	@Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
 | 
			
		||||
	public ResponseEntity<byte[]> processPdfToHTML(
 | 
			
		||||
			@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to HTML format", required = true) MultipartFile inputFile)
 | 
			
		||||
			throws IOException, InterruptedException {
 | 
			
		||||
@ -27,7 +29,7 @@ public class ConvertPDFToOffice {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-presentation")
 | 
			
		||||
	@Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format.")
 | 
			
		||||
	@Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format. Input:PDF Output:PPT Type:SISO")
 | 
			
		||||
	public ResponseEntity<byte[]> processPdfToPresentation(
 | 
			
		||||
			@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
 | 
			
		||||
			@RequestParam("outputFormat") @Parameter(description = "The output Presentation format", schema = @Schema(allowableValues = {
 | 
			
		||||
@ -38,7 +40,7 @@ public class ConvertPDFToOffice {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-text")
 | 
			
		||||
	@Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format.")
 | 
			
		||||
	@Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format. Input:PDF Output:TXT Type:SISO")
 | 
			
		||||
	public ResponseEntity<byte[]> processPdfToRTForTXT(
 | 
			
		||||
			@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
 | 
			
		||||
			@RequestParam("outputFormat") @Parameter(description = "The output Text or RTF format", schema = @Schema(allowableValues = {
 | 
			
		||||
@ -49,7 +51,7 @@ public class ConvertPDFToOffice {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-word")
 | 
			
		||||
	@Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format.")
 | 
			
		||||
	@Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format. Input:PDF Output:WORD Type:SISO")
 | 
			
		||||
	public ResponseEntity<byte[]> processPdfToWord(
 | 
			
		||||
			@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
 | 
			
		||||
			@RequestParam("outputFormat") @Parameter(description = "The output Word document format", schema = @Schema(allowableValues = {
 | 
			
		||||
@ -60,7 +62,7 @@ public class ConvertPDFToOffice {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-xml")
 | 
			
		||||
	@Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file.")
 | 
			
		||||
	@Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML Type:SISO")
 | 
			
		||||
	public ResponseEntity<byte[]> processPdfToXML(
 | 
			
		||||
			@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to an XML file", required = true) MultipartFile inputFile)
 | 
			
		||||
			throws IOException, InterruptedException {
 | 
			
		||||
 | 
			
		||||
@ -14,16 +14,18 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.ProcessExecutor;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Convert", description = "Convert APIs")
 | 
			
		||||
public class ConvertPDFToPDFA {
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa")
 | 
			
		||||
	@Operation(
 | 
			
		||||
	    summary = "Convert a PDF to a PDF/A",
 | 
			
		||||
	    description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents."
 | 
			
		||||
	    description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO"
 | 
			
		||||
	)
 | 
			
		||||
	public ResponseEntity<byte[]> pdfToPdfA(
 | 
			
		||||
	    @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,162 @@
 | 
			
		||||
package stirling.software.SPDF.controller.api.filters;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.apache.pdfbox.pdmodel.PDDocument;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.PDPage;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestPart;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.PdfUtils;
 | 
			
		||||
import stirling.software.SPDF.utils.ProcessExecutor;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Filter", description = "Filter APIs")
 | 
			
		||||
public class FilterController {
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/contains-text")
 | 
			
		||||
	@Operation(summary = "Checks if a PDF contains set text, returns true if does", description = "Input:PDF Output:Boolean Type:SISO")
 | 
			
		||||
	public Boolean containsText(
 | 
			
		||||
			@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true) MultipartFile inputFile,
 | 
			
		||||
			@Parameter(description = "The text to check for", required = true) String text,
 | 
			
		||||
			@Parameter(description = "The page number to check for text on accepts 'All', ranges like '1-4'", required = false) String pageNumber)
 | 
			
		||||
			throws IOException, InterruptedException {
 | 
			
		||||
		PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
 | 
			
		||||
		return PdfUtils.hasText(pdfDocument, pageNumber);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/contains-image")
 | 
			
		||||
	@Operation(summary = "Checks if a PDF contains an image", description = "Input:PDF Output:Boolean Type:SISO")
 | 
			
		||||
	public Boolean containsImage(
 | 
			
		||||
			@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true) MultipartFile inputFile,
 | 
			
		||||
			@Parameter(description = "The page number to check for image on accepts 'All', ranges like '1-4'", required = false) String pageNumber)
 | 
			
		||||
			throws IOException, InterruptedException {
 | 
			
		||||
		PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
 | 
			
		||||
		return PdfUtils.hasImagesOnPage(null);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/page-count")
 | 
			
		||||
	@Operation(summary = "Checks if a PDF is greater, less or equal to a setPageCount", description = "Input:PDF Output:Boolean Type:SISO")
 | 
			
		||||
	public Boolean pageCount(
 | 
			
		||||
			@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
 | 
			
		||||
			@Parameter(description = "Page Count", required = true) String pageCount,
 | 
			
		||||
			@Parameter(description = "Comparison type, accepts Greater, Equal, Less than", required = false) String comparator)
 | 
			
		||||
			throws IOException, InterruptedException {
 | 
			
		||||
		// Load the PDF
 | 
			
		||||
		PDDocument document = PDDocument.load(inputFile.getInputStream());
 | 
			
		||||
		int actualPageCount = document.getNumberOfPages();
 | 
			
		||||
 | 
			
		||||
		// Perform the comparison
 | 
			
		||||
		switch (comparator) {
 | 
			
		||||
		case "Greater":
 | 
			
		||||
			return actualPageCount > Integer.parseInt(pageCount);
 | 
			
		||||
		case "Equal":
 | 
			
		||||
			return actualPageCount == Integer.parseInt(pageCount);
 | 
			
		||||
		case "Less":
 | 
			
		||||
			return actualPageCount < Integer.parseInt(pageCount);
 | 
			
		||||
		default:
 | 
			
		||||
			throw new IllegalArgumentException("Invalid comparator: " + comparator);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/page-size")
 | 
			
		||||
	@Operation(summary = "Checks if a PDF is of a certain size", description = "Input:PDF Output:Boolean Type:SISO")
 | 
			
		||||
	public Boolean pageSize(
 | 
			
		||||
		@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
 | 
			
		||||
		@Parameter(description = "Standard Page Size", required = true) String standardPageSize,
 | 
			
		||||
		@Parameter(description = "Comparison type, accepts Greater, Equal, Less than", required = false) String comparator)
 | 
			
		||||
		throws IOException, InterruptedException {
 | 
			
		||||
		
 | 
			
		||||
		// Load the PDF
 | 
			
		||||
		PDDocument document = PDDocument.load(inputFile.getInputStream());
 | 
			
		||||
 | 
			
		||||
		PDPage firstPage = document.getPage(0);
 | 
			
		||||
		PDRectangle actualPageSize = firstPage.getMediaBox();
 | 
			
		||||
 | 
			
		||||
		// Calculate the area of the actual page size
 | 
			
		||||
		float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight();
 | 
			
		||||
 | 
			
		||||
		// Get the standard size and calculate its area
 | 
			
		||||
		PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize);
 | 
			
		||||
		float standardArea = standardSize.getWidth() * standardSize.getHeight();
 | 
			
		||||
 | 
			
		||||
		// Perform the comparison
 | 
			
		||||
		switch (comparator) {
 | 
			
		||||
		case "Greater":
 | 
			
		||||
			return actualArea > standardArea;
 | 
			
		||||
		case "Equal":
 | 
			
		||||
			return actualArea == standardArea;
 | 
			
		||||
		case "Less":
 | 
			
		||||
			return actualArea < standardArea;
 | 
			
		||||
		default:
 | 
			
		||||
			throw new IllegalArgumentException("Invalid comparator: " + comparator);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/file-size")
 | 
			
		||||
	@Operation(summary = "Checks if a PDF is a set file size", description = "Input:PDF Output:Boolean Type:SISO")
 | 
			
		||||
	public Boolean fileSize(
 | 
			
		||||
		@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
 | 
			
		||||
		@Parameter(description = "File Size", required = true) String fileSize,
 | 
			
		||||
		@Parameter(description = "Comparison type, accepts Greater, Equal, Less than", required = false) String comparator)
 | 
			
		||||
		throws IOException, InterruptedException {
 | 
			
		||||
		
 | 
			
		||||
		// Get the file size
 | 
			
		||||
		long actualFileSize = inputFile.getSize();
 | 
			
		||||
 | 
			
		||||
		// Perform the comparison
 | 
			
		||||
		switch (comparator) {
 | 
			
		||||
		case "Greater":
 | 
			
		||||
			return actualFileSize > Long.parseLong(fileSize);
 | 
			
		||||
		case "Equal":
 | 
			
		||||
			return actualFileSize == Long.parseLong(fileSize);
 | 
			
		||||
		case "Less":
 | 
			
		||||
			return actualFileSize < Long.parseLong(fileSize);
 | 
			
		||||
		default:
 | 
			
		||||
			throw new IllegalArgumentException("Invalid comparator: " + comparator);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/page-rotation")
 | 
			
		||||
	@Operation(summary = "Checks if a PDF is of a certain rotation", description = "Input:PDF Output:Boolean Type:SISO")
 | 
			
		||||
	public Boolean pageRotation(
 | 
			
		||||
		@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
 | 
			
		||||
		@Parameter(description = "Rotation in degrees", required = true) int rotation,
 | 
			
		||||
		@Parameter(description = "Comparison type, accepts Greater, Equal, Less than", required = false) String comparator)
 | 
			
		||||
		throws IOException, InterruptedException {
 | 
			
		||||
		
 | 
			
		||||
		// Load the PDF
 | 
			
		||||
		PDDocument document = PDDocument.load(inputFile.getInputStream());
 | 
			
		||||
 | 
			
		||||
		// Get the rotation of the first page
 | 
			
		||||
		PDPage firstPage = document.getPage(0);
 | 
			
		||||
		int actualRotation = firstPage.getRotation();
 | 
			
		||||
 | 
			
		||||
		// Perform the comparison
 | 
			
		||||
		switch (comparator) {
 | 
			
		||||
		case "Greater":
 | 
			
		||||
			return actualRotation > rotation;
 | 
			
		||||
		case "Equal":
 | 
			
		||||
			return actualRotation == rotation;
 | 
			
		||||
		case "Less":
 | 
			
		||||
			return actualRotation < rotation;
 | 
			
		||||
		default:
 | 
			
		||||
			throw new IllegalArgumentException("Invalid comparator: " + comparator);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -28,17 +28,19 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import stirling.software.SPDF.pdf.ImageFinder;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.PdfUtils;
 | 
			
		||||
import stirling.software.SPDF.utils.ProcessExecutor;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Other", description = "Other APIs")
 | 
			
		||||
public class BlankPageController {
 | 
			
		||||
 | 
			
		||||
	@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
 | 
			
		||||
	@Operation(
 | 
			
		||||
	    summary = "Remove blank pages from a PDF file",
 | 
			
		||||
	    description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages."
 | 
			
		||||
	    description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages. Input:PDF Output:PDF Type:SISO"
 | 
			
		||||
	)
 | 
			
		||||
	public ResponseEntity<byte[]> removeBlankPages(
 | 
			
		||||
	    @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
@ -71,7 +73,7 @@ public class BlankPageController {
 | 
			
		||||
                    pagesToKeepIndex.add(pageIndex);
 | 
			
		||||
                    System.out.println("page " + pageIndex + " has text");
 | 
			
		||||
                } else {
 | 
			
		||||
                    boolean hasImages = hasImagesOnPage(page);
 | 
			
		||||
                    boolean hasImages = PdfUtils.hasImagesOnPage(page);
 | 
			
		||||
                    if (hasImages) {
 | 
			
		||||
                        System.out.println("page " + pageIndex + " has image");
 | 
			
		||||
    
 | 
			
		||||
@ -120,9 +122,5 @@ public class BlankPageController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private static boolean hasImagesOnPage(PDPage page) throws IOException {
 | 
			
		||||
        ImageFinder imageFinder = new ImageFinder(page);
 | 
			
		||||
        imageFinder.processPage(page);
 | 
			
		||||
        return imageFinder.hasImages();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -31,17 +31,19 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.GeneralUtils;
 | 
			
		||||
import stirling.software.SPDF.utils.ProcessExecutor;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Other", description = "Other APIs")
 | 
			
		||||
public class CompressController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
 | 
			
		||||
    @Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters.")
 | 
			
		||||
    @Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters. Input:PDF Output:PDF Type:SISO")
 | 
			
		||||
    public ResponseEntity<byte[]> optimizePdf(
 | 
			
		||||
            @RequestPart(value = "fileInput") @Parameter(description = "The input PDF file to be optimized.", required = true) MultipartFile inputFile,
 | 
			
		||||
            @RequestParam(required = false, value = "optimizeLevel") @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", schema = @Schema(allowableValues = {
 | 
			
		||||
 | 
			
		||||
@ -31,17 +31,19 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.ProcessExecutor;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Other", description = "Other APIs")
 | 
			
		||||
public class ExtractImageScansController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class);
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
 | 
			
		||||
    @Operation(summary = "Extract image scans from an input file",
 | 
			
		||||
            description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size.")
 | 
			
		||||
            description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP Type:SIMO")
 | 
			
		||||
    public ResponseEntity<byte[]> extractImageScans(
 | 
			
		||||
            @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
            @Parameter(description = "The input file containing image scans")
 | 
			
		||||
 | 
			
		||||
@ -29,15 +29,17 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Other", description = "Other APIs")
 | 
			
		||||
public class ExtractImagesController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/extract-images")
 | 
			
		||||
    @Operation(summary = "Extract images from a PDF file",
 | 
			
		||||
            description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format.")
 | 
			
		||||
            description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO")
 | 
			
		||||
    public ResponseEntity<byte[]> extractImages(
 | 
			
		||||
            @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
            @Parameter(description = "The input PDF file containing images")
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,31 @@
 | 
			
		||||
package stirling.software.SPDF.controller.api.other;
 | 
			
		||||
 | 
			
		||||
import java.awt.Color;
 | 
			
		||||
import java.awt.geom.AffineTransform;
 | 
			
		||||
import java.awt.image.AffineTransformOp;
 | 
			
		||||
//Required for image manipulation
 | 
			
		||||
import java.awt.image.BufferedImage;
 | 
			
		||||
import java.awt.image.BufferedImageOp;
 | 
			
		||||
import java.awt.image.ConvolveOp;
 | 
			
		||||
import java.awt.image.Kernel;
 | 
			
		||||
import java.awt.image.RescaleOp;
 | 
			
		||||
//Required for file input/output
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
//Other required classes
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
 | 
			
		||||
//Required for image input/output
 | 
			
		||||
import javax.imageio.ImageIO;
 | 
			
		||||
 | 
			
		||||
import org.apache.pdfbox.pdmodel.PDDocument;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.PDPage;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
 | 
			
		||||
import org.apache.pdfbox.rendering.ImageType;
 | 
			
		||||
import org.apache.pdfbox.rendering.PDFRenderer;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
@ -17,46 +36,17 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import com.itextpdf.io.source.ByteArrayOutputStream;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Hidden;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import stirling.software.SPDF.utils.ProcessExecutor;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
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 {
 | 
			
		||||
@Tag(name = "Other", description = "Other APIs")
 | 
			
		||||
public class FakeScanControllerWIP {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(FakeScanController.class);
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class);
 | 
			
		||||
 | 
			
		||||
    //TODO
 | 
			
		||||
    @Hidden
 | 
			
		||||
@ -19,9 +19,11 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Other", description = "Other APIs")
 | 
			
		||||
public class MetadataController {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -38,7 +40,7 @@ public class MetadataController {
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
 | 
			
		||||
    @Operation(summary = "Update metadata of a PDF file",
 | 
			
		||||
            description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields.")
 | 
			
		||||
            description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields. Input:PDF Output:PDF Type:SISO")
 | 
			
		||||
    public ResponseEntity<byte[]> metadata(
 | 
			
		||||
            @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
            @Parameter(description = "The input PDF file to update metadata")
 | 
			
		||||
@ -73,6 +75,7 @@ public class MetadataController {
 | 
			
		||||
            @RequestParam(value = "trapped", required = false)
 | 
			
		||||
            @Parameter(description = "The trapped status of the document")
 | 
			
		||||
                    String trapped,
 | 
			
		||||
                    @Parameter(description = "Map list of key and value of custom parameters, note these must start with customKey and customValue if they are non standard")
 | 
			
		||||
            @RequestParam Map<String, String> allRequestParams)
 | 
			
		||||
            throws IOException {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -27,10 +27,12 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.ProcessExecutor;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Other", description = "Other APIs")
 | 
			
		||||
public class OCRController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
 | 
			
		||||
@ -47,7 +49,7 @@ public class OCRController {
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
 | 
			
		||||
    @Operation(summary = "Process a PDF file with OCR",
 | 
			
		||||
            description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options.")
 | 
			
		||||
            description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options. Input:PDF Output:PDF Type:SI-Conditional")
 | 
			
		||||
    public ResponseEntity<byte[]> processPdfWithOCR(
 | 
			
		||||
            @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
            @Parameter(description = "The input PDF file to be processed with OCR")
 | 
			
		||||
 | 
			
		||||
@ -14,10 +14,12 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.PdfUtils;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Other", description = "Other APIs")
 | 
			
		||||
public class OverlayImageController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
 | 
			
		||||
@ -25,7 +27,7 @@ public class OverlayImageController {
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/add-image")
 | 
			
		||||
    @Operation(
 | 
			
		||||
        summary = "Overlay image onto a PDF file",
 | 
			
		||||
        description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified."
 | 
			
		||||
        description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified.  Input:PDF/IMAGE Output:PDF Type:MF-SISO"
 | 
			
		||||
    )
 | 
			
		||||
    public ResponseEntity<byte[]> overlayImage(
 | 
			
		||||
        @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
 | 
			
		||||
@ -16,10 +16,12 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.ProcessExecutor;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Other", description = "Other APIs")
 | 
			
		||||
public class RepairController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
 | 
			
		||||
@ -27,7 +29,7 @@ public class RepairController {
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/repair")
 | 
			
		||||
    @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."
 | 
			
		||||
        description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO"
 | 
			
		||||
    )
 | 
			
		||||
    public ResponseEntity<byte[]> repairPdf(
 | 
			
		||||
        @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,399 @@
 | 
			
		||||
package stirling.software.SPDF.controller.api.pipeline;
 | 
			
		||||
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.ByteArrayOutputStream;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.io.PrintStream;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
import java.time.LocalTime;
 | 
			
		||||
import java.time.format.DateTimeFormatter;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
import java.util.zip.ZipEntry;
 | 
			
		||||
import java.util.zip.ZipInputStream;
 | 
			
		||||
import java.util.zip.ZipOutputStream;
 | 
			
		||||
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.core.io.ByteArrayResource;
 | 
			
		||||
import org.springframework.core.io.Resource;
 | 
			
		||||
import org.springframework.http.HttpEntity;
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.HttpMethod;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.scheduling.annotation.Scheduled;
 | 
			
		||||
import org.springframework.util.LinkedMultiValueMap;
 | 
			
		||||
import org.springframework.util.MultiValueMap;
 | 
			
		||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestParam;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestPart;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
import org.springframework.web.client.RestTemplate;
 | 
			
		||||
import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.model.PipelineConfig;
 | 
			
		||||
import stirling.software.SPDF.model.PipelineOperation;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Pipeline", description = "Pipeline APIs")
 | 
			
		||||
public class Controller {
 | 
			
		||||
 | 
			
		||||
	@Autowired
 | 
			
		||||
	private ObjectMapper objectMapper;
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	final String jsonFileName = "pipelineCofig.json";
 | 
			
		||||
	final String watchedFoldersDir = "watchedFolders/";
 | 
			
		||||
	@Scheduled(fixedRate = 5000)
 | 
			
		||||
	public void scanFolders() {
 | 
			
		||||
		Path watchedFolderPath = Paths.get(watchedFoldersDir);
 | 
			
		||||
	    if (!Files.exists(watchedFolderPath)) {
 | 
			
		||||
	        try {
 | 
			
		||||
	            Files.createDirectories(watchedFolderPath);
 | 
			
		||||
	        } catch (IOException e) {
 | 
			
		||||
	            e.printStackTrace();
 | 
			
		||||
	            return;
 | 
			
		||||
	        }
 | 
			
		||||
	    }
 | 
			
		||||
	    
 | 
			
		||||
		try (Stream<Path> paths = Files.walk(watchedFolderPath)) {
 | 
			
		||||
	        paths.filter(Files::isDirectory).forEach(t -> {
 | 
			
		||||
				try {
 | 
			
		||||
					if (!t.equals(watchedFolderPath) && !t.endsWith("processing")) {
 | 
			
		||||
						handleDirectory(t);
 | 
			
		||||
					}
 | 
			
		||||
				} catch (Exception e) {
 | 
			
		||||
					e.printStackTrace();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
	    } catch (Exception e) {
 | 
			
		||||
	        e.printStackTrace();
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	private void handleDirectory(Path dir) throws Exception {
 | 
			
		||||
	    Path jsonFile = dir.resolve(jsonFileName);
 | 
			
		||||
	    Path processingDir = dir.resolve("processing"); // Directory to move files during processing
 | 
			
		||||
	    if (!Files.exists(processingDir)) {
 | 
			
		||||
	        Files.createDirectory(processingDir);
 | 
			
		||||
	    }
 | 
			
		||||
 | 
			
		||||
	    if (Files.exists(jsonFile)) {
 | 
			
		||||
	        // Read JSON file
 | 
			
		||||
	        String jsonString;
 | 
			
		||||
	        try {
 | 
			
		||||
	            jsonString = new String(Files.readAllBytes(jsonFile));
 | 
			
		||||
	        } catch (IOException e) {
 | 
			
		||||
	            e.printStackTrace();
 | 
			
		||||
	            return;
 | 
			
		||||
	        }
 | 
			
		||||
 | 
			
		||||
	        // Decode JSON to PipelineConfig
 | 
			
		||||
	        PipelineConfig config;
 | 
			
		||||
	        try {
 | 
			
		||||
	            config = objectMapper.readValue(jsonString, PipelineConfig.class);
 | 
			
		||||
	            // Assuming your PipelineConfig class has getters for all necessary fields, you can perform checks here
 | 
			
		||||
	            if (config.getOperations() == null || config.getOutputDir() == null || config.getName() == null) {
 | 
			
		||||
	                throw new IOException("Invalid JSON format");
 | 
			
		||||
	            }
 | 
			
		||||
	        } catch (IOException e) {
 | 
			
		||||
	            e.printStackTrace();
 | 
			
		||||
	            return;
 | 
			
		||||
	        }
 | 
			
		||||
 | 
			
		||||
	        // For each operation in the pipeline
 | 
			
		||||
	        for (PipelineOperation operation : config.getOperations()) {
 | 
			
		||||
	            // Collect all files based on fileInput
 | 
			
		||||
	            File[] files;
 | 
			
		||||
	            String fileInput = (String) operation.getParameters().get("fileInput");
 | 
			
		||||
	            if ("automated".equals(fileInput)) {
 | 
			
		||||
	                // If fileInput is "automated", process all files in the directory
 | 
			
		||||
	                try (Stream<Path> paths = Files.list(dir)) {
 | 
			
		||||
	                    files = paths.filter(path -> !path.equals(jsonFile))
 | 
			
		||||
	                            .map(Path::toFile)
 | 
			
		||||
	                            .toArray(File[]::new);
 | 
			
		||||
	                } catch (IOException e) {
 | 
			
		||||
	                    e.printStackTrace();
 | 
			
		||||
	                    return;
 | 
			
		||||
	                }
 | 
			
		||||
	            } else {
 | 
			
		||||
	                // If fileInput contains a path, process only this file
 | 
			
		||||
	                files = new File[]{new File(fileInput)};
 | 
			
		||||
	            }
 | 
			
		||||
	            
 | 
			
		||||
	            // Prepare the files for processing
 | 
			
		||||
	            File[] filesToProcess = files.clone();
 | 
			
		||||
	            for (File file : filesToProcess) {
 | 
			
		||||
	                Files.move(file.toPath(), processingDir.resolve(file.getName()));
 | 
			
		||||
	            }
 | 
			
		||||
	            
 | 
			
		||||
	            // Process the files
 | 
			
		||||
	            try {
 | 
			
		||||
	                List<Resource> resources = handleFiles(filesToProcess, jsonString);
 | 
			
		||||
 | 
			
		||||
	                // Move resultant files and rename them as per config in JSON file
 | 
			
		||||
	                for (Resource resource : resources) {
 | 
			
		||||
	                    String outputFileName = config.getOutputPattern().replace("{filename}", resource.getFile().getName());
 | 
			
		||||
	                    outputFileName = outputFileName.replace("{pipelineName}", config.getName());
 | 
			
		||||
	                    DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
 | 
			
		||||
	                    outputFileName = outputFileName.replace("{date}", LocalDate.now().format(dateFormatter));
 | 
			
		||||
	                    DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HHmmss");
 | 
			
		||||
	                    outputFileName = outputFileName.replace("{time}", LocalTime.now().format(timeFormatter));
 | 
			
		||||
	                    // {filename} {folder} {date} {tmime} {pipeline}
 | 
			
		||||
 | 
			
		||||
	                    Files.move(resource.getFile().toPath(), Paths.get(config.getOutputDir(), outputFileName));
 | 
			
		||||
	                }
 | 
			
		||||
	                
 | 
			
		||||
	                // If successful, delete the original files
 | 
			
		||||
	                for (File file : filesToProcess) {
 | 
			
		||||
	                    Files.deleteIfExists(processingDir.resolve(file.getName()));
 | 
			
		||||
	                }
 | 
			
		||||
	            } catch (Exception e) {
 | 
			
		||||
	                // If an error occurs, move the original files back
 | 
			
		||||
	                for (File file : filesToProcess) {
 | 
			
		||||
	                    Files.move(processingDir.resolve(file.getName()), file.toPath());
 | 
			
		||||
	                }
 | 
			
		||||
	                throw e;
 | 
			
		||||
	            }
 | 
			
		||||
	        }
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
List<Resource> processFiles(List<Resource> outputFiles, String jsonString) throws Exception{
 | 
			
		||||
	ObjectMapper mapper = new ObjectMapper();
 | 
			
		||||
	JsonNode jsonNode = mapper.readTree(jsonString);
 | 
			
		||||
 | 
			
		||||
	JsonNode pipelineNode = jsonNode.get("pipeline");
 | 
			
		||||
	ByteArrayOutputStream logStream = new ByteArrayOutputStream();
 | 
			
		||||
	PrintStream logPrintStream = new PrintStream(logStream);
 | 
			
		||||
	
 | 
			
		||||
	boolean hasErrors = false;
 | 
			
		||||
 | 
			
		||||
	for (JsonNode operationNode : pipelineNode) {
 | 
			
		||||
		String operation = operationNode.get("operation").asText();
 | 
			
		||||
		JsonNode parametersNode = operationNode.get("parameters");
 | 
			
		||||
		String inputFileExtension = "";
 | 
			
		||||
		if(operationNode.has("inputFileType")) {
 | 
			
		||||
		 inputFileExtension = operationNode.get("inputFileType").asText();
 | 
			
		||||
		} else {
 | 
			
		||||
			inputFileExtension=".pdf";
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		List<Resource> newOutputFiles = new ArrayList<>();
 | 
			
		||||
		boolean hasInputFileType = false;
 | 
			
		||||
 | 
			
		||||
		for (Resource file : outputFiles) {
 | 
			
		||||
			if (file.getFilename().endsWith(inputFileExtension)) {
 | 
			
		||||
				hasInputFileType = true;
 | 
			
		||||
				MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
 | 
			
		||||
				body.add("fileInput", file);
 | 
			
		||||
 | 
			
		||||
				Iterator<Map.Entry<String, JsonNode>> parameters = parametersNode.fields();
 | 
			
		||||
				while (parameters.hasNext()) {
 | 
			
		||||
					Map.Entry<String, JsonNode> parameter = parameters.next();
 | 
			
		||||
					body.add(parameter.getKey(), parameter.getValue().asText());
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				HttpHeaders headers = new HttpHeaders();
 | 
			
		||||
				headers.setContentType(MediaType.MULTIPART_FORM_DATA);
 | 
			
		||||
 | 
			
		||||
				HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
 | 
			
		||||
 | 
			
		||||
				RestTemplate restTemplate = new RestTemplate();
 | 
			
		||||
				String url = "http://localhost:8080/" + operation;
 | 
			
		||||
 | 
			
		||||
				ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
 | 
			
		||||
 | 
			
		||||
				if (!response.getStatusCode().equals(HttpStatus.OK)) {
 | 
			
		||||
					logPrintStream.println("Error: " + response.getBody());
 | 
			
		||||
					hasErrors = true;
 | 
			
		||||
                    continue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Check if the response body is a zip file
 | 
			
		||||
				if (isZip(response.getBody())) {
 | 
			
		||||
					// Unzip the file and add all the files to the new output files
 | 
			
		||||
					newOutputFiles.addAll(unzip(response.getBody()));
 | 
			
		||||
				} else {
 | 
			
		||||
					Resource outputResource = new ByteArrayResource(response.getBody()) {
 | 
			
		||||
						@Override
 | 
			
		||||
						public String getFilename() {
 | 
			
		||||
							return file.getFilename(); // Preserving original filename
 | 
			
		||||
						}
 | 
			
		||||
					};
 | 
			
		||||
					newOutputFiles.add(outputResource);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			 if (!hasInputFileType) {
 | 
			
		||||
                logPrintStream.println("No files with extension " + inputFileExtension + " found for operation " + operation);
 | 
			
		||||
                hasErrors = true;
 | 
			
		||||
            }
 | 
			
		||||
			 
 | 
			
		||||
			outputFiles = newOutputFiles;
 | 
			
		||||
		}
 | 
			
		||||
		logPrintStream.close();
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
	return outputFiles;
 | 
			
		||||
}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
List<Resource> handleFiles(File[] files, String jsonString) throws Exception{
 | 
			
		||||
	ObjectMapper mapper = new ObjectMapper();
 | 
			
		||||
	JsonNode jsonNode = mapper.readTree(jsonString);
 | 
			
		||||
 | 
			
		||||
	JsonNode pipelineNode = jsonNode.get("pipeline");
 | 
			
		||||
	ByteArrayOutputStream logStream = new ByteArrayOutputStream();
 | 
			
		||||
	PrintStream logPrintStream = new PrintStream(logStream);
 | 
			
		||||
	
 | 
			
		||||
	boolean hasErrors = false;
 | 
			
		||||
	List<Resource> outputFiles = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
	for (File file : files) {
 | 
			
		||||
		Path path = Paths.get(file.getAbsolutePath());
 | 
			
		||||
        Resource fileResource = new ByteArrayResource(Files.readAllBytes(path)) {
 | 
			
		||||
            @Override
 | 
			
		||||
            public String getFilename() {
 | 
			
		||||
                return file.getName();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
		outputFiles.add(fileResource);
 | 
			
		||||
	}
 | 
			
		||||
	return processFiles(outputFiles, jsonString);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	List<Resource> handleFiles(MultipartFile[] files, String jsonString) throws Exception{
 | 
			
		||||
		ObjectMapper mapper = new ObjectMapper();
 | 
			
		||||
		JsonNode jsonNode = mapper.readTree(jsonString);
 | 
			
		||||
 | 
			
		||||
		JsonNode pipelineNode = jsonNode.get("pipeline");
 | 
			
		||||
		ByteArrayOutputStream logStream = new ByteArrayOutputStream();
 | 
			
		||||
		PrintStream logPrintStream = new PrintStream(logStream);
 | 
			
		||||
		
 | 
			
		||||
		boolean hasErrors = false;
 | 
			
		||||
		List<Resource> outputFiles = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
		for (MultipartFile file : files) {
 | 
			
		||||
			Resource fileResource = new ByteArrayResource(file.getBytes()) {
 | 
			
		||||
				@Override
 | 
			
		||||
				public String getFilename() {
 | 
			
		||||
					return file.getOriginalFilename();
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
			outputFiles.add(fileResource);
 | 
			
		||||
		}
 | 
			
		||||
		return processFiles(outputFiles, jsonString);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@PostMapping("/handleData")
 | 
			
		||||
	public ResponseEntity<byte[]> handleData(@RequestPart("fileInput") MultipartFile[] files,
 | 
			
		||||
			@RequestParam("json") String jsonString) {
 | 
			
		||||
		try {
 | 
			
		||||
			
 | 
			
		||||
		List<Resource> outputFiles = handleFiles(files, jsonString);
 | 
			
		||||
 | 
			
		||||
		if (outputFiles.size() == 1) {
 | 
			
		||||
		    // If there is only one file, return it directly
 | 
			
		||||
		    Resource singleFile = outputFiles.get(0);
 | 
			
		||||
		    InputStream is = singleFile.getInputStream();
 | 
			
		||||
		    byte[] bytes = new byte[(int)singleFile.contentLength()];
 | 
			
		||||
		    is.read(bytes);
 | 
			
		||||
		    is.close();
 | 
			
		||||
		
 | 
			
		||||
		    return WebResponseUtils.bytesToWebResponse(bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM);
 | 
			
		||||
		} 
 | 
			
		||||
 | 
			
		||||
			// Create a ByteArrayOutputStream to hold the zip
 | 
			
		||||
		    ByteArrayOutputStream baos = new ByteArrayOutputStream();
 | 
			
		||||
		    ZipOutputStream zipOut = new ZipOutputStream(baos);
 | 
			
		||||
 | 
			
		||||
		    // Loop through each file and add it to the zip
 | 
			
		||||
		    for (Resource file : outputFiles) {
 | 
			
		||||
		        ZipEntry zipEntry = new ZipEntry(file.getFilename());
 | 
			
		||||
		        zipOut.putNextEntry(zipEntry);
 | 
			
		||||
 | 
			
		||||
		        // Read the file into a byte array
 | 
			
		||||
		        InputStream is = file.getInputStream();
 | 
			
		||||
		        byte[] bytes = new byte[(int)file.contentLength()];
 | 
			
		||||
		        is.read(bytes);
 | 
			
		||||
 | 
			
		||||
		        // Write the bytes of the file to the zip
 | 
			
		||||
		        zipOut.write(bytes, 0, bytes.length);
 | 
			
		||||
		        zipOut.closeEntry();
 | 
			
		||||
 | 
			
		||||
		        is.close();
 | 
			
		||||
		    }
 | 
			
		||||
 | 
			
		||||
		    zipOut.close();
 | 
			
		||||
		    
 | 
			
		||||
			return WebResponseUtils.boasToWebResponse(baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM);
 | 
			
		||||
		} catch (Exception e) {
 | 
			
		||||
			e.printStackTrace();
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private boolean isZip(byte[] data) {
 | 
			
		||||
		if (data == null || data.length < 4) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check the first four bytes of the data against the standard zip magic number
 | 
			
		||||
		return data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private List<Resource> unzip(byte[] data) throws IOException {
 | 
			
		||||
		List<Resource> unzippedFiles = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
		try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
 | 
			
		||||
				ZipInputStream zis = new ZipInputStream(bais)) {
 | 
			
		||||
 | 
			
		||||
			ZipEntry entry;
 | 
			
		||||
			while ((entry = zis.getNextEntry()) != null) {
 | 
			
		||||
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
 | 
			
		||||
				byte[] buffer = new byte[1024];
 | 
			
		||||
				int count;
 | 
			
		||||
 | 
			
		||||
				while ((count = zis.read(buffer)) != -1) {
 | 
			
		||||
					baos.write(buffer, 0, count);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				final String filename = entry.getName();
 | 
			
		||||
				Resource fileResource = new ByteArrayResource(baos.toByteArray()) {
 | 
			
		||||
					@Override
 | 
			
		||||
					public String getFilename() {
 | 
			
		||||
						return filename;
 | 
			
		||||
					}
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				// If the unzipped file is a zip file, unzip it
 | 
			
		||||
				if (isZip(baos.toByteArray())) {
 | 
			
		||||
					unzippedFiles.addAll(unzip(baos.toByteArray()));
 | 
			
		||||
				} else {
 | 
			
		||||
					unzippedFiles.add(fileResource);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return unzippedFiles;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -51,8 +51,10 @@ import com.itextpdf.signatures.SignatureUtil;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Security", description = "Security APIs")
 | 
			
		||||
public class CertSignController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(CertSignController.class);
 | 
			
		||||
@ -63,7 +65,7 @@ public class CertSignController {
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
 | 
			
		||||
    @Operation(summary = "Sign PDF with a Digital Certificate",
 | 
			
		||||
        description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file.")
 | 
			
		||||
        description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO")
 | 
			
		||||
    public ResponseEntity<byte[]> signPDF(
 | 
			
		||||
        @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
        @Parameter(description = "The input PDF file to be signed")
 | 
			
		||||
 | 
			
		||||
@ -17,8 +17,10 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Security", description = "Security APIs")
 | 
			
		||||
public class PasswordController {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
 | 
			
		||||
@ -27,7 +29,7 @@ public class PasswordController {
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/remove-password")
 | 
			
		||||
    @Operation(
 | 
			
		||||
        summary = "Remove password from a PDF file",
 | 
			
		||||
        description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password."
 | 
			
		||||
        description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO"
 | 
			
		||||
    )
 | 
			
		||||
    public ResponseEntity<byte[]> removePassword(
 | 
			
		||||
        @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
@ -44,7 +46,7 @@ public class PasswordController {
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/add-password")
 | 
			
		||||
    @Operation(
 | 
			
		||||
        summary = "Add password to a PDF file",
 | 
			
		||||
        description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file."
 | 
			
		||||
        description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF"
 | 
			
		||||
    )
 | 
			
		||||
    public ResponseEntity<byte[]> addPassword(
 | 
			
		||||
        @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,23 @@
 | 
			
		||||
package stirling.software.SPDF.controller.api.security;
 | 
			
		||||
 | 
			
		||||
import java.awt.Color;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileOutputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.apache.commons.io.IOUtils;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.PDDocument;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.PDPage;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.font.PDFont;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.font.PDType0Font;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
 | 
			
		||||
import org.apache.pdfbox.util.Matrix;
 | 
			
		||||
import org.springframework.core.io.ClassPathResource;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestParam;
 | 
			
		||||
@ -19,18 +27,26 @@ import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import stirling.software.SPDF.utils.WebResponseUtils;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
@RestController
 | 
			
		||||
@Tag(name = "Security", description = "Security APIs")
 | 
			
		||||
public class WatermarkController {
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
 | 
			
		||||
    @Operation(summary = "Add watermark to a PDF file",
 | 
			
		||||
            description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark text, font size, rotation, opacity, width spacer, and height spacer.")
 | 
			
		||||
            description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark text, font size, rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
 | 
			
		||||
    public ResponseEntity<byte[]> addWatermark(
 | 
			
		||||
            @RequestPart(required = true, value = "fileInput")
 | 
			
		||||
            @Parameter(description = "The input PDF file to add a watermark")
 | 
			
		||||
                    MultipartFile pdfFile,
 | 
			
		||||
            @RequestParam(defaultValue = "roman", name = "alphabet")
 | 
			
		||||
            @Parameter(description = "The selected alphabet", 
 | 
			
		||||
                       schema = @Schema(type = "string", 
 | 
			
		||||
                                        allowableValues = {"roman","arabic","japanese","korean","chinese"}, 
 | 
			
		||||
                                        defaultValue = "roman"))
 | 
			
		||||
                    String alphabet,
 | 
			
		||||
            @RequestParam("watermarkText")
 | 
			
		||||
            @Parameter(description = "The watermark text to add to the PDF file")
 | 
			
		||||
                    String watermarkText,
 | 
			
		||||
@ -48,11 +64,11 @@ public class WatermarkController {
 | 
			
		||||
                    int widthSpacer,
 | 
			
		||||
            @RequestParam(defaultValue = "50", name = "heightSpacer")
 | 
			
		||||
            @Parameter(description = "The height spacer between watermark texts", example = "50")
 | 
			
		||||
                    int heightSpacer) throws IOException {
 | 
			
		||||
                    int heightSpacer) throws IOException, Exception {
 | 
			
		||||
 | 
			
		||||
        // Load the input PDF
 | 
			
		||||
        PDDocument document = PDDocument.load(pdfFile.getInputStream());
 | 
			
		||||
 | 
			
		||||
        String producer = document.getDocumentInformation().getProducer();
 | 
			
		||||
        // Create a page in the document
 | 
			
		||||
        for (PDPage page : document.getPages()) {
 | 
			
		||||
 | 
			
		||||
@ -64,8 +80,40 @@ public class WatermarkController {
 | 
			
		||||
            graphicsState.setNonStrokingAlphaConstant(opacity);
 | 
			
		||||
            contentStream.setGraphicsStateParameters(graphicsState);
 | 
			
		||||
 | 
			
		||||
            // Set font of watermark
 | 
			
		||||
            PDFont font = PDType1Font.HELVETICA_BOLD;
 | 
			
		||||
 | 
			
		||||
			String resourceDir = "";
 | 
			
		||||
		    PDFont font = PDType1Font.HELVETICA_BOLD;
 | 
			
		||||
		    switch (alphabet) {
 | 
			
		||||
		        case "arabic":
 | 
			
		||||
		            resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
 | 
			
		||||
		            break;
 | 
			
		||||
		        case "japanese":
 | 
			
		||||
		            resourceDir = "static/fonts/Meiryo.ttf";
 | 
			
		||||
		            break;
 | 
			
		||||
		        case "korean":
 | 
			
		||||
		            resourceDir = "static/fonts/malgun.ttf";
 | 
			
		||||
		            break;
 | 
			
		||||
		        case "chinese":
 | 
			
		||||
		            resourceDir = "static/fonts/SimSun.ttf";
 | 
			
		||||
		            break;
 | 
			
		||||
		        case "roman":
 | 
			
		||||
		        default:
 | 
			
		||||
		            resourceDir = "static/fonts/NotoSans-Regular.ttf";
 | 
			
		||||
		            break;
 | 
			
		||||
		    }
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
            if(!resourceDir.equals("")) {
 | 
			
		||||
	            ClassPathResource classPathResource = new ClassPathResource(resourceDir);
 | 
			
		||||
	            String fileExtension = resourceDir.substring(resourceDir.lastIndexOf("."));
 | 
			
		||||
	            File tempFile = File.createTempFile("NotoSansFont", fileExtension);
 | 
			
		||||
	            try (InputStream is = classPathResource.getInputStream(); FileOutputStream os = new FileOutputStream(tempFile)) {
 | 
			
		||||
	                IOUtils.copy(is, os);
 | 
			
		||||
	            }
 | 
			
		||||
	            
 | 
			
		||||
	            font = PDType0Font.load(document, tempFile);
 | 
			
		||||
	            tempFile.deleteOnExit();
 | 
			
		||||
            }
 | 
			
		||||
            contentStream.beginText();
 | 
			
		||||
            contentStream.setFont(font, fontSize);
 | 
			
		||||
            contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
 | 
			
		||||
@ -81,11 +129,19 @@ public class WatermarkController {
 | 
			
		||||
            // Add the watermark text
 | 
			
		||||
            for (int i = 0; i < watermarkRows; i++) {
 | 
			
		||||
                for (int j = 0; j < watermarkCols; j++) {
 | 
			
		||||
                    contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), j * watermarkWidth, i * watermarkHeight));
 | 
			
		||||
                	
 | 
			
		||||
                	if(producer.contains("Google Docs")) {
 | 
			
		||||
                		//This fixes weird unknown google docs y axis rotation/flip issue 
 | 
			
		||||
                		//TODO: Long term fix one day
 | 
			
		||||
                        //contentStream.setTextMatrix(1, 0, 0, -1, j * watermarkWidth, pageHeight - i * watermarkHeight);
 | 
			
		||||
                		Matrix matrix = new Matrix(1, 0, 0, -1, j * watermarkWidth, pageHeight - i * watermarkHeight);
 | 
			
		||||
                		contentStream.setTextMatrix(matrix);
 | 
			
		||||
                	} else {
 | 
			
		||||
                		contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), j * watermarkWidth, i * watermarkHeight));
 | 
			
		||||
                	}
 | 
			
		||||
                    contentStream.showTextWithPositioning(new Object[] { watermarkText });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            contentStream.endText();
 | 
			
		||||
 | 
			
		||||
            // Close the content stream
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,10 @@ import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.servlet.ModelAndView;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Hidden;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
 | 
			
		||||
@Controller
 | 
			
		||||
@Tag(name = "Convert", description = "Convert APIs")
 | 
			
		||||
public class ConverterWebController {
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/img-to-pdf")
 | 
			
		||||
 | 
			
		||||
@ -1,27 +1,29 @@
 | 
			
		||||
package stirling.software.SPDF.controller.web;
 | 
			
		||||
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.stereotype.Controller;
 | 
			
		||||
import org.springframework.ui.Model;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.ResponseBody;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Hidden;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
 | 
			
		||||
@Controller
 | 
			
		||||
@Tag(name = "General", description = "General APIs")
 | 
			
		||||
public class GeneralWebController {
 | 
			
		||||
	 @GetMapping("/pipeline")
 | 
			
		||||
	    @Hidden
 | 
			
		||||
	    public String pipelineForm(Model model) {
 | 
			
		||||
	        model.addAttribute("currentPage", "pipeline");
 | 
			
		||||
	        return "pipeline";
 | 
			
		||||
	    }
 | 
			
		||||
	 
 | 
			
		||||
    @GetMapping("/merge-pdfs")
 | 
			
		||||
    @Hidden
 | 
			
		||||
    public String mergePdfForm(Model model) {
 | 
			
		||||
        model.addAttribute("currentPage", "merge-pdfs");
 | 
			
		||||
        return "merge-pdfs";
 | 
			
		||||
    }
 | 
			
		||||
    @GetMapping("/about")
 | 
			
		||||
    @Hidden
 | 
			
		||||
    public String gameForm(Model model) {
 | 
			
		||||
        model.addAttribute("currentPage", "about");
 | 
			
		||||
        return "about";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    @GetMapping("/multi-tool")
 | 
			
		||||
    @Hidden
 | 
			
		||||
@ -29,17 +31,7 @@ public class GeneralWebController {
 | 
			
		||||
        model.addAttribute("currentPage", "multi-tool");
 | 
			
		||||
        return "multi-tool";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @GetMapping("/")
 | 
			
		||||
    public String home(Model model) {
 | 
			
		||||
        model.addAttribute("currentPage", "home");
 | 
			
		||||
        return "home";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/home")
 | 
			
		||||
    public String root(Model model) {
 | 
			
		||||
        return "redirect:/";
 | 
			
		||||
    }
 | 
			
		||||
   
 | 
			
		||||
    
 | 
			
		||||
    @GetMapping("/remove-pages")
 | 
			
		||||
    @Hidden
 | 
			
		||||
@ -76,20 +68,4 @@ public class GeneralWebController {
 | 
			
		||||
        return "sign";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @Hidden
 | 
			
		||||
    public String getRobotsTxt() {
 | 
			
		||||
        String allowGoogleVisibility = System.getProperty("ALLOW_GOOGLE_VISIBILITY");
 | 
			
		||||
        if (allowGoogleVisibility == null)
 | 
			
		||||
            allowGoogleVisibility = System.getenv("ALLOW_GOOGLE_VISIBILITY");
 | 
			
		||||
        if (allowGoogleVisibility == null)
 | 
			
		||||
            allowGoogleVisibility = "false";
 | 
			
		||||
        if (Boolean.parseBoolean(allowGoogleVisibility)) {
 | 
			
		||||
            return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /";
 | 
			
		||||
        } else {
 | 
			
		||||
            return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,52 @@
 | 
			
		||||
package stirling.software.SPDF.controller.web;
 | 
			
		||||
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.stereotype.Controller;
 | 
			
		||||
import org.springframework.ui.Model;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.ResponseBody;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Hidden;
 | 
			
		||||
 | 
			
		||||
@Controller
 | 
			
		||||
public class HomeWebController {
 | 
			
		||||
	 
 | 
			
		||||
    @GetMapping("/about")
 | 
			
		||||
    @Hidden
 | 
			
		||||
    public String gameForm(Model model) {
 | 
			
		||||
        model.addAttribute("currentPage", "about");
 | 
			
		||||
        return "about";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
   
 | 
			
		||||
    
 | 
			
		||||
    @GetMapping("/")
 | 
			
		||||
    public String home(Model model) {
 | 
			
		||||
        model.addAttribute("currentPage", "home");
 | 
			
		||||
        return "home";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/home")
 | 
			
		||||
    public String root(Model model) {
 | 
			
		||||
        return "redirect:/";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
   
 | 
			
		||||
 | 
			
		||||
    @GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @Hidden
 | 
			
		||||
    public String getRobotsTxt() {
 | 
			
		||||
        String allowGoogleVisibility = System.getProperty("ALLOW_GOOGLE_VISIBILITY");
 | 
			
		||||
        if (allowGoogleVisibility == null)
 | 
			
		||||
            allowGoogleVisibility = System.getenv("ALLOW_GOOGLE_VISIBILITY");
 | 
			
		||||
        if (allowGoogleVisibility == null)
 | 
			
		||||
            allowGoogleVisibility = "false";
 | 
			
		||||
        if (Boolean.parseBoolean(allowGoogleVisibility)) {
 | 
			
		||||
            return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /";
 | 
			
		||||
        } else {
 | 
			
		||||
            return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
@ -12,9 +12,12 @@ import io.micrometer.core.instrument.Counter;
 | 
			
		||||
import io.micrometer.core.instrument.Meter;
 | 
			
		||||
import io.micrometer.core.instrument.MeterRegistry;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/api/v1")
 | 
			
		||||
@Tag(name = "API", description = "Info APIs")
 | 
			
		||||
public class MetricsController {
 | 
			
		||||
 | 
			
		||||
    private final MeterRegistry meterRegistry;
 | 
			
		||||
@ -36,17 +39,31 @@ public class MetricsController {
 | 
			
		||||
    @GetMapping("/loads")
 | 
			
		||||
    @Operation(summary = "GET request count",
 | 
			
		||||
            description = "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.")
 | 
			
		||||
    public Double getPageLoads(@RequestParam Optional<String> endpoint) {
 | 
			
		||||
    public Double getPageLoads(@RequestParam(required = false,  name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
 | 
			
		||||
        try {
 | 
			
		||||
            double count = 0.0;
 | 
			
		||||
 | 
			
		||||
            double count = 0.0;
 | 
			
		||||
            
 | 
			
		||||
            for (Meter meter : meterRegistry.getMeters()) {
 | 
			
		||||
                if (meter.getId().getName().equals("http.requests")) {
 | 
			
		||||
                    String method = meter.getId().getTag("method");
 | 
			
		||||
                    if (method != null && method.equals("GET")) {
 | 
			
		||||
                        if (meter instanceof Counter) {
 | 
			
		||||
                            count += ((Counter) meter).count();
 | 
			
		||||
                        }
 | 
			
		||||
                    	
 | 
			
		||||
                    	if (endpoint.isPresent() && !endpoint.get().isBlank()) {
 | 
			
		||||
                    		if(!endpoint.get().startsWith("/")) {
 | 
			
		||||
                    			endpoint =  Optional.of("/" + endpoint.get());
 | 
			
		||||
                    		}
 | 
			
		||||
                    		System.out.println("loads " + endpoint.get() +  " vs " + meter.getId().getTag("uri"));
 | 
			
		||||
                    		if(endpoint.get().equals(meter.getId().getTag("uri"))){
 | 
			
		||||
                    			if (meter instanceof Counter) {
 | 
			
		||||
    	                            count += ((Counter) meter).count();
 | 
			
		||||
    	                        }
 | 
			
		||||
                    		}
 | 
			
		||||
                    	} else {
 | 
			
		||||
	                        if (meter instanceof Counter) {
 | 
			
		||||
	                            count += ((Counter) meter).count();
 | 
			
		||||
	                        }
 | 
			
		||||
                    	}
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -60,10 +77,15 @@ public class MetricsController {
 | 
			
		||||
    @GetMapping("/requests")
 | 
			
		||||
    @Operation(summary = "POST request count",
 | 
			
		||||
            description = "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.")
 | 
			
		||||
    public Double getTotalRequests(@RequestParam Optional<String> endpoint) {
 | 
			
		||||
    public Double getTotalRequests(@RequestParam(required = false,  name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
 | 
			
		||||
        try {
 | 
			
		||||
            Counter counter;
 | 
			
		||||
            if (endpoint.isPresent()) {
 | 
			
		||||
            if (endpoint.isPresent() && !endpoint.get().isBlank()) {
 | 
			
		||||
            	if(!endpoint.get().startsWith("/")) {
 | 
			
		||||
        			endpoint =  Optional.of("/" + endpoint.get());
 | 
			
		||||
        		}
 | 
			
		||||
            	
 | 
			
		||||
            	System.out.println("loads " + endpoint.get() +  " vs " + meterRegistry.get("http.requests").tags("uri", endpoint.get()).toString());
 | 
			
		||||
                counter = meterRegistry.get("http.requests")
 | 
			
		||||
                    .tags("method", "POST", "uri", endpoint.get()).counter();
 | 
			
		||||
            } else {
 | 
			
		||||
@ -72,7 +94,8 @@ public class MetricsController {
 | 
			
		||||
            }
 | 
			
		||||
            return counter.count();
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            return -1.0;
 | 
			
		||||
        	e.printStackTrace();
 | 
			
		||||
            return 0.0;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -12,8 +12,10 @@ import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.servlet.ModelAndView;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Hidden;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
 | 
			
		||||
@Controller
 | 
			
		||||
@Tag(name = "Other", description = "Other APIs")
 | 
			
		||||
public class OtherWebController {
 | 
			
		||||
    @GetMapping("/compress-pdf")
 | 
			
		||||
    @Hidden
 | 
			
		||||
 | 
			
		||||
@ -5,8 +5,10 @@ import org.springframework.ui.Model;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.Hidden;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
 | 
			
		||||
@Controller
 | 
			
		||||
@Tag(name = "Security", description = "Security APIs")
 | 
			
		||||
public class SecurityWebController {
 | 
			
		||||
    @GetMapping("/add-password")
 | 
			
		||||
    @Hidden
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,51 @@
 | 
			
		||||
package stirling.software.SPDF.model;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
public class PipelineConfig {
 | 
			
		||||
    private String name;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("pipeline")
 | 
			
		||||
    private List<PipelineOperation> operations;
 | 
			
		||||
 | 
			
		||||
    private String outputDir;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("outputFileName")
 | 
			
		||||
    private String outputPattern;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public String getName() {
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setName(String name) {
 | 
			
		||||
        this.name = name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<PipelineOperation> getOperations() {
 | 
			
		||||
        return operations;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setOperations(List<PipelineOperation> operations) {
 | 
			
		||||
        this.operations = operations;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getOutputDir() {
 | 
			
		||||
        return outputDir;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setOutputDir(String outputDir) {
 | 
			
		||||
        this.outputDir = outputDir;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getOutputPattern() {
 | 
			
		||||
        return outputPattern;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setOutputPattern(String outputPattern) {
 | 
			
		||||
        this.outputPattern = outputPattern;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
   
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
package stirling.software.SPDF.model;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class PipelineOperation {
 | 
			
		||||
        private String operation;
 | 
			
		||||
        private Map<String, Object> parameters;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public String getOperation() {
 | 
			
		||||
            return operation;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void setOperation(String operation) {
 | 
			
		||||
            this.operation = operation;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Map<String, Object> getParameters() {
 | 
			
		||||
            return parameters;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void setParameters(Map<String, Object> parameters) {
 | 
			
		||||
            this.parameters = parameters;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -27,14 +27,179 @@ import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
 | 
			
		||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
 | 
			
		||||
import org.apache.pdfbox.rendering.ImageType;
 | 
			
		||||
import org.apache.pdfbox.rendering.PDFRenderer;
 | 
			
		||||
import org.apache.pdfbox.text.PDFTextStripper;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import com.itextpdf.kernel.pdf.PdfPage;
 | 
			
		||||
import com.itextpdf.kernel.pdf.canvas.parser.PdfTextExtractor;
 | 
			
		||||
import com.itextpdf.kernel.pdf.canvas.parser.listener.SimpleTextExtractionStrategy;
 | 
			
		||||
 | 
			
		||||
import stirling.software.SPDF.pdf.ImageFinder;
 | 
			
		||||
 | 
			
		||||
public class PdfUtils {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	public static PDRectangle textToPageSize(String size) {
 | 
			
		||||
		switch (size) {
 | 
			
		||||
		case "A0":
 | 
			
		||||
			return PDRectangle.A0;
 | 
			
		||||
		case "A1":
 | 
			
		||||
			return PDRectangle.A1;
 | 
			
		||||
		case "A2":
 | 
			
		||||
			return PDRectangle.A2;
 | 
			
		||||
		case "A3":
 | 
			
		||||
			return PDRectangle.A3;
 | 
			
		||||
		case "A4":
 | 
			
		||||
			return PDRectangle.A4;
 | 
			
		||||
		case "A5":
 | 
			
		||||
			return PDRectangle.A5;
 | 
			
		||||
		case "A6":
 | 
			
		||||
			return PDRectangle.A6;
 | 
			
		||||
		case "LETTER":
 | 
			
		||||
			return PDRectangle.LETTER;
 | 
			
		||||
		case "LEGAL":
 | 
			
		||||
			return PDRectangle.LEGAL;
 | 
			
		||||
		default:
 | 
			
		||||
			throw new IllegalArgumentException("Invalid standard page size: " + size);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    public boolean hasImageInFile(PDDocument pdfDocument, String text, String pagesToCheck) throws IOException {
 | 
			
		||||
        PDFTextStripper textStripper = new PDFTextStripper();
 | 
			
		||||
        String pdfText = "";
 | 
			
		||||
 | 
			
		||||
        if(pagesToCheck == null || pagesToCheck.equals("all")) {
 | 
			
		||||
            pdfText = textStripper.getText(pdfDocument);
 | 
			
		||||
        } else {
 | 
			
		||||
            // remove whitespaces
 | 
			
		||||
            pagesToCheck = pagesToCheck.replaceAll("\\s+", "");
 | 
			
		||||
 | 
			
		||||
            String[] splitPoints = pagesToCheck.split(",");
 | 
			
		||||
            for (String splitPoint : splitPoints) {
 | 
			
		||||
                if (splitPoint.contains("-")) {
 | 
			
		||||
                    // Handle page ranges
 | 
			
		||||
                    String[] range = splitPoint.split("-");
 | 
			
		||||
                    int startPage = Integer.parseInt(range[0]);
 | 
			
		||||
                    int endPage = Integer.parseInt(range[1]);
 | 
			
		||||
 | 
			
		||||
                    for (int i = startPage; i <= endPage; i++) {
 | 
			
		||||
                        textStripper.setStartPage(i);
 | 
			
		||||
                        textStripper.setEndPage(i);
 | 
			
		||||
                        pdfText += textStripper.getText(pdfDocument);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Handle individual page
 | 
			
		||||
                    int page = Integer.parseInt(splitPoint);
 | 
			
		||||
                    textStripper.setStartPage(page);
 | 
			
		||||
                    textStripper.setEndPage(page);
 | 
			
		||||
                    pdfText += textStripper.getText(pdfDocument);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pdfDocument.close();
 | 
			
		||||
 | 
			
		||||
        return pdfText.contains(text);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static boolean hasImagesOnPage(PDPage page) throws IOException {
 | 
			
		||||
        ImageFinder imageFinder = new ImageFinder(page);
 | 
			
		||||
        imageFinder.processPage(page);
 | 
			
		||||
        return imageFinder.hasImages();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    public static boolean hasText(PDDocument  document, String phrase) throws IOException {
 | 
			
		||||
    	PDFTextStripper pdfStripper = new PDFTextStripper();
 | 
			
		||||
        String text = pdfStripper.getText(document);
 | 
			
		||||
        return text.contains(phrase);
 | 
			
		||||
   }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck) throws IOException {
 | 
			
		||||
        PDFTextStripper textStripper = new PDFTextStripper();
 | 
			
		||||
        String pdfText = "";
 | 
			
		||||
 | 
			
		||||
        if(pagesToCheck == null || pagesToCheck.equals("all")) {
 | 
			
		||||
            pdfText = textStripper.getText(pdfDocument);
 | 
			
		||||
        } else {
 | 
			
		||||
            // remove whitespaces
 | 
			
		||||
            pagesToCheck = pagesToCheck.replaceAll("\\s+", "");
 | 
			
		||||
 | 
			
		||||
            String[] splitPoints = pagesToCheck.split(",");
 | 
			
		||||
            for (String splitPoint : splitPoints) {
 | 
			
		||||
                if (splitPoint.contains("-")) {
 | 
			
		||||
                    // Handle page ranges
 | 
			
		||||
                    String[] range = splitPoint.split("-");
 | 
			
		||||
                    int startPage = Integer.parseInt(range[0]);
 | 
			
		||||
                    int endPage = Integer.parseInt(range[1]);
 | 
			
		||||
 | 
			
		||||
                    for (int i = startPage; i <= endPage; i++) {
 | 
			
		||||
                        textStripper.setStartPage(i);
 | 
			
		||||
                        textStripper.setEndPage(i);
 | 
			
		||||
                        pdfText += textStripper.getText(pdfDocument);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Handle individual page
 | 
			
		||||
                    int page = Integer.parseInt(splitPoint);
 | 
			
		||||
                    textStripper.setStartPage(page);
 | 
			
		||||
                    textStripper.setEndPage(page);
 | 
			
		||||
                    pdfText += textStripper.getText(pdfDocument);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pdfDocument.close();
 | 
			
		||||
 | 
			
		||||
        return pdfText.contains(text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator) throws IOException {
 | 
			
		||||
        int actualPageCount = pdfDocument.getNumberOfPages();
 | 
			
		||||
        pdfDocument.close();
 | 
			
		||||
 | 
			
		||||
        switch(comparator.toLowerCase()) {
 | 
			
		||||
            case "greater":
 | 
			
		||||
                return actualPageCount > pageCount;
 | 
			
		||||
            case "equal":
 | 
			
		||||
                return actualPageCount == pageCount;
 | 
			
		||||
            case "less":
 | 
			
		||||
                return actualPageCount < pageCount;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new IllegalArgumentException("Invalid comparator. Only 'greater', 'equal', and 'less' are supported.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws IOException {
 | 
			
		||||
        PDPage firstPage = pdfDocument.getPage(0);
 | 
			
		||||
        PDRectangle mediaBox = firstPage.getMediaBox();
 | 
			
		||||
 | 
			
		||||
        float actualPageWidth = mediaBox.getWidth();
 | 
			
		||||
        float actualPageHeight = mediaBox.getHeight();
 | 
			
		||||
 | 
			
		||||
        pdfDocument.close();
 | 
			
		||||
 | 
			
		||||
        // Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4
 | 
			
		||||
        String[] dimensions = expectedPageSize.split("x");
 | 
			
		||||
        float expectedPageWidth = Float.parseFloat(dimensions[0]);
 | 
			
		||||
        float expectedPageHeight = Float.parseFloat(dimensions[1]);
 | 
			
		||||
 | 
			
		||||
        // Checks if the actual page size matches the expected page size
 | 
			
		||||
        return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI, String filename) throws IOException, Exception {
 | 
			
		||||
        try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
 | 
			
		||||
            PDFRenderer pdfRenderer = new PDFRenderer(document);
 | 
			
		||||
@ -43,7 +208,7 @@ public class PdfUtils {
 | 
			
		||||
 | 
			
		||||
            // Create images of all pages
 | 
			
		||||
            for (int i = 0; i < pageCount; i++) {
 | 
			
		||||
                images.add(pdfRenderer.renderImageWithDPI(i, 300, colorType));
 | 
			
		||||
                images.add(pdfRenderer.renderImageWithDPI(i, DPI, colorType));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (singleImage) {
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,12 @@
 | 
			
		||||
spring.http.multipart.max-file-size=2GB
 | 
			
		||||
spring.http.multipart.max-request-size=2GB
 | 
			
		||||
spring.http.multipart.max-file-size=${MAX_FILE_SIZE:2000MB}
 | 
			
		||||
spring.http.multipart.max-request-size=${MAX_FILE_SIZE:2000MB}
 | 
			
		||||
 | 
			
		||||
multipart.enabled=true
 | 
			
		||||
multipart.max-file-size=2000MB
 | 
			
		||||
multipart.max-request-size=2000MB
 | 
			
		||||
multipart.max-file-size=${MAX_FILE_SIZE:2000MB}
 | 
			
		||||
multipart.max-request-size=${MAX_FILE_SIZE:2000MB}
 | 
			
		||||
 | 
			
		||||
spring.servlet.multipart.max-file-size=2000MB
 | 
			
		||||
spring.servlet.multipart.max-request-size=2000MB
 | 
			
		||||
spring.servlet.multipart.max-file-size=${MAX_FILE_SIZE:2000MB}
 | 
			
		||||
spring.servlet.multipart.max-request-size=${MAX_FILE_SIZE:2000MB}
 | 
			
		||||
 | 
			
		||||
server.forward-headers-strategy=NATIVE
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,7 @@ server.servlet.context-path=${APP_ROOT_PATH:/}
 | 
			
		||||
spring.devtools.restart.enabled=true
 | 
			
		||||
spring.devtools.livereload.enabled=true
 | 
			
		||||
 | 
			
		||||
spring.thymeleaf.encoding=UTF-8
 | 
			
		||||
spring.thymeleaf.encoding=UTF-8 
 | 
			
		||||
 | 
			
		||||
server.connection-timeout=${CONNECTION_TIMEOUT:5m}
 | 
			
		||||
spring.mvc.async.request-timeout=${ASYNC_CONNECTION_TIMEOUT:300000}
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ close=\u0625\u063A\u0644\u0627\u0642
 | 
			
		||||
filesSelected = الملفات المحددة
 | 
			
		||||
noFavourites = لم تتم إضافة أي مفضلات
 | 
			
		||||
bored = الانتظار بالملل؟
 | 
			
		||||
 | 
			
		||||
alphabet=\u0627\u0644\u0623\u0628\u062C\u062F\u064A\u0629
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=Tanca
 | 
			
		||||
filesSelected=fitxers seleccionats
 | 
			
		||||
noFavourites=No s'ha afegit cap favorit
 | 
			
		||||
bored=Avorrit esperant?
 | 
			
		||||
alphabet=Alfabet
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=Schließen
 | 
			
		||||
filesSelected=Dateien ausgewählt
 | 
			
		||||
noFavourites=Keine Favoriten hinzugefügt
 | 
			
		||||
bored=Gelangweiltes Warten?
 | 
			
		||||
alphabet=Alphabet
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=Close
 | 
			
		||||
filesSelected=files selected
 | 
			
		||||
noFavourites=No favourites added
 | 
			
		||||
bored=Bored Waiting?
 | 
			
		||||
alphabet=Alphabet
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
@ -131,6 +132,9 @@ 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 a page and/or its contents.
 | 
			
		||||
 | 
			
		||||
home.pipeline.title=Pipeline
 | 
			
		||||
home.pipeline.desc=Pipeline desc.
 | 
			
		||||
 | 
			
		||||
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
 | 
			
		||||
 | 
			
		||||
downloadPdf=Download PDF
 | 
			
		||||
@ -139,6 +143,8 @@ font=Font
 | 
			
		||||
selectFillter=-- Select --
 | 
			
		||||
pageNum=Page Number 
 | 
			
		||||
 | 
			
		||||
pipeline.title=Pipeline
 | 
			
		||||
 | 
			
		||||
pageLayout.title=Multi Page Layout
 | 
			
		||||
pageLayout.header=Multi Page Layout
 | 
			
		||||
pageLayout.pagesPerSheet=Pages per sheet:
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=Cerrar
 | 
			
		||||
filesSelected=archivos seleccionados
 | 
			
		||||
noFavourites=No se agregaron favoritos
 | 
			
		||||
bored=¿Aburrido de esperar?
 | 
			
		||||
alphabet=Alfabeto
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=Itxi
 | 
			
		||||
filesSelected=Hautatutako fitxategiak
 | 
			
		||||
noFavourites=Ez dira gogokoak gehitu
 | 
			
		||||
bored=Itxaroten aspertuta?
 | 
			
		||||
alphabet=Alfabetoa
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,7 @@ close=Fermer
 | 
			
		||||
filesSelected=fichiers sélectionnés
 | 
			
		||||
noFavourites=Aucun favori ajouté
 | 
			
		||||
bored=Ennuyé d'attendre ?
 | 
			
		||||
alphabet=Alphabet
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=Chiudi
 | 
			
		||||
filesSelected=file selezionati
 | 
			
		||||
noFavourites=Nessun preferito
 | 
			
		||||
bored=Stanco di aspettare?
 | 
			
		||||
alphabet=Alfabeto
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,8 @@ save=保存
 | 
			
		||||
close=閉じる
 | 
			
		||||
filesSelected=選択されたファイル
 | 
			
		||||
noFavourites=お気に入りはありません
 | 
			
		||||
bored=待ち時間が退屈?
 | 
			
		||||
bored=å¾…ã<EFBFBD>¡æ™‚é–“ã<EFBFBD>Œé€€å±ˆï¼
 | 
			
		||||
alphabet=\u30A2\u30EB\u30D5\u30A1\u30D9\u30C3\u30C8Ÿ
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=닫기
 | 
			
		||||
filesSelected=개 파일 선택됨
 | 
			
		||||
noFavourites=즐겨찾기 없음
 | 
			
		||||
bored=기다리는 게 지루하신가요?
 | 
			
		||||
alphabet=\uC54C\uD30C\uBCB3
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=Zamknij
 | 
			
		||||
filesSelected=wybrane pliki
 | 
			
		||||
noFavourites=Nie dodano ulubionych
 | 
			
		||||
bored=Znudzony czekaniem?
 | 
			
		||||
alphabet=Alfabet
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=Fechar
 | 
			
		||||
filesSelected=arquivos selecionados
 | 
			
		||||
noFavourites=Nenhum favorito adicionado
 | 
			
		||||
bored=Entediado esperando?
 | 
			
		||||
alphabet=Alfabeto
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=Închide
 | 
			
		||||
filesSelected=fișiere selectate
 | 
			
		||||
noFavourites=Niciun favorit adăugat
 | 
			
		||||
bored=Plictisit așteptând?
 | 
			
		||||
alphabet=Alfabet
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=Закрыть
 | 
			
		||||
filesSelected=файлов выбрано
 | 
			
		||||
noFavourites=Нет избранного
 | 
			
		||||
bored=Скучно ждать?
 | 
			
		||||
alphabet=\u0430\u043B\u0444\u0430\u0432\u0438\u0442
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=Stäng
 | 
			
		||||
filesSelected=filer valda
 | 
			
		||||
noFavourites=Inga favoriter har lagts till
 | 
			
		||||
bored=Utråkad att vänta?
 | 
			
		||||
alphabet=Alfabet
 | 
			
		||||
#############
 | 
			
		||||
# HEMSIDA #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ close=关闭
 | 
			
		||||
filesSelected=\u9009\u62E9\u7684\u6587\u4EF6
 | 
			
		||||
noFavourites=\u6CA1\u6709\u6DFB\u52A0\u6536\u85CF\u5939
 | 
			
		||||
bored=\u65E0\u804A\u7B49\u5F85\uFF1F
 | 
			
		||||
alphabet=\u5B57\u6BCD\u8868
 | 
			
		||||
#############
 | 
			
		||||
# HOME-PAGE #
 | 
			
		||||
#############
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/Meiryo.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/Meiryo.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/NotoSans-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/NotoSans-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/NotoSansArabic-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/NotoSansArabic-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/NotoSansJP-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/NotoSansJP-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/NotoSansSC-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/NotoSansSC-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/SimSun.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/SimSun.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/malgun.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/malgun.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/static/NotoSansJP-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/fonts/static/NotoSansJP-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										3
									
								
								src/main/resources/static/images/pipeline.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/main/resources/static/images/pipeline.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bezier2" viewBox="0 0 16 16">
 | 
			
		||||
  <path fill-rule="evenodd" d="M1 2.5A1.5 1.5 0 0 1 2.5 1h1A1.5 1.5 0 0 1 5 2.5h4.134a1 1 0 1 1 0 1h-2.01c.18.18.34.381.484.605.638.992.892 2.354.892 3.895 0 1.993.257 3.092.713 3.7.356.476.895.721 1.787.784A1.5 1.5 0 0 1 12.5 11h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5H6.866a1 1 0 1 1 0-1h1.711a2.839 2.839 0 0 1-.165-.2C7.743 11.407 7.5 10.007 7.5 8c0-1.46-.246-2.597-.733-3.355-.39-.605-.952-1-1.767-1.112A1.5 1.5 0 0 1 3.5 5h-1A1.5 1.5 0 0 1 1 3.5v-1zM2.5 2a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm10 10a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 790 B  | 
@ -218,7 +218,7 @@ const DraggableUtils = {
 | 
			
		||||
    },
 | 
			
		||||
    async getOverlayedPdfDocument() {
 | 
			
		||||
        const pdfBytes = await this.pdfDoc.getData();
 | 
			
		||||
        const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes);
 | 
			
		||||
        const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, { ignoreEncryption: true });
 | 
			
		||||
        this.storePageContents();
 | 
			
		||||
 | 
			
		||||
        const pagesMap = this.documentsMap.get(this.pdfDoc);
 | 
			
		||||
 | 
			
		||||
@ -152,7 +152,7 @@ class PdfContainer {
 | 
			
		||||
 | 
			
		||||
    async toPdfLib(objectUrl) {
 | 
			
		||||
        const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer());
 | 
			
		||||
        const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes);
 | 
			
		||||
        const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, { ignoreEncryption: true });
 | 
			
		||||
        return pdfDoc;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										419
									
								
								src/main/resources/static/js/pipeline.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										419
									
								
								src/main/resources/static/js/pipeline.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,419 @@
 | 
			
		||||
document.getElementById('validateButton').addEventListener('click', function(event) {
 | 
			
		||||
	event.preventDefault();
 | 
			
		||||
	validatePipeline();
 | 
			
		||||
});
 | 
			
		||||
function validatePipeline() {
 | 
			
		||||
	let pipelineListItems = document.getElementById('pipelineList').children;
 | 
			
		||||
	let isValid = true;
 | 
			
		||||
	let containsAddPassword = false;
 | 
			
		||||
	for (let i = 0; i < pipelineListItems.length - 1; i++) {
 | 
			
		||||
		let currentOperation = pipelineListItems[i].querySelector('.operationName').textContent;
 | 
			
		||||
		let nextOperation = pipelineListItems[i + 1].querySelector('.operationName').textContent;
 | 
			
		||||
		if (currentOperation === '/add-password') {
 | 
			
		||||
			containsAddPassword = true;
 | 
			
		||||
		}
 | 
			
		||||
		console.log(currentOperation);
 | 
			
		||||
		console.log(apiDocs[currentOperation]);
 | 
			
		||||
		let currentOperationDescription = apiDocs[currentOperation]?.post?.description || "";
 | 
			
		||||
		let nextOperationDescription = apiDocs[nextOperation]?.post?.description || "";
 | 
			
		||||
 | 
			
		||||
		console.log("currentOperationDescription", currentOperationDescription);
 | 
			
		||||
		console.log("nextOperationDescription", nextOperationDescription);
 | 
			
		||||
 | 
			
		||||
		let currentOperationOutput = currentOperationDescription.match(/Output:([A-Z\/]*)/)?.[1] || "";
 | 
			
		||||
		let nextOperationInput = nextOperationDescription.match(/Input:([A-Z\/]*)/)?.[1] || "";
 | 
			
		||||
 | 
			
		||||
		console.log("Operation " + currentOperation + " Output: " + currentOperationOutput);
 | 
			
		||||
		console.log("Operation " + nextOperation + " Input: " + nextOperationInput);
 | 
			
		||||
 | 
			
		||||
		// Splitting in case of multiple possible output/input
 | 
			
		||||
		let currentOperationOutputArr = currentOperationOutput.split('/');
 | 
			
		||||
		let nextOperationInputArr = nextOperationInput.split('/');
 | 
			
		||||
 | 
			
		||||
		if (currentOperationOutput !== 'ANY' && nextOperationInput !== 'ANY') {
 | 
			
		||||
			let intersection = currentOperationOutputArr.filter(value => nextOperationInputArr.includes(value));
 | 
			
		||||
			console.log(`Intersection: ${intersection}`);
 | 
			
		||||
 | 
			
		||||
			if (intersection.length === 0) {
 | 
			
		||||
				isValid = false;
 | 
			
		||||
				console.log(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
 | 
			
		||||
				alert(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (containsAddPassword && pipelineListItems[pipelineListItems.length - 1].querySelector('.operationName').textContent !== '/add-password') {
 | 
			
		||||
		alert('The "add-password" operation should be at the end of the operations sequence. Please adjust the operations order.');
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	if (isValid) {
 | 
			
		||||
		console.log('Pipeline is valid');
 | 
			
		||||
		// Continue with the pipeline operation
 | 
			
		||||
	} else {
 | 
			
		||||
		console.error('Pipeline is not valid');
 | 
			
		||||
		// Stop operation, maybe display an error to the user
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return isValid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
document.getElementById('submitConfigBtn').addEventListener('click', function() {
 | 
			
		||||
 | 
			
		||||
	if (validatePipeline() === false) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	let selectedOperation = document.getElementById('operationsDropdown').value;
 | 
			
		||||
	let parameters = operationSettings[selectedOperation] || {};
 | 
			
		||||
 | 
			
		||||
	let pipelineConfig = {
 | 
			
		||||
		"name": "uniquePipelineName",
 | 
			
		||||
		"pipeline": [{
 | 
			
		||||
			"operation": selectedOperation,
 | 
			
		||||
			"parameters": parameters
 | 
			
		||||
		}]
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	let pipelineConfigJson = JSON.stringify(pipelineConfig, null, 2);
 | 
			
		||||
 | 
			
		||||
	let formData = new FormData();
 | 
			
		||||
 | 
			
		||||
	let fileInput = document.getElementById('fileInput');
 | 
			
		||||
	let files = fileInput.files;
 | 
			
		||||
 | 
			
		||||
	for (let i = 0; i < files.length; i++) {
 | 
			
		||||
		console.log("files[i]", files[i].name);
 | 
			
		||||
		formData.append('fileInput', files[i], files[i].name);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	console.log("pipelineConfigJson", pipelineConfigJson);
 | 
			
		||||
	formData.append('json', pipelineConfigJson);
 | 
			
		||||
	console.log("formData", formData);
 | 
			
		||||
 | 
			
		||||
	fetch('/handleData', {
 | 
			
		||||
		method: 'POST',
 | 
			
		||||
		body: formData
 | 
			
		||||
	})
 | 
			
		||||
		.then(response => response.blob())
 | 
			
		||||
		.then(blob => {
 | 
			
		||||
 | 
			
		||||
			let url = window.URL.createObjectURL(blob);
 | 
			
		||||
			let a = document.createElement('a');
 | 
			
		||||
			a.href = url;
 | 
			
		||||
			a.download = 'outputfile';
 | 
			
		||||
			document.body.appendChild(a);
 | 
			
		||||
			a.click();
 | 
			
		||||
			a.remove();
 | 
			
		||||
		})
 | 
			
		||||
		.catch((error) => {
 | 
			
		||||
			console.error('Error:', error);
 | 
			
		||||
		});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
let apiDocs = {};
 | 
			
		||||
 | 
			
		||||
let operationSettings = {};
 | 
			
		||||
 | 
			
		||||
fetch('v3/api-docs')
 | 
			
		||||
	.then(response => response.json())
 | 
			
		||||
	.then(data => {
 | 
			
		||||
		
 | 
			
		||||
		apiDocs = data.paths;
 | 
			
		||||
		let operationsDropdown = document.getElementById('operationsDropdown');
 | 
			
		||||
		const ignoreOperations = ["/handleData", "operationToIgnore"]; // Add the operations you want to ignore here
 | 
			
		||||
				
 | 
			
		||||
		operationsDropdown.innerHTML = '';
 | 
			
		||||
 | 
			
		||||
		let operationsByTag = {};
 | 
			
		||||
 | 
			
		||||
		// Group operations by tags
 | 
			
		||||
		Object.keys(data.paths).forEach(operationPath => {
 | 
			
		||||
			let operation = data.paths[operationPath].post;
 | 
			
		||||
			if (operation && !ignoreOperations.includes(operationPath) && !operation.description.includes("Type:MISO")) {
 | 
			
		||||
				let operationTag = operation.tags[0]; // This assumes each operation has exactly one tag
 | 
			
		||||
				if (!operationsByTag[operationTag]) {
 | 
			
		||||
					operationsByTag[operationTag] = [];
 | 
			
		||||
				}
 | 
			
		||||
				operationsByTag[operationTag].push(operationPath);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// Specify the order of tags
 | 
			
		||||
        let tagOrder = ["General", "Security", "Convert", "Other", "Filter"];
 | 
			
		||||
 | 
			
		||||
        // Create dropdown options
 | 
			
		||||
        tagOrder.forEach(tag => {
 | 
			
		||||
            if (operationsByTag[tag]) {
 | 
			
		||||
                let group = document.createElement('optgroup');
 | 
			
		||||
                group.label = tag;
 | 
			
		||||
 | 
			
		||||
                operationsByTag[tag].forEach(operationPath => {
 | 
			
		||||
                    let option = document.createElement('option');
 | 
			
		||||
                    let operationWithoutSlash = operationPath.replace(/\//g, ''); // Remove slashes
 | 
			
		||||
                    option.textContent = operationWithoutSlash;
 | 
			
		||||
                    option.value = operationPath; // Keep the value with slashes for querying
 | 
			
		||||
                    group.appendChild(option);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                operationsDropdown.appendChild(group);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
document.getElementById('addOperationBtn').addEventListener('click', function() {
 | 
			
		||||
	let selectedOperation = document.getElementById('operationsDropdown').value;
 | 
			
		||||
	let pipelineList = document.getElementById('pipelineList');
 | 
			
		||||
 | 
			
		||||
	let listItem = document.createElement('li');
 | 
			
		||||
	listItem.className = "list-group-item";
 | 
			
		||||
	let hasSettings = (apiDocs[selectedOperation] && apiDocs[selectedOperation].post &&
 | 
			
		||||
	apiDocs[selectedOperation].post.parameters && apiDocs[selectedOperation].post.parameters.length > 0);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	listItem.innerHTML = `
 | 
			
		||||
		<div class="d-flex justify-content-between align-items-center w-100">
 | 
			
		||||
			<div class="operationName">${selectedOperation}</div>
 | 
			
		||||
			<div class="arrows d-flex">
 | 
			
		||||
				<button class="btn btn-secondary move-up btn-margin"><span>↑</span></button>
 | 
			
		||||
				<button class="btn btn-secondary move-down btn-margin"><span>↓</span></button>
 | 
			
		||||
				<button class="btn btn-warning pipelineSettings btn-margin" ${hasSettings ? "" : "disabled"}><span style="color: ${hasSettings ? "black" : "grey"};">⚙️</span></button>
 | 
			
		||||
				<button class="btn btn-danger remove"><span>X</span></button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	`;
 | 
			
		||||
 | 
			
		||||
	pipelineList.appendChild(listItem);
 | 
			
		||||
 | 
			
		||||
	listItem.querySelector('.move-up').addEventListener('click', function(event) {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		if (listItem.previousElementSibling) {
 | 
			
		||||
			pipelineList.insertBefore(listItem, listItem.previousElementSibling);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	listItem.querySelector('.move-down').addEventListener('click', function(event) {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		if (listItem.nextElementSibling) {
 | 
			
		||||
			pipelineList.insertBefore(listItem.nextElementSibling, listItem);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	listItem.querySelector('.remove').addEventListener('click', function(event) {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		pipelineList.removeChild(listItem);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	listItem.querySelector('.pipelineSettings').addEventListener('click', function(event) {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		showpipelineSettingsModal(selectedOperation);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	function showpipelineSettingsModal(operation) {
 | 
			
		||||
		let pipelineSettingsModal = document.getElementById('pipelineSettingsModal');
 | 
			
		||||
		let pipelineSettingsContent = document.getElementById('pipelineSettingsContent');
 | 
			
		||||
		let operationData = apiDocs[operation].post.parameters || [];
 | 
			
		||||
 | 
			
		||||
		pipelineSettingsContent.innerHTML = '';
 | 
			
		||||
 | 
			
		||||
		operationData.forEach(parameter => {
 | 
			
		||||
			let parameterDiv = document.createElement('div');
 | 
			
		||||
			parameterDiv.className = "form-group";
 | 
			
		||||
 | 
			
		||||
			let parameterLabel = document.createElement('label');
 | 
			
		||||
			parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `;
 | 
			
		||||
			parameterLabel.title = parameter.description;
 | 
			
		||||
			parameterDiv.appendChild(parameterLabel);
 | 
			
		||||
 | 
			
		||||
			let parameterInput;
 | 
			
		||||
			switch (parameter.schema.type) {
 | 
			
		||||
				case 'string':
 | 
			
		||||
				case 'number':
 | 
			
		||||
				case 'integer':
 | 
			
		||||
					parameterInput = document.createElement('input');
 | 
			
		||||
					parameterInput.type = parameter.schema.type === 'string' ? 'text' : 'number';
 | 
			
		||||
					parameterInput.className = "form-control";
 | 
			
		||||
					break;
 | 
			
		||||
				case 'boolean':
 | 
			
		||||
					parameterInput = document.createElement('input');
 | 
			
		||||
					parameterInput.type = 'checkbox';
 | 
			
		||||
					break;
 | 
			
		||||
				case 'array':
 | 
			
		||||
				case 'object':
 | 
			
		||||
					parameterInput = document.createElement('textarea');
 | 
			
		||||
					parameterInput.placeholder = `Enter a JSON formatted ${parameter.schema.type}`;
 | 
			
		||||
					parameterInput.className = "form-control";
 | 
			
		||||
					break;
 | 
			
		||||
				case 'enum':
 | 
			
		||||
					parameterInput = document.createElement('select');
 | 
			
		||||
					parameterInput.className = "form-control";
 | 
			
		||||
					parameter.schema.enum.forEach(option => {
 | 
			
		||||
						let optionElement = document.createElement('option');
 | 
			
		||||
						optionElement.value = option;
 | 
			
		||||
						optionElement.text = option;
 | 
			
		||||
						parameterInput.appendChild(optionElement);
 | 
			
		||||
					});
 | 
			
		||||
					break;
 | 
			
		||||
				default:
 | 
			
		||||
					parameterInput = document.createElement('input');
 | 
			
		||||
					parameterInput.type = 'text';
 | 
			
		||||
					parameterInput.className = "form-control";
 | 
			
		||||
			}
 | 
			
		||||
			parameterInput.id = parameter.name;
 | 
			
		||||
 | 
			
		||||
			if (operationSettings[operation] && operationSettings[operation][parameter.name] !== undefined) {
 | 
			
		||||
				let savedValue = operationSettings[operation][parameter.name];
 | 
			
		||||
 | 
			
		||||
				switch (parameter.schema.type) {
 | 
			
		||||
					case 'number':
 | 
			
		||||
					case 'integer':
 | 
			
		||||
						parameterInput.value = savedValue.toString();
 | 
			
		||||
						break;
 | 
			
		||||
					case 'boolean':
 | 
			
		||||
						parameterInput.checked = savedValue;
 | 
			
		||||
						break;
 | 
			
		||||
					case 'array':
 | 
			
		||||
					case 'object':
 | 
			
		||||
						parameterInput.value = JSON.stringify(savedValue);
 | 
			
		||||
						break;
 | 
			
		||||
					default:
 | 
			
		||||
						parameterInput.value = savedValue;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			parameterDiv.appendChild(parameterInput);
 | 
			
		||||
 | 
			
		||||
			pipelineSettingsContent.appendChild(parameterDiv);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		let saveButton = document.createElement('button');
 | 
			
		||||
		saveButton.textContent = "Save Settings";
 | 
			
		||||
		saveButton.className = "btn btn-primary";
 | 
			
		||||
		saveButton.addEventListener('click', function(event) {
 | 
			
		||||
			event.preventDefault();
 | 
			
		||||
			let settings = {};
 | 
			
		||||
			operationData.forEach(parameter => {
 | 
			
		||||
				let value = document.getElementById(parameter.name).value;
 | 
			
		||||
				switch (parameter.schema.type) {
 | 
			
		||||
					case 'number':
 | 
			
		||||
					case 'integer':
 | 
			
		||||
						settings[parameter.name] = Number(value);
 | 
			
		||||
						break;
 | 
			
		||||
					case 'boolean':
 | 
			
		||||
						settings[parameter.name] = document.getElementById(parameter.name).checked;
 | 
			
		||||
						break;
 | 
			
		||||
					case 'array':
 | 
			
		||||
					case 'object':
 | 
			
		||||
						try {
 | 
			
		||||
							settings[parameter.name] = JSON.parse(value);
 | 
			
		||||
						} catch (err) {
 | 
			
		||||
							console.error(`Invalid JSON format for ${parameter.name}`);
 | 
			
		||||
						}
 | 
			
		||||
						break;
 | 
			
		||||
					default:
 | 
			
		||||
						settings[parameter.name] = value;
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			operationSettings[operation] = settings;
 | 
			
		||||
			console.log(settings);
 | 
			
		||||
			pipelineSettingsModal.style.display = "none";
 | 
			
		||||
		});
 | 
			
		||||
		pipelineSettingsContent.appendChild(saveButton);
 | 
			
		||||
 | 
			
		||||
		pipelineSettingsModal.style.display = "block";
 | 
			
		||||
 | 
			
		||||
		pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() {
 | 
			
		||||
			pipelineSettingsModal.style.display = "none";
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		window.onclick = function(event) {
 | 
			
		||||
			if (event.target == pipelineSettingsModal) {
 | 
			
		||||
				pipelineSettingsModal.style.display = "none";
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	document.getElementById('savePipelineBtn').addEventListener('click', function() {
 | 
			
		||||
		if (validatePipeline() === false) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		let pipelineList = document.getElementById('pipelineList').children;
 | 
			
		||||
		let pipelineConfig = {
 | 
			
		||||
			"name": "uniquePipelineName",
 | 
			
		||||
			"pipeline": []
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		for (let i = 0; i < pipelineList.length; i++) {
 | 
			
		||||
			let operationName = pipelineList[i].querySelector('.operationName').textContent;
 | 
			
		||||
			let parameters = operationSettings[operationName] || {};
 | 
			
		||||
 | 
			
		||||
			pipelineConfig.pipeline.push({
 | 
			
		||||
				"operation": operationName,
 | 
			
		||||
				"parameters": parameters
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let a = document.createElement('a');
 | 
			
		||||
		a.href = URL.createObjectURL(new Blob([JSON.stringify(pipelineConfig, null, 2)], {
 | 
			
		||||
			type: 'application/json'
 | 
			
		||||
		}));
 | 
			
		||||
		a.download = 'pipelineConfig.json';
 | 
			
		||||
		a.style.display = 'none';
 | 
			
		||||
 | 
			
		||||
		document.body.appendChild(a);
 | 
			
		||||
		a.click();
 | 
			
		||||
		document.body.removeChild(a);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	document.getElementById('uploadPipelineBtn').addEventListener('click', function() {
 | 
			
		||||
		document.getElementById('uploadPipelineInput').click();
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	document.getElementById('uploadPipelineInput').addEventListener('change', function(e) {
 | 
			
		||||
		let reader = new FileReader();
 | 
			
		||||
		reader.onload = function(event) {
 | 
			
		||||
			let pipelineConfig = JSON.parse(event.target.result);
 | 
			
		||||
			let pipelineList = document.getElementById('pipelineList');
 | 
			
		||||
 | 
			
		||||
			while (pipelineList.firstChild) {
 | 
			
		||||
				pipelineList.removeChild(pipelineList.firstChild);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			pipelineConfig.pipeline.forEach(operationConfig => {
 | 
			
		||||
				let operationsDropdown = document.getElementById('operationsDropdown');
 | 
			
		||||
				operationsDropdown.value = operationConfig.operation;
 | 
			
		||||
				operationSettings[operationConfig.operation] = operationConfig.parameters;
 | 
			
		||||
				document.getElementById('addOperationBtn').click();
 | 
			
		||||
 | 
			
		||||
				let lastOperation = pipelineList.lastChild;
 | 
			
		||||
 | 
			
		||||
				lastOperation.querySelector('.pipelineSettings').click();
 | 
			
		||||
 | 
			
		||||
				Object.keys(operationConfig.parameters).forEach(parameterName => {
 | 
			
		||||
					let input = document.getElementById(parameterName);
 | 
			
		||||
					if (input) {
 | 
			
		||||
						switch (input.type) {
 | 
			
		||||
							case 'checkbox':
 | 
			
		||||
								input.checked = operationConfig.parameters[parameterName];
 | 
			
		||||
								break;
 | 
			
		||||
							case 'number':
 | 
			
		||||
								input.value = operationConfig.parameters[parameterName].toString();
 | 
			
		||||
								break;
 | 
			
		||||
							case 'text':
 | 
			
		||||
							case 'textarea':
 | 
			
		||||
							default:
 | 
			
		||||
								input.value = JSON.stringify(operationConfig.parameters[parameterName]);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				document.querySelector('#pipelineSettingsModal .btn-primary').click();
 | 
			
		||||
			});
 | 
			
		||||
		};
 | 
			
		||||
		reader.readAsText(e.target.files[0]);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
@ -32,6 +32,13 @@
 | 
			
		||||
                            <span class="icon-text"  th:text="#{home.multiTool.title}"></span>
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <!--<li class="nav-item">
 | 
			
		||||
                        <a class="nav-link" href="#" th:href="@{pipeline}" th:classappend="${currentPage}=='pipeline' ? 'active' : ''" th:title="#{home.pipeline.desc}">
 | 
			
		||||
                            <img class="icon" src="images/pipeline.svg" alt="icon">
 | 
			
		||||
                            <span class="icon-text"  th:text="#{home.pipeline.title}"></span>
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </li>-->
 | 
			
		||||
                    
 | 
			
		||||
<li class="nav-item nav-item-separator"></li>
 | 
			
		||||
                    <li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='merge-pdfs' OR ${currentPage}=='split-pdfs' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' ? 'active' : ''">
 | 
			
		||||
    <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,7 @@
 | 
			
		||||
            <!-- Features -->
 | 
			
		||||
            <div class="features-container container">
 | 
			
		||||
            
 | 
			
		||||
                <!--  <div th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', svgPath='images/pipeline.svg')}"></div>-->
 | 
			
		||||
                
 | 
			
		||||
                <div th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', svgPath='images/tools.svg')}"></div>
 | 
			
		||||
                <div th:replace="~{fragments/card :: card(id='merge-pdfs', cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs', svgPath='images/union.svg')}"></div>
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,7 @@
 | 
			
		||||
            const processFile = async (file) => {
 | 
			
		||||
                const origFileUrl = URL.createObjectURL(file);
 | 
			
		||||
                const formPdfBytes = await fetch(origFileUrl).then(res => res.arrayBuffer());
 | 
			
		||||
                const pdfDoc = await PDFDocument.load(formPdfBytes);
 | 
			
		||||
                const pdfDoc = await PDFDocument.load(formPdfBytes, { ignoreEncryption: true });
 | 
			
		||||
 | 
			
		||||
                const form = pdfDoc.getForm();
 | 
			
		||||
                form.flatten();
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										111
									
								
								src/main/resources/templates/pipeline.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/main/resources/templates/pipeline.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
			
		||||
<!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=#{pipeline.title})}"></th:block>
 | 
			
		||||
<body>
 | 
			
		||||
	<div id="page-container">
 | 
			
		||||
		<div id="content-wrap">
 | 
			
		||||
			<div th:insert="~{fragments/navbar.html :: navbar}"></div>
 | 
			
		||||
			<br> <br>
 | 
			
		||||
			<div class="container" id="dropContainer">
 | 
			
		||||
				<div class="row justify-content-center">
 | 
			
		||||
					<div class="col-md-6">
 | 
			
		||||
 | 
			
		||||
						<div class="mb-3">
 | 
			
		||||
							<button id="savePipelineBtn" class="btn btn-success">Download</button>
 | 
			
		||||
 | 
			
		||||
							<button id="validateButton" class="btn btn-success">Validate</button>
 | 
			
		||||
							<div class="btn-group">
 | 
			
		||||
								<button id="uploadPipelineBtn" class="btn btn-primary">Upload</button>
 | 
			
		||||
								<input type="file" id="uploadPipelineInput" accept=".json"
 | 
			
		||||
									style="display: none;">
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div id="pipelineContainer" class="card">
 | 
			
		||||
 | 
			
		||||
							<!-- Pipeline Configuration Card Header -->
 | 
			
		||||
							<div class="card-header">
 | 
			
		||||
								<h2 class="card-title">Pipeline Configuration</h2>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							<!-- Pipeline Configuration Body -->
 | 
			
		||||
							<div class="card-body">
 | 
			
		||||
								<div class="mb-3">
 | 
			
		||||
									<select id="operationsDropdown" class="form-select">
 | 
			
		||||
										<!-- Options will be dynamically populated here -->
 | 
			
		||||
									</select>
 | 
			
		||||
								</div>
 | 
			
		||||
								<div class="mb-3">
 | 
			
		||||
									<button id="addOperationBtn" class="btn btn-primary">Add operation</button>
 | 
			
		||||
								</div>
 | 
			
		||||
								<h3>Pipeline:</h3>
 | 
			
		||||
								<ol id="pipelineList" class="list-group">
 | 
			
		||||
									<!-- Pipeline operations will be dynamically populated here -->
 | 
			
		||||
								</ol>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							<input type="file" id="fileInput" multiple>
 | 
			
		||||
 | 
			
		||||
							<button class="btn btn-primary" id="submitConfigBtn">Submit</button>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<!-- pipelineSettings modal -->
 | 
			
		||||
						<div id="pipelineSettingsModal" class="modal">
 | 
			
		||||
							<div class="modal-content">
 | 
			
		||||
								<div class="modal-body">
 | 
			
		||||
									<span class="close">×</span>
 | 
			
		||||
									<h2>Operation Settings</h2>
 | 
			
		||||
									<div id="pipelineSettingsContent">
 | 
			
		||||
										<!-- pipelineSettings will be dynamically populated here -->
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
							<script src="js/pipeline.js"></script>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<style>
 | 
			
		||||
.modal {
 | 
			
		||||
	display: none; /* Hidden by default */
 | 
			
		||||
	position: fixed; /* Stay in place */
 | 
			
		||||
	z-index: 1; /* Sit on top */
 | 
			
		||||
	padding-top: 100px; /* Location of the box */
 | 
			
		||||
	left: 0;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	width: 100%; /* Full width */
 | 
			
		||||
	height: 100%; /* Full height */
 | 
			
		||||
	overflow: auto; /* Enable scroll if needed */
 | 
			
		||||
	background-color: rgb(0, 0, 0); /* Fallback color */
 | 
			
		||||
	background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Modal Content */
 | 
			
		||||
.modal-content {
 | 
			
		||||
	background-color: #fefefe;
 | 
			
		||||
	margin: auto;
 | 
			
		||||
	padding: 20px;
 | 
			
		||||
	border: 1px solid #888;
 | 
			
		||||
	width: 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-margin {
 | 
			
		||||
	margin-right: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.modal-body {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
		<div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
@ -18,6 +18,17 @@
 | 
			
		||||
                                <label th:text="#{watermark.selectText.1}"></label>
 | 
			
		||||
                                <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
	                            <label for="fontSize" th:text="#{alphabet} + ':'"></label> 
 | 
			
		||||
	                            <select class="form-control" name="alphabet" id="alphabet-select">
 | 
			
		||||
	                                <option value="romain">Roman</option>
 | 
			
		||||
	                                <option value="arabic">العربية</option>
 | 
			
		||||
	                                <option value="japanese">日本語</option>
 | 
			
		||||
	                                <option value="korean">한국어</option>
 | 
			
		||||
	                                <option value="chinese">简体中文</option>
 | 
			
		||||
	                            </select>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label for="watermarkText" th:text="#{watermark.selectText.2}"></label> 
 | 
			
		||||
                                <input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user