mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-10-25 11:17:28 +02:00 
			
		
		
		
	utf8 bug fix and scan pages (#113)
This commit is contained in:
		
							parent
							
								
									2d4aff3b08
								
							
						
					
					
						commit
						5bee714437
					
				| @ -1,5 +1,9 @@ | ||||
| # Build jbig2enc in a separate stage | ||||
| FROM frooodle/stirling-pdf-base:latest | ||||
| FROM frooodle/stirling-pdf-base:beta2 | ||||
| 
 | ||||
| # Create scripts folder and copy local scripts | ||||
| RUN mkdir /scripts | ||||
| COPY ./scripts/* /scripts/ | ||||
| 
 | ||||
| # Copy the application JAR file | ||||
| COPY build/libs/*.jar app.jar | ||||
| @ -13,7 +17,8 @@ ENV APP_HOME_NAME="Stirling PDF" | ||||
| #ENV APP_NAVBAR_NAME="Stirling PDF" | ||||
| 
 | ||||
| # Run the application | ||||
| ENTRYPOINT java -jar /app.jar | ||||
| ENTRYPOINT ["/scripts/init.sh"] | ||||
| CMD ["java", "-jar", "/app.jar"] | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -21,10 +21,9 @@ RUN git clone https://github.com/agl/jbig2enc && \ | ||||
|     make && \ | ||||
|     make install | ||||
| 
 | ||||
| # Main stage | ||||
| FROM openjdk:17-jdk-slim | ||||
| 
 | ||||
| # Install necessary dependencies | ||||
| # Main stage | ||||
| FROM openjdk:17-jdk-slim AS base | ||||
| RUN apt-get update && \ | ||||
|     apt-get install -y --no-install-recommends \ | ||||
|         libreoffice-core \ | ||||
| @ -33,12 +32,31 @@ RUN apt-get update && \ | ||||
|         libreoffice-calc \ | ||||
|         libreoffice-impress \ | ||||
|         python3-uno \ | ||||
| 		python3-pip \ | ||||
|         python3-pip \ | ||||
|         unoconv \ | ||||
| 		pngquant \ | ||||
| 		unpaper \ | ||||
|         pngquant \ | ||||
|         unpaper \ | ||||
|         ocrmypdf && \ | ||||
| 	pip install --user --upgrade ocrmypdf | ||||
|     rm -rf /var/lib/apt/lists/* && \ | ||||
|     mkdir /usr/share/tesseract-ocr-original && \ | ||||
|     cp -r /usr/share/tesseract-ocr/* /usr/share/tesseract-ocr-original && \ | ||||
|     rm -rf /usr/share/tesseract-ocr | ||||
| 
 | ||||
| # Copy the jbig2enc binary from the builder stage | ||||
| # Python packages stage | ||||
| FROM base AS python-packages | ||||
| RUN apt-get update && \ | ||||
|     apt-get install -y --no-install-recommends \ | ||||
|         build-essential \ | ||||
|         libffi-dev \ | ||||
|         libssl-dev \ | ||||
|         zlib1g-dev \ | ||||
|         libjpeg-dev && \ | ||||
|     pip install --upgrade pip && \ | ||||
|     pip install --no-cache-dir \ | ||||
|         opencv-python-headless && \ | ||||
|     rm -rf /var/lib/apt/lists/* | ||||
| 
 | ||||
| # Final stage: Copy necessary files from the previous stage | ||||
| FROM base | ||||
| COPY --from=python-packages /usr/local /usr/local | ||||
| COPY --from=jbig2enc_builder /usr/local/bin/jbig2 /usr/local/bin/jbig2 | ||||
| @ -20,6 +20,8 @@ Depending on your requirements, you can choose the appropriate language pack for | ||||
| 1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need. | ||||
| 2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tesseract-ocr/4.00/tessdata` | ||||
| 
 | ||||
| # DO NOT REMOVE EXISTING ENG.TRAINEDDATA, ITS REQUIRED. | ||||
| 
 | ||||
| #### Docker | ||||
| 
 | ||||
| If you are using Docker, you need to expose the Tesseract tessdata directory as a volume in order to use the additional language packs.  | ||||
|  | ||||
| @ -99,3 +99,7 @@ Simply use environment variables APP_HOME_NAME, APP_HOME_DESCRIPTION and APP_NAV | ||||
| If running Java directly, you can also pass these as properties using -D arguments. | ||||
| 
 | ||||
| Using the same method you can also change the default language by providing APP_LOCALE with values like de-DE fr-FR or ar-AR to select your default language (Will always default to English on invalid locale) | ||||
| 
 | ||||
| ## API | ||||
| For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation | ||||
| [here](https://app.swaggerhub.com/apis-docs/Frooodle/Stirling-PDF/1.0.0) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation  | ||||
| @ -5,7 +5,7 @@ plugins { | ||||
| } | ||||
| 
 | ||||
| group = 'stirling.software' | ||||
| version = '0.6.0' | ||||
| version = '0.7.0' | ||||
| sourceCompatibility = '17' | ||||
| 
 | ||||
| repositories { | ||||
| @ -19,11 +19,11 @@ dependencies { | ||||
| 	// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio | ||||
| 	implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4' | ||||
| 	implementation 'commons-io:commons-io:2.11.0' | ||||
| 		 | ||||
| 	 | ||||
|     implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' | ||||
|      | ||||
| 	//general PDF | ||||
|     implementation 'org.apache.pdfbox:pdfbox:2.0.28' | ||||
|          | ||||
|     implementation 'com.itextpdf:itextpdf:5.5.13.3' | ||||
|     developmentOnly("org.springframework.boot:spring-boot-devtools") | ||||
| 
 | ||||
| } | ||||
|  | ||||
							
								
								
									
										9
									
								
								scripts/init.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								scripts/init.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| #!/bin/bash | ||||
| 
 | ||||
| # Copy the original tesseract-ocr files to the volume directory without overwriting existing files | ||||
| echo "Copying original files without overwriting existing files" | ||||
| mkdir -p /usr/share/tesseract-ocr | ||||
| cp -rn /usr/share/tesseract-ocr-original/* /usr/share/tesseract-ocr | ||||
| 
 | ||||
| # Run the main command | ||||
| exec "$@" | ||||
							
								
								
									
										134
									
								
								scripts/split_photos.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								scripts/split_photos.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,134 @@ | ||||
| import sys | ||||
| import cv2 | ||||
| import numpy as np | ||||
| import os  | ||||
| 
 | ||||
| def find_photo_boundaries(image, background_color, tolerance=30, min_area=10000, min_contour_area=500): | ||||
|     mask = cv2.inRange(image, background_color - tolerance, background_color + tolerance) | ||||
|     mask = cv2.bitwise_not(mask) | ||||
|     kernel = np.ones((5,5),np.uint8) | ||||
|     mask = cv2.dilate(mask, kernel, iterations=2) | ||||
|     contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | ||||
| 
 | ||||
|     photo_boundaries = [] | ||||
|     for contour in contours: | ||||
|         x, y, w, h = cv2.boundingRect(contour) | ||||
|         area = w * h | ||||
|         contour_area = cv2.contourArea(contour) | ||||
|         if area >= min_area and contour_area >= min_contour_area: | ||||
|             photo_boundaries.append((x, y, w, h)) | ||||
| 
 | ||||
|     return photo_boundaries | ||||
| 
 | ||||
| def estimate_background_color(image, sample_points=5): | ||||
|     h, w, _ = image.shape | ||||
|     points = [ | ||||
|         (0, 0), | ||||
|         (w - 1, 0), | ||||
|         (w - 1, h - 1), | ||||
|         (0, h - 1), | ||||
|         (w // 2, h // 2), | ||||
|     ] | ||||
| 
 | ||||
|     colors = [] | ||||
|     for x, y in points: | ||||
|         colors.append(image[y, x]) | ||||
| 
 | ||||
|     return np.median(colors, axis=0) | ||||
| 
 | ||||
| def auto_rotate(image, angle_threshold=10): | ||||
|     gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | ||||
|     ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) | ||||
|     contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | ||||
| 
 | ||||
|     if len(contours) == 0: | ||||
|         return image | ||||
| 
 | ||||
|     largest_contour = max(contours, key=cv2.contourArea) | ||||
|     mu = cv2.moments(largest_contour) | ||||
|      | ||||
|     if mu["m00"] == 0: | ||||
|         return image | ||||
|      | ||||
|     x_centroid = int(mu["m10"] / mu["m00"]) | ||||
|     y_centroid = int(mu["m01"] / mu["m00"]) | ||||
| 
 | ||||
|     coords = np.column_stack(np.where(binary > 0)) | ||||
|     u, _, vt = np.linalg.svd(coords - np.array([[y_centroid, x_centroid]]), full_matrices=False) | ||||
| 
 | ||||
|     angle = np.arctan2(u[1, 0], u[0, 0]) * 180 / np.pi | ||||
| 
 | ||||
|     if angle < -45: | ||||
|         angle = -(90 + angle) | ||||
|     else: | ||||
|         angle = -angle | ||||
| 
 | ||||
|     if abs(angle) < angle_threshold: | ||||
|         return image | ||||
| 
 | ||||
|     (h, w) = image.shape[:2] | ||||
|     center = (w // 2, h // 2) | ||||
|     M = cv2.getRotationMatrix2D(center, angle, 1.0) | ||||
|     return cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| def crop_borders(image, border_color, tolerance=30): | ||||
|     mask = cv2.inRange(image, border_color - tolerance, border_color + tolerance) | ||||
|      | ||||
|     contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | ||||
|     if len(contours) == 0: | ||||
|         return image | ||||
| 
 | ||||
|     largest_contour = max(contours, key=cv2.contourArea) | ||||
|     x, y, w, h = cv2.boundingRect(largest_contour) | ||||
|      | ||||
|     return image[y:y+h, x:x+w] | ||||
|      | ||||
| def split_photos(input_file, output_directory, tolerance=30, min_area=10000, min_contour_area=500, angle_threshold=10, border_size=0): | ||||
|     image = cv2.imread(input_file) | ||||
|     background_color = estimate_background_color(image) | ||||
| 
 | ||||
|     # Add a constant border around the image | ||||
|     image = cv2.copyMakeBorder(image, border_size, border_size, border_size, border_size, cv2.BORDER_CONSTANT, value=background_color) | ||||
| 
 | ||||
|     photo_boundaries = find_photo_boundaries(image, background_color, tolerance) | ||||
| 
 | ||||
|     if not os.path.exists(output_directory): | ||||
|         os.makedirs(output_directory) | ||||
| 
 | ||||
|     # Get the input file's base name without the extension | ||||
|     input_file_basename = os.path.splitext(os.path.basename(input_file))[0] | ||||
| 
 | ||||
|     for idx, (x, y, w, h) in enumerate(photo_boundaries): | ||||
|         cropped_image = image[y:y+h, x:x+w] | ||||
|         cropped_image = auto_rotate(cropped_image, angle_threshold) | ||||
| 
 | ||||
|         # Remove the added border | ||||
|         cropped_image = cropped_image[border_size:-border_size, border_size:-border_size] | ||||
| 
 | ||||
|         output_path = os.path.join(output_directory, f"{input_file_basename}_{idx+1}.png") | ||||
|         cv2.imwrite(output_path, cropped_image) | ||||
|         print(f"Saved {output_path}") | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     if len(sys.argv) < 2: | ||||
|         print("Usage: python3 split_photos.py <input_file> <output_directory> [tolerance] [min_area] [min_contour_area] [angle_threshold] [border_size]") | ||||
|         print("\nParameters:") | ||||
|         print("  <input_file>       - The input scanned image containing multiple photos.") | ||||
|         print("  <output_directory> - The directory where the result images should be placed.") | ||||
|         print("  [tolerance]        - Optional. Determines the range of color variation around the estimated background color (default: 30).") | ||||
|         print("  [min_area]         - Optional. Sets the minimum area threshold for a photo (default: 10000).") | ||||
|         print("  [min_contour_area] - Optional. Sets the minimum contour area threshold for a photo (default: 500).") | ||||
|         print("  [angle_threshold]  - Optional. Sets the minimum absolute angle required for the image to be rotated (default: 10).") | ||||
|         print("  [border_size]      - Optional. Sets the size of the border added and removed to prevent white borders in the output (default: 0).") | ||||
|         sys.exit(1) | ||||
| 
 | ||||
|     input_file = sys.argv[1] | ||||
|     output_directory = sys.argv[2] | ||||
|     tolerance = int(sys.argv[3]) if len(sys.argv) > 3 else 20 | ||||
|     min_area = int(sys.argv[4]) if len(sys.argv) > 4 else 8000 | ||||
|     min_contour_area = int(sys.argv[5]) if len(sys.argv) > 5 else 500 | ||||
|     angle_threshold = int(sys.argv[6]) if len(sys.argv) > 6 else 60 | ||||
|     border_size = int(sys.argv[7]) if len(sys.argv) > 7 else 0 | ||||
|     split_photos(input_file, output_directory, tolerance=tolerance, min_area=min_area, min_contour_area=min_contour_area, angle_threshold=angle_threshold, border_size=border_size) | ||||
| @ -16,6 +16,7 @@ public class Beans implements WebMvcConfigurer { | ||||
|     @Override | ||||
|     public void addInterceptors(InterceptorRegistry registry) { | ||||
|         registry.addInterceptor(localeChangeInterceptor()); | ||||
|         registry.addInterceptor(new CleanUrlInterceptor()); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|  | ||||
| @ -0,0 +1,45 @@ | ||||
| package stirling.software.SPDF.config; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import org.springframework.web.servlet.HandlerInterceptor; | ||||
| import org.springframework.web.servlet.ModelAndView; | ||||
| 
 | ||||
| import jakarta.servlet.http.HttpServletRequest; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| public class CleanUrlInterceptor implements HandlerInterceptor { | ||||
| 
 | ||||
|     private static final Pattern LANG_PATTERN = Pattern.compile("&?lang=([^&]+)"); | ||||
| 
 | ||||
|     @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) : ""; | ||||
| 
 | ||||
|             // Check if there are any other query parameters besides the lang parameter | ||||
|             String remainingQueryString = queryString.replaceAll(LANG_PATTERN.pattern(), "").replaceAll("&+", "&").replaceAll("^&|&$", ""); | ||||
| 
 | ||||
|             if (!remainingQueryString.isEmpty()) { | ||||
|                 // Redirect to the URL without other query parameters | ||||
|                 String redirectUrl = requestURI + (langQueryString.isEmpty() ? "" : "?" + langQueryString); | ||||
|                 response.sendRedirect(redirectUrl); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| package stirling.software.SPDF.config; | ||||
| 
 | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| 
 | ||||
| import io.swagger.v3.oas.models.Components; | ||||
| import io.swagger.v3.oas.models.OpenAPI; | ||||
| import io.swagger.v3.oas.models.info.Info; | ||||
| import io.swagger.v3.oas.models.info.License; | ||||
| 
 | ||||
| @Configuration | ||||
| public class OpenApiConfig { | ||||
| 
 | ||||
|     @Bean | ||||
|     public OpenAPI customOpenAPI() { | ||||
|         return new OpenAPI().components(new Components()).info( | ||||
|                 new Info().title("Your API Title").version("1.0.0").description("Your API Description").license(new License().name("Your License Name").url("Your License URL"))); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,20 +0,0 @@ | ||||
| package stirling.software.SPDF.controller; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| 
 | ||||
| @Controller | ||||
| public class MultiToolController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(MultiToolController.class); | ||||
| 
 | ||||
|     @GetMapping("/multi-tool") | ||||
|     public String multiToolForm(Model model) { | ||||
|         model.addAttribute("currentPage", "multi-tool"); | ||||
|         return "multi-tool"; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,25 +0,0 @@ | ||||
| package stirling.software.SPDF.controller; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| 
 | ||||
| @Controller | ||||
| public class PdfController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(PdfController.class); | ||||
| 
 | ||||
|     @GetMapping("/") | ||||
|     public String home(Model model) { | ||||
|         model.addAttribute("currentPage", "home"); | ||||
|         return "home"; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/home") | ||||
|     public String root(Model model) { | ||||
|         return "redirect:/"; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,67 +1,69 @@ | ||||
| package stirling.software.SPDF.controller; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 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.PDPageTree; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @Controller | ||||
| public class MergeController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(MergeController.class); | ||||
| 
 | ||||
|     @GetMapping("/merge-pdfs") | ||||
|     public String hello(Model model) { | ||||
|         model.addAttribute("currentPage", "merge-pdfs"); | ||||
|         return "merge-pdfs"; | ||||
|     } | ||||
| 
 | ||||
|     private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException { | ||||
|         // Create a new empty document | ||||
|         PDDocument mergedDoc = new PDDocument(); | ||||
| 
 | ||||
|         // Iterate over the list of documents and add their pages to the merged document | ||||
|         for (PDDocument doc : documents) { | ||||
|             // Get all pages from the current document | ||||
|             PDPageTree pages = doc.getPages(); | ||||
|             // Iterate over the pages and add them to the merged document | ||||
|             for (PDPage page : pages) { | ||||
|                 mergedDoc.addPage(page); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Return the merged document | ||||
|         return mergedDoc; | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/merge-pdfs") | ||||
|     public ResponseEntity<byte[]> mergePdfs(@RequestParam("fileInput") MultipartFile[] files) throws IOException { | ||||
|         // Read the input PDF files into PDDocument objects | ||||
|         List<PDDocument> documents = new ArrayList<>(); | ||||
| 
 | ||||
|         // Loop through the files array and read each file into a PDDocument | ||||
|         for (MultipartFile file : files) { | ||||
|             documents.add(PDDocument.load(file.getInputStream())); | ||||
|         } | ||||
| 
 | ||||
|         PDDocument mergedDoc = mergeDocuments(documents); | ||||
| 
 | ||||
|         // Return the merged PDF as a response | ||||
|         return PdfUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf"); | ||||
|     } | ||||
| 
 | ||||
| package stirling.software.SPDF.controller.api; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 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.PDPageTree; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestPart; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @RestController | ||||
| public class MergeController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(MergeController.class); | ||||
| 
 | ||||
|     private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException { | ||||
|         // Create a new empty document | ||||
|         PDDocument mergedDoc = new PDDocument(); | ||||
| 
 | ||||
|         // Iterate over the list of documents and add their pages to the merged document | ||||
|         for (PDDocument doc : documents) { | ||||
|             // Get all pages from the current document | ||||
|             PDPageTree pages = doc.getPages(); | ||||
|             // Iterate over the pages and add them to the merged document | ||||
|             for (PDPage page : pages) { | ||||
|                 mergedDoc.addPage(page); | ||||
|             } | ||||
|              | ||||
|              | ||||
|         } | ||||
| 
 | ||||
|         // Return the merged document | ||||
|         return mergedDoc; | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs") | ||||
|     public ResponseEntity<byte[]> mergePdfs(@RequestPart(required = true, value = "fileInput") MultipartFile[] files) throws IOException { | ||||
|         // Read the input PDF files into PDDocument objects | ||||
|         List<PDDocument> documents = new ArrayList<>(); | ||||
| 
 | ||||
|         // Loop through the files array and read each file into a PDDocument | ||||
|         for (MultipartFile file : files) { | ||||
|             documents.add(PDDocument.load(file.getInputStream())); | ||||
|         } | ||||
| 
 | ||||
|         PDDocument mergedDoc = mergeDocuments(documents); | ||||
| 
 | ||||
|          | ||||
|         // Return the merged PDF as a response | ||||
|         ResponseEntity<byte[]> response = PdfUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf"); | ||||
|          | ||||
|         for (PDDocument doc : documents) { | ||||
|             // Close the document after processing | ||||
|             doc.close(); | ||||
|            } | ||||
|          | ||||
|         return response; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,121 +1,109 @@ | ||||
| package stirling.software.SPDF.controller; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.PDPage; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @Controller | ||||
| public class RearrangePagesPDFController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); | ||||
| 
 | ||||
|     @PostMapping("/remove-pages") | ||||
|     public ResponseEntity<byte[]> deletePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pagesToDelete") String pagesToDelete) throws IOException { | ||||
| 
 | ||||
|         PDDocument document = PDDocument.load(pdfFile.getBytes()); | ||||
| 
 | ||||
|         // Split the page order string into an array of page numbers or range of numbers | ||||
|         String[] pageOrderArr = pagesToDelete.split(","); | ||||
| 
 | ||||
|         List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages()); | ||||
| 
 | ||||
|         for (int i = pagesToRemove.size() - 1; i >= 0; i--) { | ||||
|             int pageIndex = pagesToRemove.get(i); | ||||
|             document.removePage(pageIndex); | ||||
|         } | ||||
|         return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf"); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/remove-pages") | ||||
|     public String pageDeleter(Model model) { | ||||
|         model.addAttribute("currentPage", "remove-pages"); | ||||
|         return "remove-pages"; | ||||
|     } | ||||
| 
 | ||||
|     private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) { | ||||
|         List<Integer> newPageOrder = new ArrayList<>(); | ||||
|         // loop through the page order array | ||||
|         for (String element : pageOrderArr) { | ||||
|             // check if the element contains a range of pages | ||||
|             if (element.contains("-")) { | ||||
|                 // split the range into start and end page | ||||
|                 String[] range = element.split("-"); | ||||
|                 int start = Integer.parseInt(range[0]); | ||||
|                 int end = Integer.parseInt(range[1]); | ||||
|                 // check if the end page is greater than total pages | ||||
|                 if (end > totalPages) { | ||||
|                     end = totalPages; | ||||
|                 } | ||||
|                 // loop through the range of pages | ||||
|                 for (int j = start; j <= end; j++) { | ||||
|                     // print the current index | ||||
|                     newPageOrder.add(j - 1); | ||||
|                 } | ||||
|             } else { | ||||
|                 // if the element is a single page | ||||
|                 newPageOrder.add(Integer.parseInt(element) - 1); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return newPageOrder; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pdf-organizer") | ||||
|     public String pageOrganizer(Model model) { | ||||
|         model.addAttribute("currentPage", "pdf-organizer"); | ||||
|         return "pdf-organizer"; | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/rearrange-pages") | ||||
|     public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) { | ||||
|         try { | ||||
|             // Load the input PDF | ||||
|             PDDocument document = PDDocument.load(pdfFile.getInputStream()); | ||||
| 
 | ||||
|             // Split the page order string into an array of page numbers or range of numbers | ||||
|             String[] pageOrderArr = pageOrder.split(","); | ||||
|             // int[] newPageOrder = new int[pageOrderArr.length]; | ||||
|             int totalPages = document.getNumberOfPages(); | ||||
| 
 | ||||
|             List<Integer> newPageOrder = pageOrderToString(pageOrderArr, totalPages); | ||||
| 
 | ||||
|             // Create a new list to hold the pages in the new order | ||||
|             List<PDPage> newPages = new ArrayList<>(); | ||||
|             for (int i = 0; i < newPageOrder.size(); i++) { | ||||
|                 newPages.add(document.getPage(newPageOrder.get(i))); | ||||
|             } | ||||
| 
 | ||||
|             // Remove all the pages from the original document | ||||
|             for (int i = document.getNumberOfPages() - 1; i >= 0; i--) { | ||||
|                 document.removePage(i); | ||||
|             } | ||||
| 
 | ||||
|             // Add the pages in the new order | ||||
|             for (PDPage page : newPages) { | ||||
|                 document.addPage(page); | ||||
|             } | ||||
| 
 | ||||
|             return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf"); | ||||
|         } catch (IOException e) { | ||||
| 
 | ||||
|             logger.error("Failed rearranging documents", e); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| package stirling.software.SPDF.controller.api; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.PDPage; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.bind.annotation.RequestPart; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @RestController | ||||
| public class RearrangePagesPDFController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/remove-pages") | ||||
|     public ResponseEntity<byte[]> deletePages(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("pagesToDelete") String pagesToDelete) | ||||
|             throws IOException { | ||||
| 
 | ||||
|         PDDocument document = PDDocument.load(pdfFile.getBytes()); | ||||
| 
 | ||||
|         // Split the page order string into an array of page numbers or range of numbers | ||||
|         String[] pageOrderArr = pagesToDelete.split(","); | ||||
| 
 | ||||
|         List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages()); | ||||
| 
 | ||||
|         for (int i = pagesToRemove.size() - 1; i >= 0; i--) { | ||||
|             int pageIndex = pagesToRemove.get(i); | ||||
|             document.removePage(pageIndex); | ||||
|         } | ||||
|         return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf"); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) { | ||||
|         List<Integer> newPageOrder = new ArrayList<>(); | ||||
|         // loop through the page order array | ||||
|         for (String element : pageOrderArr) { | ||||
|             // check if the element contains a range of pages | ||||
|             if (element.contains("-")) { | ||||
|                 // split the range into start and end page | ||||
|                 String[] range = element.split("-"); | ||||
|                 int start = Integer.parseInt(range[0]); | ||||
|                 int end = Integer.parseInt(range[1]); | ||||
|                 // check if the end page is greater than total pages | ||||
|                 if (end > totalPages) { | ||||
|                     end = totalPages; | ||||
|                 } | ||||
|                 // loop through the range of pages | ||||
|                 for (int j = start; j <= end; j++) { | ||||
|                     // print the current index | ||||
|                     newPageOrder.add(j - 1); | ||||
|                 } | ||||
|             } else { | ||||
|                 // if the element is a single page | ||||
|                 newPageOrder.add(Integer.parseInt(element) - 1); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return newPageOrder; | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages") | ||||
|     public ResponseEntity<byte[]> rearrangePages(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) { | ||||
|         try { | ||||
|             // Load the input PDF | ||||
|             PDDocument document = PDDocument.load(pdfFile.getInputStream()); | ||||
| 
 | ||||
|             // Split the page order string into an array of page numbers or range of numbers | ||||
|             String[] pageOrderArr = pageOrder.split(","); | ||||
|             // int[] newPageOrder = new int[pageOrderArr.length]; | ||||
|             int totalPages = document.getNumberOfPages(); | ||||
| 
 | ||||
|             List<Integer> newPageOrder = pageOrderToString(pageOrderArr, totalPages); | ||||
| 
 | ||||
|             // Create a new list to hold the pages in the new order | ||||
|             List<PDPage> newPages = new ArrayList<>(); | ||||
|             for (int i = 0; i < newPageOrder.size(); i++) { | ||||
|                 newPages.add(document.getPage(newPageOrder.get(i))); | ||||
|             } | ||||
| 
 | ||||
|             // Remove all the pages from the original document | ||||
|             for (int i = document.getNumberOfPages() - 1; i >= 0; i--) { | ||||
|                 document.removePage(i); | ||||
|             } | ||||
| 
 | ||||
|             // Add the pages in the new order | ||||
|             for (PDPage page : newPages) { | ||||
|                 document.addPage(page); | ||||
|             } | ||||
| 
 | ||||
|             return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf"); | ||||
|         } catch (IOException e) { | ||||
| 
 | ||||
|             logger.error("Failed rearranging documents", e); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,48 +1,41 @@ | ||||
| package stirling.software.SPDF.controller; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.PDPage; | ||||
| import org.apache.pdfbox.pdmodel.PDPageTree; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @Controller | ||||
| public class RotationController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(RotationController.class); | ||||
| 
 | ||||
|     @PostMapping("/rotate-pdf") | ||||
|     public ResponseEntity<byte[]> rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("angle") Integer angle) throws IOException { | ||||
| 
 | ||||
|         // Load the PDF document | ||||
|         PDDocument document = PDDocument.load(pdfFile.getBytes()); | ||||
| 
 | ||||
|         // Get the list of pages in the document | ||||
|         PDPageTree pages = document.getPages(); | ||||
| 
 | ||||
|         for (PDPage page : pages) { | ||||
|             page.setRotation(page.getRotation() + angle); | ||||
|         } | ||||
| 
 | ||||
|         return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf"); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/rotate-pdf") | ||||
|     public String rotatePdfForm(Model model) { | ||||
|         model.addAttribute("currentPage", "rotate-pdf"); | ||||
|         return "rotate-pdf"; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| package stirling.software.SPDF.controller.api; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.PDPage; | ||||
| import org.apache.pdfbox.pdmodel.PDPageTree; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @RestController | ||||
| public class RotationController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(RotationController.class); | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf") | ||||
|     public ResponseEntity<byte[]> rotatePDF(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("angle") Integer angle) throws IOException { | ||||
| 
 | ||||
|         // Load the PDF document | ||||
|         PDDocument document = PDDocument.load(pdfFile.getBytes()); | ||||
| 
 | ||||
|         // Get the list of pages in the document | ||||
|         PDPageTree pages = document.getPages(); | ||||
| 
 | ||||
|         for (PDPage page : pages) { | ||||
|             page.setRotation(page.getRotation() + angle); | ||||
|         } | ||||
| 
 | ||||
|         return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf"); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,135 +1,129 @@ | ||||
| package stirling.software.SPDF.controller; | ||||
| 
 | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| import java.util.zip.ZipEntry; | ||||
| import java.util.zip.ZipOutputStream; | ||||
| 
 | ||||
| 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.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| @Controller | ||||
| public class SplitPDFController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class); | ||||
| 
 | ||||
|     @PostMapping("/split-pages") | ||||
|     public ResponseEntity<Resource> splitPdf(@RequestParam("fileInput") MultipartFile file, @RequestParam("pages") String pages) throws IOException { | ||||
|         // parse user input | ||||
| 
 | ||||
|         // open the pdf document | ||||
|         InputStream inputStream = file.getInputStream(); | ||||
|         PDDocument document = PDDocument.load(inputStream); | ||||
| 
 | ||||
|         List<Integer> pageNumbers = new ArrayList<>(); | ||||
|         pages = pages.replaceAll("\\s+", ""); // remove whitespaces | ||||
|         if (pages.toLowerCase().equals("all")) { | ||||
|             for (int i = 0; i < document.getNumberOfPages(); i++) { | ||||
|                 pageNumbers.add(i); | ||||
|             } | ||||
|         } else { | ||||
|             List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(","))); | ||||
|             if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) { | ||||
|                 String lastpage = String.valueOf(document.getNumberOfPages()); | ||||
|                 pageNumbersStr.add(lastpage); | ||||
|             } | ||||
|             for (String page : pageNumbersStr) { | ||||
|                 if (page.contains("-")) { | ||||
|                     String[] range = page.split("-"); | ||||
|                     int start = Integer.parseInt(range[0]); | ||||
|                     int end = Integer.parseInt(range[1]); | ||||
|                     for (int i = start; i <= end; i++) { | ||||
|                         pageNumbers.add(i); | ||||
|                     } | ||||
|                 } else { | ||||
|                     pageNumbers.add(Integer.parseInt(page)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(","))); | ||||
| 
 | ||||
|         // split the document | ||||
|         List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); | ||||
|         int currentPage = 0; | ||||
|         for (int pageNumber : pageNumbers) { | ||||
|             try (PDDocument splitDocument = new PDDocument()) { | ||||
|                 for (int i = currentPage; i < pageNumber; i++) { | ||||
|                     PDPage page = document.getPage(i); | ||||
|                     splitDocument.addPage(page); | ||||
|                     logger.debug("Adding page {} to split document", i); | ||||
|                 } | ||||
|                 currentPage = pageNumber; | ||||
|                 logger.debug("Setting current page to {}", currentPage); | ||||
| 
 | ||||
|                 ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||||
|                 splitDocument.save(baos); | ||||
| 
 | ||||
|                 splitDocumentsBoas.add(baos); | ||||
|             } catch (Exception e) { | ||||
|                 logger.error("Failed splitting documents and saving them", e); | ||||
|                 throw e; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // closing the original document | ||||
|         document.close(); | ||||
| 
 | ||||
|         Path zipFile = Files.createTempFile("split_documents", ".zip"); | ||||
| 
 | ||||
|         try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { | ||||
|             // loop through the split documents and write them to the zip file | ||||
|             for (int i = 0; i < splitDocumentsBoas.size(); i++) { | ||||
|                 String fileName = "split_document_" + (i + 1) + ".pdf"; | ||||
|                 ByteArrayOutputStream baos = splitDocumentsBoas.get(i); | ||||
|                 byte[] pdf = baos.toByteArray(); | ||||
| 
 | ||||
|                 // Add PDF file to the zip | ||||
|                 ZipEntry pdfEntry = new ZipEntry(fileName); | ||||
|                 zipOut.putNextEntry(pdfEntry); | ||||
|                 zipOut.write(pdf); | ||||
|                 zipOut.closeEntry(); | ||||
| 
 | ||||
|                 logger.info("Wrote split document {} to zip file", fileName); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             logger.error("Failed writing to zip", e); | ||||
|             throw e; | ||||
|         } | ||||
| 
 | ||||
|         logger.info("Successfully created zip file with split documents: {}", zipFile.toString()); | ||||
|         byte[] data = Files.readAllBytes(zipFile); | ||||
|         ByteArrayResource resource = new ByteArrayResource(data); | ||||
|         Files.delete(zipFile); | ||||
| 
 | ||||
|         // return the Resource in the response | ||||
|         return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_split.zip") | ||||
|                 .contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/split-pdfs") | ||||
|     public String splitPdfForm(Model model) { | ||||
|         model.addAttribute("currentPage", "split-pdfs"); | ||||
|         return "split-pdfs"; | ||||
|     } | ||||
| } | ||||
| package stirling.software.SPDF.controller.api; | ||||
| 
 | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| import java.util.zip.ZipEntry; | ||||
| import java.util.zip.ZipOutputStream; | ||||
| 
 | ||||
| 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; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| @RestController | ||||
| public class SplitPDFController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class); | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/split-pages") | ||||
|     public ResponseEntity<Resource> splitPdf(@RequestPart(required = true, value = "fileInput") MultipartFile file, @RequestParam("pages") String pages) throws IOException { | ||||
|         // parse user input | ||||
| 
 | ||||
|         // open the pdf document | ||||
|         InputStream inputStream = file.getInputStream(); | ||||
|         PDDocument document = PDDocument.load(inputStream); | ||||
| 
 | ||||
|         List<Integer> pageNumbers = new ArrayList<>(); | ||||
|         pages = pages.replaceAll("\\s+", ""); // remove whitespaces | ||||
|         if (pages.toLowerCase().equals("all")) { | ||||
|             for (int i = 0; i < document.getNumberOfPages(); i++) { | ||||
|                 pageNumbers.add(i); | ||||
|             } | ||||
|         } else { | ||||
|             List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(","))); | ||||
|             if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) { | ||||
|                 String lastpage = String.valueOf(document.getNumberOfPages()); | ||||
|                 pageNumbersStr.add(lastpage); | ||||
|             } | ||||
|             for (String page : pageNumbersStr) { | ||||
|                 if (page.contains("-")) { | ||||
|                     String[] range = page.split("-"); | ||||
|                     int start = Integer.parseInt(range[0]); | ||||
|                     int end = Integer.parseInt(range[1]); | ||||
|                     for (int i = start; i <= end; i++) { | ||||
|                         pageNumbers.add(i); | ||||
|                     } | ||||
|                 } else { | ||||
|                     pageNumbers.add(Integer.parseInt(page)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(","))); | ||||
| 
 | ||||
|         // split the document | ||||
|         List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); | ||||
|         int currentPage = 0; | ||||
|         for (int pageNumber : pageNumbers) { | ||||
|             try (PDDocument splitDocument = new PDDocument()) { | ||||
|                 for (int i = currentPage; i < pageNumber; i++) { | ||||
|                     PDPage page = document.getPage(i); | ||||
|                     splitDocument.addPage(page); | ||||
|                     logger.debug("Adding page {} to split document", i); | ||||
|                 } | ||||
|                 currentPage = pageNumber; | ||||
|                 logger.debug("Setting current page to {}", currentPage); | ||||
| 
 | ||||
|                 ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||||
|                 splitDocument.save(baos); | ||||
| 
 | ||||
|                 splitDocumentsBoas.add(baos); | ||||
|             } catch (Exception e) { | ||||
|                 logger.error("Failed splitting documents and saving them", e); | ||||
|                 throw e; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // closing the original document | ||||
|         document.close(); | ||||
| 
 | ||||
|         Path zipFile = Files.createTempFile("split_documents", ".zip"); | ||||
| 
 | ||||
|         try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { | ||||
|             // loop through the split documents and write them to the zip file | ||||
|             for (int i = 0; i < splitDocumentsBoas.size(); i++) { | ||||
|                 String fileName = "split_document_" + (i + 1) + ".pdf"; | ||||
|                 ByteArrayOutputStream baos = splitDocumentsBoas.get(i); | ||||
|                 byte[] pdf = baos.toByteArray(); | ||||
| 
 | ||||
|                 // Add PDF file to the zip | ||||
|                 ZipEntry pdfEntry = new ZipEntry(fileName); | ||||
|                 zipOut.putNextEntry(pdfEntry); | ||||
|                 zipOut.write(pdf); | ||||
|                 zipOut.closeEntry(); | ||||
| 
 | ||||
|                 logger.info("Wrote split document {} to zip file", fileName); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             logger.error("Failed writing to zip", e); | ||||
|             throw e; | ||||
|         } | ||||
| 
 | ||||
|         logger.info("Successfully created zip file with split documents: {}", zipFile.toString()); | ||||
|         byte[] data = Files.readAllBytes(zipFile); | ||||
|         ByteArrayResource resource = new ByteArrayResource(data); | ||||
|         Files.delete(zipFile); | ||||
| 
 | ||||
|         // return the Resource in the response | ||||
|         return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_split.zip") | ||||
|                 .contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,97 +1,86 @@ | ||||
| package stirling.software.SPDF.controller.converters; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.apache.pdfbox.rendering.ImageType; | ||||
| 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.HttpStatus; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @Controller | ||||
| public class ConvertImgPDFController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class); | ||||
| 
 | ||||
|     @PostMapping("/pdf-to-img") | ||||
|     public ResponseEntity<Resource> convertToImage(@RequestParam("fileInput") MultipartFile file, @RequestParam("imageFormat") String imageFormat, | ||||
|             @RequestParam("singleOrMultiple") String singleOrMultiple, @RequestParam("colorType") String colorType, @RequestParam("dpi") String dpi) throws IOException { | ||||
| 
 | ||||
|         byte[] pdfBytes = file.getBytes(); | ||||
|         ImageType colorTypeResult = ImageType.RGB; | ||||
|         if ("greyscale".equals(colorType)) { | ||||
|             colorTypeResult = ImageType.GRAY; | ||||
|         } else if ("blackwhite".equals(colorType)) { | ||||
|             colorTypeResult = ImageType.BINARY; | ||||
|         } | ||||
|         // returns bytes for image | ||||
|         boolean singleImage = singleOrMultiple.equals("single"); | ||||
|         byte[] result = null; | ||||
|         try { | ||||
|             result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toUpperCase(), colorTypeResult, singleImage, Integer.valueOf(dpi)); | ||||
|         } catch (IOException e) { | ||||
|             // TODO Auto-generated catch block | ||||
|             e.printStackTrace(); | ||||
|         } catch (Exception e) { | ||||
|             // TODO Auto-generated catch block | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         if (singleImage) { | ||||
|             HttpHeaders headers = new HttpHeaders(); | ||||
|             headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat))); | ||||
|             ResponseEntity<Resource> response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK); | ||||
|             return response; | ||||
|         } else { | ||||
|             ByteArrayResource resource = new ByteArrayResource(result); | ||||
|             // return the Resource in the response | ||||
|             return ResponseEntity.ok() | ||||
|                     .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToImages.zip") | ||||
|                     .contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/img-to-pdf") | ||||
|     public ResponseEntity<byte[]> convertToPdf(@RequestParam("fileInput") MultipartFile[] file, @RequestParam(defaultValue = "false", name = "stretchToFit") boolean stretchToFit, | ||||
|             @RequestParam(defaultValue = "true", name = "autoRotate") boolean autoRotate) throws IOException { | ||||
|         // Convert the file to PDF and get the resulting bytes | ||||
|         System.out.println(stretchToFit); | ||||
|         byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate); | ||||
|         return PdfUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_coverted.pdf"); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/img-to-pdf") | ||||
|     public String convertToPdfForm(Model model) { | ||||
|         model.addAttribute("currentPage", "img-to-pdf"); | ||||
|         return "convert/img-to-pdf"; | ||||
|     } | ||||
| 
 | ||||
|     private String getMediaType(String imageFormat) { | ||||
|         if (imageFormat.equalsIgnoreCase("PNG")) | ||||
|             return "image/png"; | ||||
|         else if (imageFormat.equalsIgnoreCase("JPEG") || imageFormat.equalsIgnoreCase("JPG")) | ||||
|             return "image/jpeg"; | ||||
|         else if (imageFormat.equalsIgnoreCase("GIF")) | ||||
|             return "image/gif"; | ||||
|         else | ||||
|             return "application/octet-stream"; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-img") | ||||
|     public String pdfToimgForm(Model model) { | ||||
|         model.addAttribute("currentPage", "pdf-to-img"); | ||||
|         return "convert/pdf-to-img"; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| package stirling.software.SPDF.controller.api.converters; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.apache.pdfbox.rendering.ImageType; | ||||
| 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.HttpStatus; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @RestController | ||||
| public class ConvertImgPDFController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class); | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-img") | ||||
|     public ResponseEntity<Resource> convertToImage(@RequestPart(required = true, value = "fileInput") MultipartFile file, @RequestParam("imageFormat") String imageFormat, | ||||
|             @RequestParam("singleOrMultiple") String singleOrMultiple, @RequestParam("colorType") String colorType, @RequestParam("dpi") String dpi) throws IOException { | ||||
| 
 | ||||
|         byte[] pdfBytes = file.getBytes(); | ||||
|         ImageType colorTypeResult = ImageType.RGB; | ||||
|         if ("greyscale".equals(colorType)) { | ||||
|             colorTypeResult = ImageType.GRAY; | ||||
|         } else if ("blackwhite".equals(colorType)) { | ||||
|             colorTypeResult = ImageType.BINARY; | ||||
|         } | ||||
|         // returns bytes for image | ||||
|         boolean singleImage = singleOrMultiple.equals("single"); | ||||
|         byte[] result = null; | ||||
|         try { | ||||
|             result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toUpperCase(), colorTypeResult, singleImage, Integer.valueOf(dpi)); | ||||
|         } catch (IOException e) { | ||||
|             // TODO Auto-generated catch block | ||||
|             e.printStackTrace(); | ||||
|         } catch (Exception e) { | ||||
|             // TODO Auto-generated catch block | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         if (singleImage) { | ||||
|             HttpHeaders headers = new HttpHeaders(); | ||||
|             headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat))); | ||||
|             ResponseEntity<Resource> response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK); | ||||
|             return response; | ||||
|         } else { | ||||
|             ByteArrayResource resource = new ByteArrayResource(result); | ||||
|             // return the Resource in the response | ||||
|             return ResponseEntity.ok() | ||||
|                     .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToImages.zip") | ||||
|                     .contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/img-to-pdf") | ||||
|     public ResponseEntity<byte[]> convertToPdf(@RequestPart(required = true, value = "fileInput") MultipartFile[] file, | ||||
|             @RequestParam(defaultValue = "false", name = "stretchToFit") boolean stretchToFit, @RequestParam(defaultValue = "true", name = "autoRotate") boolean autoRotate) | ||||
|             throws IOException { | ||||
|         // Convert the file to PDF and get the resulting bytes | ||||
|         System.out.println(stretchToFit); | ||||
|         byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate); | ||||
|         return PdfUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_coverted.pdf"); | ||||
|     } | ||||
| 
 | ||||
|     private String getMediaType(String imageFormat) { | ||||
|         if (imageFormat.equalsIgnoreCase("PNG")) | ||||
|             return "image/png"; | ||||
|         else if (imageFormat.equalsIgnoreCase("JPEG") || imageFormat.equalsIgnoreCase("JPG")) | ||||
|             return "image/jpeg"; | ||||
|         else if (imageFormat.equalsIgnoreCase("GIF")) | ||||
|             return "image/gif"; | ||||
|         else | ||||
|             return "application/octet-stream"; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package stirling.software.SPDF.controller.converters; | ||||
| package stirling.software.SPDF.controller.api.converters; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Files; | ||||
| @ -10,17 +10,15 @@ import java.util.List; | ||||
| 
 | ||||
| import org.apache.commons.io.FilenameUtils; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| import stirling.software.SPDF.utils.ProcessExecutor; | ||||
| 
 | ||||
| @Controller | ||||
| @RestController | ||||
| public class ConvertOfficeController { | ||||
| 
 | ||||
|     public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { | ||||
| @ -50,20 +48,13 @@ public class ConvertOfficeController { | ||||
| 
 | ||||
|         return pdfBytes; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/file-to-pdf") | ||||
|     public String convertToPdfForm(Model model) { | ||||
|         model.addAttribute("currentPage", "file-to-pdf"); | ||||
|         return "convert/file-to-pdf"; | ||||
|     } | ||||
| 
 | ||||
|     private boolean isValidFileExtension(String fileExtension) { | ||||
|         String extensionPattern = "^(?i)[a-z0-9]{2,4}$"; | ||||
|         return fileExtension.matches(extensionPattern); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/file-to-pdf") | ||||
|     public ResponseEntity<byte[]> processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException { | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/file-to-pdf") | ||||
|     public ResponseEntity<byte[]> processPdfWithOCR(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile) throws IOException, InterruptedException { | ||||
| 
 | ||||
|         // unused but can start server instance if startup time is to long | ||||
|         // LibreOfficeListener.getInstance().start(); | ||||
| @ -0,0 +1,52 @@ | ||||
| package stirling.software.SPDF.controller.api.converters; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.springframework.http.ResponseEntity; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PDFToFile; | ||||
| 
 | ||||
| @RestController | ||||
| public class ConvertPDFToOffice { | ||||
| 
 | ||||
|   | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-html") | ||||
|     public ResponseEntity<byte[]> processPdfToHTML(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile) throws IOException, InterruptedException { | ||||
|         PDFToFile pdfToFile = new PDFToFile(); | ||||
|         return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import"); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-presentation") | ||||
|     public ResponseEntity<byte[]> processPdfToPresentation(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile, | ||||
|             @RequestParam("outputFormat") String outputFormat) throws IOException, InterruptedException { | ||||
|         PDFToFile pdfToFile = new PDFToFile(); | ||||
|         return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import"); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-text") | ||||
|     public ResponseEntity<byte[]> processPdfToRTForTXT(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile, | ||||
|             @RequestParam("outputFormat") String outputFormat) throws IOException, InterruptedException { | ||||
|         PDFToFile pdfToFile = new PDFToFile(); | ||||
|         return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-word") | ||||
|     public ResponseEntity<byte[]> processPdfToWord(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile, @RequestParam("outputFormat") String outputFormat) | ||||
|             throws IOException, InterruptedException { | ||||
|         PDFToFile pdfToFile = new PDFToFile(); | ||||
|         return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-xml") | ||||
|     public ResponseEntity<byte[]> processPdfToXML(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile) throws IOException, InterruptedException { | ||||
|         PDFToFile pdfToFile = new PDFToFile(); | ||||
|         return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package stirling.software.SPDF.controller.converters; | ||||
| package stirling.software.SPDF.controller.api.converters; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Files; | ||||
| @ -6,23 +6,20 @@ import java.nio.file.Path; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| import stirling.software.SPDF.utils.ProcessExecutor; | ||||
| 
 | ||||
| @Controller | ||||
| @RestController | ||||
| public class ConvertPDFToPDFA { | ||||
| 
 | ||||
|     @PostMapping("/pdf-to-pdfa") | ||||
|     public ResponseEntity<byte[]> pdfToPdfA(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException { | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa") | ||||
|     public ResponseEntity<byte[]> pdfToPdfA(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile) throws IOException, InterruptedException { | ||||
| 
 | ||||
|         // Save the uploaded file to a temporary location | ||||
|         Path tempInputFile = Files.createTempFile("input_", ".pdf"); | ||||
| @ -52,16 +49,7 @@ public class ConvertPDFToPDFA { | ||||
| 
 | ||||
|         // Return the optimized PDF as a response | ||||
|         String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf"; | ||||
|         HttpHeaders headers = new HttpHeaders(); | ||||
|         headers.setContentType(MediaType.APPLICATION_PDF); | ||||
|         headers.setContentDispositionFormData("attachment", outputFilename); | ||||
|         return ResponseEntity.ok().headers(headers).body(pdfBytes); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-pdfa") | ||||
|     public String pdfToPdfAForm(Model model) { | ||||
|         model.addAttribute("currentPage", "pdf-to-pdfa"); | ||||
|         return "convert/pdf-to-pdfa"; | ||||
|         return PdfUtils.bytesToWebResponse(pdfBytes, outputFilename); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,87 +1,76 @@ | ||||
| package stirling.software.SPDF.controller.other; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.ProcessExecutor; | ||||
| 
 | ||||
| @Controller | ||||
| public class CompressController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(CompressController.class); | ||||
| 
 | ||||
|     @GetMapping("/compress-pdf") | ||||
|     public String compressPdfForm(Model model) { | ||||
|         model.addAttribute("currentPage", "compress-pdf"); | ||||
|         return "other/compress-pdf"; | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/compress-pdf") | ||||
|     public ResponseEntity<byte[]> optimizePdf(@RequestParam("fileInput") MultipartFile inputFile, @RequestParam("optimizeLevel") int optimizeLevel, | ||||
|             @RequestParam(name = "fastWebView", required = false) Boolean fastWebView, @RequestParam(name = "jbig2Lossy", required = false) Boolean jbig2Lossy) | ||||
|             throws IOException, InterruptedException { | ||||
| 
 | ||||
|         // Save the uploaded file to a temporary location | ||||
|         Path tempInputFile = Files.createTempFile("input_", ".pdf"); | ||||
|         inputFile.transferTo(tempInputFile.toFile()); | ||||
| 
 | ||||
|         // Prepare the output file path | ||||
|         Path tempOutputFile = Files.createTempFile("output_", ".pdf"); | ||||
| 
 | ||||
|         // Prepare the OCRmyPDF command | ||||
|         List<String> command = new ArrayList<>(); | ||||
|         command.add("ocrmypdf"); | ||||
|         command.add("--skip-text"); | ||||
|         command.add("--tesseract-timeout=0"); | ||||
|         command.add("--optimize"); | ||||
|         command.add(String.valueOf(optimizeLevel)); | ||||
|         command.add("--output-type"); | ||||
|         command.add("pdf"); | ||||
| 
 | ||||
|         if (fastWebView != null && fastWebView) { | ||||
|             long fileSize = inputFile.getSize(); | ||||
|             long fastWebViewSize = (long) (fileSize * 1.25); // 25% higher than file size | ||||
|             command.add("--fast-web-view"); | ||||
|             command.add(String.valueOf(fastWebViewSize)); | ||||
|         } | ||||
| 
 | ||||
|         if (jbig2Lossy != null && jbig2Lossy) { | ||||
|             command.add("--jbig2-lossy"); | ||||
|         } | ||||
| 
 | ||||
|         command.add(tempInputFile.toString()); | ||||
|         command.add(tempOutputFile.toString()); | ||||
| 
 | ||||
|         int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); | ||||
| 
 | ||||
|         // Read the optimized PDF file | ||||
|         byte[] pdfBytes = Files.readAllBytes(tempOutputFile); | ||||
| 
 | ||||
|         // Clean up the temporary files | ||||
|         Files.delete(tempInputFile); | ||||
|         Files.delete(tempOutputFile); | ||||
| 
 | ||||
|         // Return the optimized PDF as a response | ||||
|         String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_Optimized.pdf"; | ||||
|         HttpHeaders headers = new HttpHeaders(); | ||||
|         headers.setContentType(MediaType.APPLICATION_PDF); | ||||
|         headers.setContentDispositionFormData("attachment", outputFilename); | ||||
|         return ResponseEntity.ok().headers(headers).body(pdfBytes); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| package stirling.software.SPDF.controller.api.other; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| import stirling.software.SPDF.utils.ProcessExecutor; | ||||
| 
 | ||||
| @RestController | ||||
| public class CompressController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(CompressController.class); | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/compress-pdf") | ||||
|     public ResponseEntity<byte[]> optimizePdf(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile, @RequestParam("optimizeLevel") int optimizeLevel, | ||||
|             @RequestParam(name = "fastWebView", required = false) Boolean fastWebView, @RequestParam(name = "jbig2Lossy", required = false) Boolean jbig2Lossy) | ||||
|             throws IOException, InterruptedException { | ||||
| 
 | ||||
|         // Save the uploaded file to a temporary location | ||||
|         Path tempInputFile = Files.createTempFile("input_", ".pdf"); | ||||
|         inputFile.transferTo(tempInputFile.toFile()); | ||||
| 
 | ||||
|         // Prepare the output file path | ||||
|         Path tempOutputFile = Files.createTempFile("output_", ".pdf"); | ||||
| 
 | ||||
|         // Prepare the OCRmyPDF command | ||||
|         List<String> command = new ArrayList<>(); | ||||
|         command.add("ocrmypdf"); | ||||
|         command.add("--skip-text"); | ||||
|         command.add("--tesseract-timeout=0"); | ||||
|         command.add("--optimize"); | ||||
|         command.add(String.valueOf(optimizeLevel)); | ||||
|         command.add("--output-type"); | ||||
|         command.add("pdf"); | ||||
| 
 | ||||
|         if (fastWebView != null && fastWebView) { | ||||
|             long fileSize = inputFile.getSize(); | ||||
|             long fastWebViewSize = (long) (fileSize * 1.25); // 25% higher than file size | ||||
|             command.add("--fast-web-view"); | ||||
|             command.add(String.valueOf(fastWebViewSize)); | ||||
|         } | ||||
| 
 | ||||
|         if (jbig2Lossy != null && jbig2Lossy) { | ||||
|             command.add("--jbig2-lossy"); | ||||
|         } | ||||
| 
 | ||||
|         command.add(tempInputFile.toString()); | ||||
|         command.add(tempOutputFile.toString()); | ||||
| 
 | ||||
|         int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); | ||||
| 
 | ||||
|         // Read the optimized PDF file | ||||
|         byte[] pdfBytes = Files.readAllBytes(tempOutputFile); | ||||
| 
 | ||||
|         // Clean up the temporary files | ||||
|         Files.delete(tempInputFile); | ||||
|         Files.delete(tempOutputFile); | ||||
| 
 | ||||
|         // Return the optimized PDF as a response | ||||
|         String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_Optimized.pdf"; | ||||
|         return PdfUtils.bytesToWebResponse(pdfBytes, outputFilename); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,130 @@ | ||||
| package stirling.software.SPDF.controller.api.other; | ||||
| 
 | ||||
| import java.awt.image.BufferedImage; | ||||
| import java.io.ByteArrayInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.StandardCopyOption; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| import java.util.zip.ZipEntry; | ||||
| import java.util.zip.ZipOutputStream; | ||||
| 
 | ||||
| import javax.imageio.ImageIO; | ||||
| 
 | ||||
| import org.apache.commons.io.FileUtils; | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.rendering.PDFRenderer; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| import stirling.software.SPDF.utils.ProcessExecutor; | ||||
| 
 | ||||
| @RestController | ||||
| public class ExtractImageScansController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class); | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans") | ||||
|     public ResponseEntity<byte[]> extractImageScans(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile, | ||||
|             @RequestParam(name = "angle_threshold", defaultValue = "5") int angleThreshold, @RequestParam(name = "tolerance", defaultValue = "20") int tolerance, | ||||
|             @RequestParam(name = "min_area", defaultValue = "8000") int minArea, @RequestParam(name = "min_contour_area", defaultValue = "500") int minContourArea, | ||||
|             @RequestParam(name = "border_size", defaultValue = "1") int borderSize) throws IOException, InterruptedException { | ||||
| 
 | ||||
|         String fileName = inputFile.getOriginalFilename(); | ||||
|         String extension = fileName.substring(fileName.lastIndexOf(".") + 1); | ||||
| 
 | ||||
|         List<String> images = new ArrayList<>(); | ||||
| 
 | ||||
|         // Check if input file is a PDF | ||||
|         if (extension.equalsIgnoreCase("pdf")) { | ||||
|             // Load PDF document | ||||
|             try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputFile.getBytes()))) { | ||||
|                 PDFRenderer pdfRenderer = new PDFRenderer(document); | ||||
|                 int pageCount = document.getNumberOfPages(); | ||||
|                 images = new ArrayList<>(); | ||||
| 
 | ||||
|                 // Create images of all pages | ||||
|                 for (int i = 0; i < pageCount; i++) { | ||||
|                     // Create temp file to save the image | ||||
|                     Path tempFile = Files.createTempFile("image_", ".png"); | ||||
| 
 | ||||
|                     // Render image and save as temp file | ||||
|                     BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300); | ||||
|                     ImageIO.write(image, "png", tempFile.toFile()); | ||||
| 
 | ||||
|                     // Add temp file path to images list | ||||
|                     images.add(tempFile.toString()); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             Path tempInputFile = Files.createTempFile("input_", "." + extension); | ||||
|             Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); | ||||
|             // Add input file path to images list | ||||
|             images.add(tempInputFile.toString()); | ||||
|         } | ||||
| 
 | ||||
|         List<byte[]> processedImageBytes = new ArrayList<>(); | ||||
| 
 | ||||
|         // Process each image | ||||
|         for (int i = 0; i < images.size(); i++) { | ||||
| 
 | ||||
|             Path tempDir = Files.createTempDirectory("openCV_output"); | ||||
|             List<String> command = new ArrayList<>(Arrays.asList("python3", "/scripts/split_photos.py", images.get(i), tempDir.toString(), String.valueOf(angleThreshold), | ||||
|                     String.valueOf(tolerance), String.valueOf(minArea), String.valueOf(minContourArea), String.valueOf(borderSize))); | ||||
| 
 | ||||
|             // Run CLI command | ||||
|             int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command); | ||||
| 
 | ||||
|             // Read the output photos in temp directory | ||||
|             List<Path> tempOutputFiles = Files.list(tempDir).sorted().collect(Collectors.toList()); | ||||
|             for (Path tempOutputFile : tempOutputFiles) { | ||||
|                 byte[] imageBytes = Files.readAllBytes(tempOutputFile); | ||||
|                 processedImageBytes.add(imageBytes); | ||||
|             } | ||||
|             // Clean up the temporary directory | ||||
|             FileUtils.deleteDirectory(tempDir.toFile()); | ||||
|         } | ||||
| 
 | ||||
|         // Create zip file if multiple images | ||||
|         if (processedImageBytes.size() > 1) { | ||||
|             String outputZipFilename = fileName.replaceFirst("[.][^.]+$", "") + "_processed.zip"; | ||||
|             Path tempZipFile = Files.createTempFile("output_", ".zip"); | ||||
| 
 | ||||
|             try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { | ||||
|                 // Add processed images to the zip | ||||
|                 for (int i = 0; i < processedImageBytes.size(); i++) { | ||||
|                     ZipEntry entry = new ZipEntry(fileName.replaceFirst("[.][^.]+$", "") + "_" + (i + 1) + ".png"); | ||||
|                     zipOut.putNextEntry(entry); | ||||
|                     zipOut.write(processedImageBytes.get(i)); | ||||
|                     zipOut.closeEntry(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             byte[] zipBytes = Files.readAllBytes(tempZipFile); | ||||
| 
 | ||||
|             // Clean up the temporary zip file | ||||
|             Files.delete(tempZipFile); | ||||
| 
 | ||||
|             return PdfUtils.bytesToWebResponse(zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); | ||||
|         } else { | ||||
|             // Return the processed image as a response | ||||
|             byte[] imageBytes = processedImageBytes.get(0); | ||||
|             return PdfUtils.bytesToWebResponse(imageBytes, fileName.replaceFirst("[.][^.]+$", "") + ".png", MediaType.IMAGE_PNG); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package stirling.software.SPDF.controller.other; | ||||
| package stirling.software.SPDF.controller.api.other; | ||||
| 
 | ||||
| import java.awt.Graphics2D; | ||||
| import java.awt.Image; | ||||
| @ -18,26 +18,23 @@ import org.apache.pdfbox.pdmodel.PDPage; | ||||
| import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; | ||||
| 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.HttpStatus; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| @Controller | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @RestController | ||||
| public class ExtractImagesController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class); | ||||
| 
 | ||||
|     @PostMapping("/extract-images") | ||||
|     public ResponseEntity<Resource> extractImages(@RequestParam("fileInput") MultipartFile file, @RequestParam("format") String format) throws IOException { | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/extract-images") | ||||
|     public ResponseEntity<byte[]> extractImages(@RequestPart(required = true, value = "fileInput") MultipartFile file, @RequestParam("format") String format) throws IOException { | ||||
| 
 | ||||
|         System.out.println(System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format); | ||||
|         PDDocument document = PDDocument.load(file.getBytes()); | ||||
| @ -98,24 +95,8 @@ public class ExtractImagesController { | ||||
| 
 | ||||
|         // Create ByteArrayResource from byte array | ||||
|         byte[] zipContents = baos.toByteArray(); | ||||
|         ByteArrayResource resource = new ByteArrayResource(zipContents); | ||||
| 
 | ||||
|         // Set content disposition header to indicate that the response should be | ||||
|         // downloaded as a file | ||||
|         HttpHeaders headers = new HttpHeaders(); | ||||
|         headers.setContentLength(zipContents.length); | ||||
|         headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted-images.zip"); | ||||
| 
 | ||||
|         // Return ResponseEntity with ByteArrayResource and headers | ||||
|         return ResponseEntity.status(HttpStatus.OK).headers(headers) | ||||
| 
 | ||||
|                 .header("Cache-Control", "no-cache").contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/extract-images") | ||||
|     public String extractImagesForm(Model model) { | ||||
|         model.addAttribute("currentPage", "extract-images"); | ||||
|         return "other/extract-images"; | ||||
|         return PdfUtils.boasToWebResponse(baos, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted-images.zip", MediaType.APPLICATION_OCTET_STREAM); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package stirling.software.SPDF.controller.other; | ||||
| package stirling.software.SPDF.controller.api.other; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.text.ParseException; | ||||
| @ -11,23 +11,17 @@ import org.apache.pdfbox.cos.COSName; | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.PDDocumentInformation; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @Controller | ||||
| @RestController | ||||
| public class MetadataController { | ||||
| 
 | ||||
|     @GetMapping("/change-metadata") | ||||
|     public String addWatermarkForm(Model model) { | ||||
|         model.addAttribute("currentPage", "change-metadata"); | ||||
|         return "other/change-metadata"; | ||||
|     } | ||||
| 
 | ||||
|     private String checkUndefined(String entry) { | ||||
|         // Check if the string is "undefined" | ||||
| @ -40,8 +34,8 @@ public class MetadataController { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/update-metadata") | ||||
|     public ResponseEntity<byte[]> metadata(@RequestParam("fileInput") MultipartFile pdfFile, | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/update-metadata") | ||||
|     public ResponseEntity<byte[]> metadata(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, | ||||
|             @RequestParam(value = "deleteAll", required = false, defaultValue = "false") Boolean deleteAll, @RequestParam(value = "author", required = false) String author, | ||||
|             @RequestParam(value = "creationDate", required = false) String creationDate, @RequestParam(value = "creator", required = false) String creator, | ||||
|             @RequestParam(value = "keywords", required = false) String keywords, @RequestParam(value = "modificationDate", required = false) String modificationDate, | ||||
| @ -1,4 +1,4 @@ | ||||
| package stirling.software.SPDF.controller.other; | ||||
| package stirling.software.SPDF.controller.api.other; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.FileOutputStream; | ||||
| @ -10,26 +10,24 @@ import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.regex.Pattern; | ||||
| import java.util.stream.Collectors; | ||||
| import java.util.zip.ZipEntry; | ||||
| import java.util.zip.ZipOutputStream; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| 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.multipart.MultipartFile; | ||||
| import org.springframework.web.servlet.ModelAndView; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| import stirling.software.SPDF.utils.ProcessExecutor; | ||||
| 
 | ||||
| @Controller | ||||
| @RestController | ||||
| public class OCRController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(OCRController.class); | ||||
| @ -44,28 +42,29 @@ public class OCRController { | ||||
|                 .filter(lang -> !lang.equalsIgnoreCase("osd")).collect(Collectors.toList()); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/ocr-pdf") | ||||
|     public ModelAndView ocrPdfPage() { | ||||
|         ModelAndView modelAndView = new ModelAndView("other/ocr-pdf"); | ||||
|         modelAndView.addObject("languages", getAvailableTesseractLanguages()); | ||||
|         modelAndView.addObject("currentPage", "ocr-pdf"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/ocr-pdf") | ||||
|     public ResponseEntity<byte[]> processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile, @RequestParam("languages") List<String> selectedLanguages, | ||||
|             @RequestParam(name = "sidecar", required = false) Boolean sidecar, @RequestParam(name = "deskew", required = false) Boolean deskew, | ||||
|             @RequestParam(name = "clean", required = false) Boolean clean, @RequestParam(name = "clean-final", required = false) Boolean cleanFinal, | ||||
|             @RequestParam(name = "ocrType", required = false) String ocrType) throws IOException, InterruptedException { | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf") | ||||
|     public ResponseEntity<byte[]> processPdfWithOCR(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile, | ||||
|             @RequestParam("languages") List<String> selectedLanguages, @RequestParam(name = "sidecar", required = false) Boolean sidecar, | ||||
|             @RequestParam(name = "deskew", required = false) Boolean deskew, @RequestParam(name = "clean", required = false) Boolean clean, | ||||
|             @RequestParam(name = "clean-final", required = false) Boolean cleanFinal, @RequestParam(name = "ocrType", required = false) String ocrType, | ||||
|             @RequestParam(name = "ocrRenderType", required = false, defaultValue = "hocr") String ocrRenderType, | ||||
|             @RequestParam(name = "removeImagesAfter", required = false) Boolean removeImagesAfter) | ||||
|             throws IOException, InterruptedException { | ||||
| 
 | ||||
|         // --output-type pdfa | ||||
|         if (selectedLanguages == null || selectedLanguages.size() < 1) { | ||||
|         if (selectedLanguages == null || selectedLanguages.isEmpty()) { | ||||
|             throw new IOException("Please select at least one language."); | ||||
|         } | ||||
|          | ||||
|         if(!ocrRenderType.equals("hocr") && !ocrRenderType.equals("sandwich")) { | ||||
|             throw new IOException("ocrRenderType wrong"); | ||||
|         } | ||||
|          | ||||
|         // Get available Tesseract languages | ||||
|         List<String> availableLanguages = getAvailableTesseractLanguages(); | ||||
| 
 | ||||
|         // Validate and sanitize selected languages using regex | ||||
|         String languagePattern = "^[a-zA-Z]{3}$"; // Regex pattern for three-letter language codes | ||||
|         selectedLanguages = selectedLanguages.stream().filter(lang -> Pattern.matches(languagePattern, lang)).collect(Collectors.toList()); | ||||
|         // Validate selected languages | ||||
|         selectedLanguages = selectedLanguages.stream().filter(availableLanguages::contains).toList(); | ||||
| 
 | ||||
|         if (selectedLanguages.isEmpty()) { | ||||
|             throw new IOException("None of the selected languages are valid."); | ||||
| @ -83,7 +82,8 @@ public class OCRController { | ||||
|         // Run OCR Command | ||||
|         String languageOption = String.join("+", selectedLanguages); | ||||
| 
 | ||||
|         List<String> command = new ArrayList<>(Arrays.asList("ocrmypdf", "--verbose", "2", "--output-type", "pdf")); | ||||
|          | ||||
|         List<String> command = new ArrayList<>(Arrays.asList("ocrmypdf", "--verbose", "2", "--output-type", "pdf", "--pdf-renderer" , ocrRenderType)); | ||||
| 
 | ||||
|         if (sidecar != null && sidecar) { | ||||
|             sidecarTextPath = Files.createTempFile("sidecar", ".txt"); | ||||
| @ -115,16 +115,27 @@ public class OCRController { | ||||
|         // Run CLI command | ||||
|         int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
|          | ||||
|          | ||||
|         // Remove images from the OCR processed PDF if the flag is set to true | ||||
|         if (removeImagesAfter != null && removeImagesAfter) { | ||||
|             Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf"); | ||||
| 
 | ||||
|             List<String> gsCommand = Arrays.asList("gs", "-sDEVICE=pdfwrite", "-dFILTERIMAGE", "-o", tempPdfWithoutImages.toString(), tempOutputFile.toString()); | ||||
| 
 | ||||
|             int gsReturnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(gsCommand); | ||||
|             tempOutputFile = tempPdfWithoutImages; | ||||
|         } | ||||
|         // Read the OCR processed PDF file | ||||
|         byte[] pdfBytes = Files.readAllBytes(tempOutputFile); | ||||
| 
 | ||||
|         // Clean up the temporary files | ||||
|         Files.delete(tempInputFile); | ||||
|          | ||||
|         // Return the OCR processed PDF as a response | ||||
|         String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf"; | ||||
| 
 | ||||
|         HttpHeaders headers = new HttpHeaders(); | ||||
| 
 | ||||
|         if (sidecar != null && sidecar) { | ||||
|             // Create a zip file containing both the PDF and the text file | ||||
|             String outputZipFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip"; | ||||
| @ -152,15 +163,11 @@ public class OCRController { | ||||
|             Files.delete(sidecarTextPath); | ||||
| 
 | ||||
|             // Return the zip file containing both the PDF and the text file | ||||
|             headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); | ||||
|             headers.setContentDispositionFormData("attachment", outputZipFilename); | ||||
|             return ResponseEntity.ok().headers(headers).body(zipBytes); | ||||
|             return PdfUtils.bytesToWebResponse(pdfBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); | ||||
|         } else { | ||||
|             // Return the OCR processed PDF as a response | ||||
|             Files.delete(tempOutputFile); | ||||
|             headers.setContentType(MediaType.APPLICATION_PDF); | ||||
|             headers.setContentDispositionFormData("attachment", outputFilename); | ||||
|             return ResponseEntity.ok().headers(headers).body(pdfBytes); | ||||
|             return PdfUtils.bytesToWebResponse(pdfBytes, outputFilename); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| @ -1,43 +1,36 @@ | ||||
| package stirling.software.SPDF.controller.other; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @Controller | ||||
| public class OverlayImageController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class); | ||||
| 
 | ||||
|     @GetMapping("/add-image") | ||||
|     public String overlayImage(Model model) { | ||||
|         model.addAttribute("currentPage", "add-image"); | ||||
|         return "other/add-image"; | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/add-image") | ||||
|     public ResponseEntity<byte[]> overlayImage(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("fileInput2") MultipartFile imageFile, @RequestParam("x") float x, | ||||
|             @RequestParam("y") float y, @RequestParam("everyPage") boolean everyPage) { | ||||
|         try { | ||||
|             byte[] pdfBytes = pdfFile.getBytes(); | ||||
|             byte[] imageBytes = imageFile.getBytes(); | ||||
|             byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y, everyPage); | ||||
| 
 | ||||
|             return PdfUtils.bytesToWebResponse(result, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf"); | ||||
|         } catch (IOException e) { | ||||
|             logger.error("Failed to add image to PDF", e); | ||||
|             return new ResponseEntity<>(HttpStatus.BAD_REQUEST); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| package stirling.software.SPDF.controller.api.other; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @RestController | ||||
| public class OverlayImageController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class); | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/add-image") | ||||
|     public ResponseEntity<byte[]> overlayImage(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("fileInput2") MultipartFile imageFile, | ||||
|             @RequestParam("x") float x, @RequestParam("y") float y, @RequestParam("everyPage") boolean everyPage) { | ||||
|         try { | ||||
|             byte[] pdfBytes = pdfFile.getBytes(); | ||||
|             byte[] imageBytes = imageFile.getBytes(); | ||||
|             byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y, everyPage); | ||||
| 
 | ||||
|             return PdfUtils.bytesToWebResponse(result, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf"); | ||||
|         } catch (IOException e) { | ||||
|             logger.error("Failed to add image to PDF", e); | ||||
|             return new ResponseEntity<>(HttpStatus.BAD_REQUEST); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,81 +1,66 @@ | ||||
| package stirling.software.SPDF.controller.security; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.encryption.AccessPermission; | ||||
| import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @Controller | ||||
| public class PasswordController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(PasswordController.class); | ||||
| 
 | ||||
|     @GetMapping("/add-password") | ||||
|     public String addPasswordForm(Model model) { | ||||
|         model.addAttribute("currentPage", "add-password"); | ||||
|         return "security/add-password"; | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/remove-password") | ||||
|     public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(name = "password") String password) throws IOException { | ||||
|         PDDocument document = PDDocument.load(fileInput.getBytes(), password); | ||||
|         document.setAllSecurityToBeRemoved(true); | ||||
|         return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_password_removed.pdf"); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/add-password") | ||||
|     public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(defaultValue = "", name = "password") String password, | ||||
|             @RequestParam(defaultValue = "128", name = "keyLength") int keyLength, @RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument, | ||||
|             @RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent, | ||||
|             @RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility, | ||||
|             @RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canModify") boolean canModify, | ||||
|             @RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations, | ||||
|             @RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, @RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful) | ||||
|             throws IOException { | ||||
| 
 | ||||
|         PDDocument document = PDDocument.load(fileInput.getBytes()); | ||||
|         AccessPermission ap = new AccessPermission(); | ||||
| 
 | ||||
|         ap.setCanAssembleDocument(!canAssembleDocument); | ||||
|         ap.setCanExtractContent(!canExtractContent); | ||||
|         ap.setCanExtractForAccessibility(!canExtractForAccessibility); | ||||
|         ap.setCanFillInForm(!canFillInForm); | ||||
|         ap.setCanModify(!canModify); | ||||
|         ap.setCanModifyAnnotations(!canModifyAnnotations); | ||||
|         ap.setCanPrint(!canPrint); | ||||
|         ap.setCanPrintFaithful(!canPrintFaithful); | ||||
|         StandardProtectionPolicy spp = new StandardProtectionPolicy(password, password, ap); | ||||
|         spp.setEncryptionKeyLength(keyLength); | ||||
| 
 | ||||
|         spp.setPermissions(ap); | ||||
| 
 | ||||
|         document.protect(spp); | ||||
| 
 | ||||
|         return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf"); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/change-permissions") | ||||
|     public String permissionsForm(Model model) { | ||||
|         model.addAttribute("currentPage", "change-permissions"); | ||||
|         return "security/change-permissions"; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/remove-password") | ||||
|     public String removePasswordForm(Model model) { | ||||
|         model.addAttribute("currentPage", "remove-password"); | ||||
|         return "security/remove-password"; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| package stirling.software.SPDF.controller.api.security; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.encryption.AccessPermission; | ||||
| import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| 
 | ||||
| @RestController | ||||
| public class PasswordController { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(PasswordController.class); | ||||
| 
 | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/remove-password") | ||||
|     public ResponseEntity<byte[]> compressPDF(@RequestPart(required = true, value = "fileInput") MultipartFile fileInput, @RequestParam(name = "password") String password) | ||||
|             throws IOException { | ||||
|         PDDocument document = PDDocument.load(fileInput.getBytes(), password); | ||||
|         document.setAllSecurityToBeRemoved(true); | ||||
|         return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_password_removed.pdf"); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/add-password") | ||||
|     public ResponseEntity<byte[]> compressPDF(@RequestPart(required = true, value = "fileInput") MultipartFile fileInput, | ||||
|             @RequestParam(defaultValue = "", name = "password") String password, @RequestParam(defaultValue = "128", name = "keyLength") int keyLength, | ||||
|             @RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument, | ||||
|             @RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent, | ||||
|             @RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility, | ||||
|             @RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canModify") boolean canModify, | ||||
|             @RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations, | ||||
|             @RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, @RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful) | ||||
|             throws IOException { | ||||
| 
 | ||||
|         PDDocument document = PDDocument.load(fileInput.getBytes()); | ||||
|         AccessPermission ap = new AccessPermission(); | ||||
| 
 | ||||
|         ap.setCanAssembleDocument(!canAssembleDocument); | ||||
|         ap.setCanExtractContent(!canExtractContent); | ||||
|         ap.setCanExtractForAccessibility(!canExtractForAccessibility); | ||||
|         ap.setCanFillInForm(!canFillInForm); | ||||
|         ap.setCanModify(!canModify); | ||||
|         ap.setCanModifyAnnotations(!canModifyAnnotations); | ||||
|         ap.setCanPrint(!canPrint); | ||||
|         ap.setCanPrintFaithful(!canPrintFaithful); | ||||
|         StandardProtectionPolicy spp = new StandardProtectionPolicy(password, password, ap); | ||||
|         spp.setEncryptionKeyLength(keyLength); | ||||
| 
 | ||||
|         spp.setPermissions(ap); | ||||
| 
 | ||||
|         document.protect(spp); | ||||
| 
 | ||||
|         return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf"); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -1,150 +1,140 @@ | ||||
| package stirling.software.SPDF.controller.security; | ||||
| 
 | ||||
| import java.awt.Color; | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.PDDocumentCatalog; | ||||
| 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.PDType1Font; | ||||
| import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; | ||||
| import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; | ||||
| import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup; | ||||
| import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; | ||||
| import org.apache.pdfbox.pdmodel.interactive.form.PDField; | ||||
| import org.apache.pdfbox.util.Matrix; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| import stirling.software.SPDF.utils.WatermarkRemover; | ||||
| 
 | ||||
| @Controller | ||||
| public class WatermarkController { | ||||
| 
 | ||||
|     @PostMapping("/add-watermark") | ||||
|     public ResponseEntity<byte[]> addWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText, | ||||
|             @RequestParam(defaultValue = "30", name = "fontSize") float fontSize, @RequestParam(defaultValue = "0", name = "rotation") float rotation, | ||||
|             @RequestParam(defaultValue = "0.5", name = "opacity") float opacity, @RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer, | ||||
|             @RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer) throws IOException { | ||||
| 
 | ||||
|         // Load the input PDF | ||||
|         PDDocument document = PDDocument.load(pdfFile.getInputStream()); | ||||
| 
 | ||||
|         // Create a page in the document | ||||
|         for (PDPage page : document.getPages()) { | ||||
| 
 | ||||
|             // Get the page's content stream | ||||
|             PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true); | ||||
| 
 | ||||
|             // Set transparency | ||||
|             PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState(); | ||||
|             graphicsState.setNonStrokingAlphaConstant(opacity); | ||||
|             contentStream.setGraphicsStateParameters(graphicsState); | ||||
| 
 | ||||
|             // Set font of watermark | ||||
|             PDFont font = PDType1Font.HELVETICA_BOLD; | ||||
|             contentStream.beginText(); | ||||
|             contentStream.setFont(font, fontSize); | ||||
|             contentStream.setNonStrokingColor(Color.LIGHT_GRAY); | ||||
| 
 | ||||
|             // Set size and location of watermark | ||||
|             float pageWidth = page.getMediaBox().getWidth(); | ||||
|             float pageHeight = page.getMediaBox().getHeight(); | ||||
|             float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000; | ||||
|             float watermarkHeight = heightSpacer + fontSize; | ||||
|             int watermarkRows = (int) (pageHeight / watermarkHeight + 1); | ||||
|             int watermarkCols = (int) (pageWidth / watermarkWidth + 1); | ||||
| 
 | ||||
|             // 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)); | ||||
|                     contentStream.showTextWithPositioning(new Object[] { watermarkText }); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             contentStream.endText(); | ||||
| 
 | ||||
|             // Close the content stream | ||||
|             contentStream.close(); | ||||
|         } | ||||
|         return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf"); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/add-watermark") | ||||
|     public String addWatermarkForm(Model model) { | ||||
|         model.addAttribute("currentPage", "add-watermark"); | ||||
|         return "security/add-watermark"; | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/remove-watermark") | ||||
|     public ResponseEntity<byte[]> removeWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText) throws Exception { | ||||
| 
 | ||||
|         // Load the input PDF | ||||
|         PDDocument document = PDDocument.load(pdfFile.getInputStream()); | ||||
| 
 | ||||
|         // Create a new PDF document for the output | ||||
|         PDDocument outputDocument = new PDDocument(); | ||||
| 
 | ||||
|         // Loop through the pages | ||||
|         int numPages = document.getNumberOfPages(); | ||||
|         for (int i = 0; i < numPages; i++) { | ||||
|             PDPage page = document.getPage(i); | ||||
| 
 | ||||
|             // Process the content stream to remove the watermark text | ||||
|             WatermarkRemover editor = new WatermarkRemover(watermarkText) { | ||||
|             }; | ||||
|             editor.processPage(page); | ||||
|             editor.processPage(page); | ||||
|             // Add the page to the output document | ||||
|             outputDocument.addPage(page); | ||||
|         } | ||||
| 
 | ||||
|         for (PDPage page : outputDocument.getPages()) { | ||||
|             List<PDAnnotation> annotations = page.getAnnotations(); | ||||
|             List<PDAnnotation> annotationsToRemove = new ArrayList<>(); | ||||
| 
 | ||||
|             for (PDAnnotation annotation : annotations) { | ||||
|                 if (annotation instanceof PDAnnotationMarkup) { | ||||
|                     PDAnnotationMarkup markup = (PDAnnotationMarkup) annotation; | ||||
|                     String contents = markup.getContents(); | ||||
|                     if (contents != null && contents.contains(watermarkText)) { | ||||
|                         annotationsToRemove.add(markup); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             annotations.removeAll(annotationsToRemove); | ||||
|         } | ||||
|         PDDocumentCatalog catalog = outputDocument.getDocumentCatalog(); | ||||
|         PDAcroForm acroForm = catalog.getAcroForm(); | ||||
|         if (acroForm != null) { | ||||
|             List<PDField> fields = acroForm.getFields(); | ||||
|             for (PDField field : fields) { | ||||
|                 String fieldValue = field.getValueAsString(); | ||||
|                 if (fieldValue.contains(watermarkText)) { | ||||
|                     field.setValue(fieldValue.replace(watermarkText, "")); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return PdfUtils.pdfDocToWebResponse(outputDocument, "removed.pdf"); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/remove-watermark") | ||||
|     public String removeWatermarkForm(Model model) { | ||||
|         model.addAttribute("currentPage", "remove-watermark"); | ||||
|         return "security/remove-watermark"; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| package stirling.software.SPDF.controller.api.security; | ||||
| 
 | ||||
| import java.awt.Color; | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.PDDocumentCatalog; | ||||
| 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.PDType1Font; | ||||
| import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; | ||||
| import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; | ||||
| import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup; | ||||
| import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; | ||||
| import org.apache.pdfbox.pdmodel.interactive.form.PDField; | ||||
| import org.apache.pdfbox.util.Matrix; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| 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.multipart.MultipartFile; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PdfUtils; | ||||
| import stirling.software.SPDF.utils.WatermarkRemover; | ||||
| 
 | ||||
| @RestController | ||||
| public class WatermarkController { | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/add-watermark") | ||||
|     public ResponseEntity<byte[]> addWatermark(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText, | ||||
|             @RequestParam(defaultValue = "30", name = "fontSize") float fontSize, @RequestParam(defaultValue = "0", name = "rotation") float rotation, | ||||
|             @RequestParam(defaultValue = "0.5", name = "opacity") float opacity, @RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer, | ||||
|             @RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer) throws IOException { | ||||
| 
 | ||||
|         // Load the input PDF | ||||
|         PDDocument document = PDDocument.load(pdfFile.getInputStream()); | ||||
| 
 | ||||
|         // Create a page in the document | ||||
|         for (PDPage page : document.getPages()) { | ||||
| 
 | ||||
|             // Get the page's content stream | ||||
|             PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true); | ||||
| 
 | ||||
|             // Set transparency | ||||
|             PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState(); | ||||
|             graphicsState.setNonStrokingAlphaConstant(opacity); | ||||
|             contentStream.setGraphicsStateParameters(graphicsState); | ||||
| 
 | ||||
|             // Set font of watermark | ||||
|             PDFont font = PDType1Font.HELVETICA_BOLD; | ||||
|             contentStream.beginText(); | ||||
|             contentStream.setFont(font, fontSize); | ||||
|             contentStream.setNonStrokingColor(Color.LIGHT_GRAY); | ||||
| 
 | ||||
|             // Set size and location of watermark | ||||
|             float pageWidth = page.getMediaBox().getWidth(); | ||||
|             float pageHeight = page.getMediaBox().getHeight(); | ||||
|             float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000; | ||||
|             float watermarkHeight = heightSpacer + fontSize; | ||||
|             int watermarkRows = (int) (pageHeight / watermarkHeight + 1); | ||||
|             int watermarkCols = (int) (pageWidth / watermarkWidth + 1); | ||||
| 
 | ||||
|             // 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)); | ||||
|                     contentStream.showTextWithPositioning(new Object[] { watermarkText }); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             contentStream.endText(); | ||||
| 
 | ||||
|             // Close the content stream | ||||
|             contentStream.close(); | ||||
|         } | ||||
|         return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf"); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/remove-watermark") | ||||
|     public ResponseEntity<byte[]> removeWatermark(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText) | ||||
|             throws Exception { | ||||
| 
 | ||||
|         // Load the input PDF | ||||
|         PDDocument document = PDDocument.load(pdfFile.getInputStream()); | ||||
| 
 | ||||
|         // Create a new PDF document for the output | ||||
|         PDDocument outputDocument = new PDDocument(); | ||||
| 
 | ||||
|         // Loop through the pages | ||||
|         int numPages = document.getNumberOfPages(); | ||||
|         for (int i = 0; i < numPages; i++) { | ||||
|             PDPage page = document.getPage(i); | ||||
| 
 | ||||
|             // Process the content stream to remove the watermark text | ||||
|             WatermarkRemover editor = new WatermarkRemover(watermarkText) { | ||||
|             }; | ||||
|             editor.processPage(page); | ||||
|             editor.processPage(page); | ||||
|             // Add the page to the output document | ||||
|             outputDocument.addPage(page); | ||||
|         } | ||||
| 
 | ||||
|         for (PDPage page : outputDocument.getPages()) { | ||||
|             List<PDAnnotation> annotations = page.getAnnotations(); | ||||
|             List<PDAnnotation> annotationsToRemove = new ArrayList<>(); | ||||
| 
 | ||||
|             for (PDAnnotation annotation : annotations) { | ||||
|                 if (annotation instanceof PDAnnotationMarkup) { | ||||
|                     PDAnnotationMarkup markup = (PDAnnotationMarkup) annotation; | ||||
|                     String contents = markup.getContents(); | ||||
|                     if (contents != null && contents.contains(watermarkText)) { | ||||
|                         annotationsToRemove.add(markup); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             annotations.removeAll(annotationsToRemove); | ||||
|         } | ||||
|         PDDocumentCatalog catalog = outputDocument.getDocumentCatalog(); | ||||
|         PDAcroForm acroForm = catalog.getAcroForm(); | ||||
|         if (acroForm != null) { | ||||
|             List<PDField> fields = acroForm.getFields(); | ||||
|             for (PDField field : fields) { | ||||
|                 String fieldValue = field.getValueAsString(); | ||||
|                 if (fieldValue.contains(watermarkText)) { | ||||
|                     field.setValue(fieldValue.replace(watermarkText, "")); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return PdfUtils.pdfDocToWebResponse(outputDocument, "removed.pdf"); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -1,86 +0,0 @@ | ||||
| package stirling.software.SPDF.controller.converters; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| import org.springframework.web.servlet.ModelAndView; | ||||
| 
 | ||||
| import stirling.software.SPDF.utils.PDFToFile; | ||||
| 
 | ||||
| @Controller | ||||
| public class ConvertPDFToOffice { | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-html") | ||||
|     public ModelAndView pdfToHTML() { | ||||
|         ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html"); | ||||
|         modelAndView.addObject("currentPage", "pdf-to-html"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-presentation") | ||||
|     public ModelAndView pdfToPresentation() { | ||||
|         ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation"); | ||||
|         modelAndView.addObject("currentPage", "pdf-to-presentation"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-text") | ||||
|     public ModelAndView pdfToText() { | ||||
|         ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text"); | ||||
|         modelAndView.addObject("currentPage", "pdf-to-text"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-word") | ||||
|     public ModelAndView pdfToWord() { | ||||
|         ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word"); | ||||
|         modelAndView.addObject("currentPage", "pdf-to-word"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-xml") | ||||
|     public ModelAndView pdfToXML() { | ||||
|         ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml"); | ||||
|         modelAndView.addObject("currentPage", "pdf-to-xml"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/pdf-to-html") | ||||
|     public ResponseEntity<byte[]> processPdfToHTML(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException { | ||||
|         PDFToFile pdfToFile = new PDFToFile(); | ||||
|         return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import"); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/pdf-to-presentation") | ||||
|     public ResponseEntity<byte[]> processPdfToPresentation(@RequestParam("fileInput") MultipartFile inputFile, @RequestParam("outputFormat") String outputFormat) | ||||
|             throws IOException, InterruptedException { | ||||
|         PDFToFile pdfToFile = new PDFToFile(); | ||||
|         return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import"); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/pdf-to-text") | ||||
|     public ResponseEntity<byte[]> processPdfToRTForTXT(@RequestParam("fileInput") MultipartFile inputFile, @RequestParam("outputFormat") String outputFormat) | ||||
|             throws IOException, InterruptedException { | ||||
|         PDFToFile pdfToFile = new PDFToFile(); | ||||
|         return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/pdf-to-word") | ||||
|     public ResponseEntity<byte[]> processPdfToWord(@RequestParam("fileInput") MultipartFile inputFile, @RequestParam("outputFormat") String outputFormat) | ||||
|             throws IOException, InterruptedException { | ||||
|         PDFToFile pdfToFile = new PDFToFile(); | ||||
|         return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("/pdf-to-xml") | ||||
|     public ResponseEntity<byte[]> processPdfToXML(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException { | ||||
|         PDFToFile pdfToFile = new PDFToFile(); | ||||
|         return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,86 @@ | ||||
| package stirling.software.SPDF.controller.web; | ||||
| 
 | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.servlet.ModelAndView; | ||||
| 
 | ||||
| import io.swagger.v3.oas.annotations.Hidden; | ||||
| 
 | ||||
| @Controller | ||||
| public class ConverterWebController { | ||||
| 
 | ||||
|     @GetMapping("/img-to-pdf") | ||||
|     @Hidden | ||||
|     public String convertImgToPdfForm(Model model) { | ||||
|         model.addAttribute("currentPage", "img-to-pdf"); | ||||
|         return "convert/img-to-pdf"; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-img") | ||||
|     @Hidden | ||||
|     public String pdfToimgForm(Model model) { | ||||
|         model.addAttribute("currentPage", "pdf-to-img"); | ||||
|         return "convert/pdf-to-img"; | ||||
|     } | ||||
|      | ||||
|     @GetMapping("/file-to-pdf") | ||||
|     @Hidden | ||||
|     public String convertToPdfForm(Model model) { | ||||
|         model.addAttribute("currentPage", "file-to-pdf"); | ||||
|         return "convert/file-to-pdf"; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|      | ||||
|     //PDF TO...... | ||||
|      | ||||
|     @GetMapping("/pdf-to-html") | ||||
|     @Hidden | ||||
|     public ModelAndView pdfToHTML() { | ||||
|         ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html"); | ||||
|         modelAndView.addObject("currentPage", "pdf-to-html"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-presentation") | ||||
|     @Hidden | ||||
|     public ModelAndView pdfToPresentation() { | ||||
|         ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation"); | ||||
|         modelAndView.addObject("currentPage", "pdf-to-presentation"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-text") | ||||
|     @Hidden | ||||
|     public ModelAndView pdfToText() { | ||||
|         ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text"); | ||||
|         modelAndView.addObject("currentPage", "pdf-to-text"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-word") | ||||
|     @Hidden | ||||
|     public ModelAndView pdfToWord() { | ||||
|         ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word"); | ||||
|         modelAndView.addObject("currentPage", "pdf-to-word"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-xml") | ||||
|     @Hidden | ||||
|     public ModelAndView pdfToXML() { | ||||
|         ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml"); | ||||
|         modelAndView.addObject("currentPage", "pdf-to-xml"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @GetMapping("/pdf-to-pdfa") | ||||
|     @Hidden | ||||
|     public String pdfToPdfAForm(Model model) { | ||||
|         model.addAttribute("currentPage", "pdf-to-pdfa"); | ||||
|         return "convert/pdf-to-pdfa"; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,69 @@ | ||||
| package stirling.software.SPDF.controller.web; | ||||
| 
 | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| 
 | ||||
| import io.swagger.v3.oas.annotations.Hidden; | ||||
| 
 | ||||
| @Controller | ||||
| public class GeneralWebController { | ||||
|     @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 | ||||
|     public String multiToolForm(Model model) { | ||||
|         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 | ||||
|     public String pageDeleter(Model model) { | ||||
|         model.addAttribute("currentPage", "remove-pages"); | ||||
|         return "remove-pages"; | ||||
|     } | ||||
|      | ||||
|     @GetMapping("/pdf-organizer") | ||||
|     @Hidden | ||||
|     public String pageOrganizer(Model model) { | ||||
|         model.addAttribute("currentPage", "pdf-organizer"); | ||||
|         return "pdf-organizer"; | ||||
|     } | ||||
|      | ||||
|     @GetMapping("/rotate-pdf") | ||||
|     @Hidden | ||||
|     public String rotatePdfForm(Model model) { | ||||
|         model.addAttribute("currentPage", "rotate-pdf"); | ||||
|         return "rotate-pdf"; | ||||
|     } | ||||
|      | ||||
|     @GetMapping("/split-pdfs") | ||||
|     @Hidden | ||||
|     public String splitPdfForm(Model model) { | ||||
|         model.addAttribute("currentPage", "split-pdfs"); | ||||
|         return "split-pdfs"; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,84 @@ | ||||
| package stirling.software.SPDF.controller.web; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.servlet.ModelAndView; | ||||
| 
 | ||||
| import io.swagger.v3.oas.annotations.Hidden; | ||||
| 
 | ||||
| @Controller | ||||
| public class OtherWebController { | ||||
|     @GetMapping("/compress-pdf") | ||||
|     @Hidden | ||||
|     public String compressPdfForm(Model model) { | ||||
|         model.addAttribute("currentPage", "compress-pdf"); | ||||
|         return "other/compress-pdf"; | ||||
|     } | ||||
|      | ||||
|     @GetMapping("/extract-image-scans") | ||||
|     @Hidden | ||||
|     public ModelAndView extractImageScansForm() { | ||||
|         ModelAndView modelAndView = new ModelAndView("other/extract-image-scans"); | ||||
|         modelAndView.addObject("currentPage", "extract-image-scans"); | ||||
|         return modelAndView; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/extract-images") | ||||
|     @Hidden | ||||
|     public String extractImagesForm(Model model) { | ||||
|         model.addAttribute("currentPage", "extract-images"); | ||||
|         return "other/extract-images"; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     @GetMapping("/change-metadata") | ||||
|     @Hidden | ||||
|     public String addWatermarkForm(Model model) { | ||||
|         model.addAttribute("currentPage", "change-metadata"); | ||||
|         return "other/change-metadata"; | ||||
|     } | ||||
|      | ||||
|      | ||||
|     public List<String> getAvailableTesseractLanguages() { | ||||
|         String tessdataDir = "/usr/share/tesseract-ocr/4.00/tessdata"; | ||||
|         File[] files = new File(tessdataDir).listFiles(); | ||||
|         if (files == null) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         return Arrays.stream(files).filter(file -> file.getName().endsWith(".traineddata")).map(file -> file.getName().replace(".traineddata", "")) | ||||
|                 .filter(lang -> !lang.equalsIgnoreCase("osd")).collect(Collectors.toList()); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/ocr-pdf") | ||||
|     @Hidden | ||||
|     public ModelAndView ocrPdfPage() { | ||||
|         ModelAndView modelAndView = new ModelAndView("other/ocr-pdf"); | ||||
|         modelAndView.addObject("languages", getAvailableTesseractLanguages()); | ||||
|         modelAndView.addObject("currentPage", "ocr-pdf"); | ||||
|         return modelAndView; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     @GetMapping("/add-image") | ||||
|     @Hidden | ||||
|     public String overlayImage(Model model) { | ||||
|         model.addAttribute("currentPage", "add-image"); | ||||
|         return "other/add-image"; | ||||
|     } | ||||
|      | ||||
|     @GetMapping("/adjust-contrast") | ||||
|     @Hidden | ||||
|     public String contrast(Model model) { | ||||
|         model.addAttribute("currentPage", "adjust-contrast"); | ||||
|         return "other/adjust-contrast"; | ||||
|     } | ||||
|      | ||||
|      | ||||
| } | ||||
| @ -0,0 +1,46 @@ | ||||
| package stirling.software.SPDF.controller.web; | ||||
| 
 | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| 
 | ||||
| import io.swagger.v3.oas.annotations.Hidden; | ||||
| 
 | ||||
| @Controller | ||||
| public class SecurityWebController { | ||||
|     @GetMapping("/add-password") | ||||
|     @Hidden | ||||
|     public String addPasswordForm(Model model) { | ||||
|         model.addAttribute("currentPage", "add-password"); | ||||
|         return "security/add-password"; | ||||
|     } | ||||
|     @GetMapping("/change-permissions") | ||||
|     @Hidden | ||||
|     public String permissionsForm(Model model) { | ||||
|         model.addAttribute("currentPage", "change-permissions"); | ||||
|         return "security/change-permissions"; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/remove-password") | ||||
|     @Hidden | ||||
|     public String removePasswordForm(Model model) { | ||||
|         model.addAttribute("currentPage", "remove-password"); | ||||
|         return "security/remove-password"; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/add-watermark") | ||||
|     @Hidden | ||||
|     public String addWatermarkForm(Model model) { | ||||
|         model.addAttribute("currentPage", "add-watermark"); | ||||
|         return "security/add-watermark"; | ||||
|     } | ||||
| 
 | ||||
|     //WIP | ||||
|     @GetMapping("/remove-watermark") | ||||
|     @Hidden | ||||
|     public String removeWatermarkForm(Model model) { | ||||
|         model.addAttribute("currentPage", "remove-watermark"); | ||||
|         return "security/remove-watermark"; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @ -15,7 +15,6 @@ import java.util.zip.ZipOutputStream; | ||||
| 
 | ||||
| import org.apache.commons.io.FileUtils; | ||||
| import org.apache.commons.io.IOUtils; | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| @ -41,8 +40,7 @@ public class PDFToFile { | ||||
|         Path tempInputFile = null; | ||||
|         Path tempOutputDir = null; | ||||
|         byte[] fileBytes; | ||||
|         // Prepare response | ||||
|         HttpHeaders headers = new HttpHeaders(); | ||||
|         String fileName = "temp.file"; | ||||
| 
 | ||||
|         try { | ||||
|             // Save the uploaded file to a temporary location | ||||
| @ -63,16 +61,14 @@ public class PDFToFile { | ||||
|             if (outputFiles.size() == 1) { | ||||
|                 // Return single output file | ||||
|                 File outputFile = outputFiles.get(0); | ||||
|                 headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); | ||||
|                 if (outputFormat.equals("txt:Text")) { | ||||
|                     outputFormat = "txt"; | ||||
|                 } | ||||
|                 headers.setContentDispositionFormData("attachment", pdfBaseName + "." + outputFormat); | ||||
|                 fileName = pdfBaseName + "." + outputFormat; | ||||
|                 fileBytes = FileUtils.readFileToByteArray(outputFile); | ||||
|             } else { | ||||
|                 // Return output files in a ZIP archive | ||||
|                 headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); | ||||
|                 headers.setContentDispositionFormData("attachment", pdfBaseName + "To" + outputFormat + ".zip"); | ||||
|                 fileName = pdfBaseName + "To" + outputFormat + ".zip"; | ||||
|                 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); | ||||
|                 ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream); | ||||
| 
 | ||||
| @ -96,6 +92,6 @@ public class PDFToFile { | ||||
|             if (tempOutputDir != null) | ||||
|                 FileUtils.deleteDirectory(tempOutputDir.toFile()); | ||||
|         } | ||||
|         return new ResponseEntity<>(fileBytes, headers, HttpStatus.OK); | ||||
|         return PdfUtils.bytesToWebResponse(fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -8,6 +8,8 @@ import java.io.File; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.net.URLEncoder; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Files; | ||||
| import java.security.KeyPair; | ||||
| import java.security.KeyStore; | ||||
| @ -43,19 +45,27 @@ public class PdfUtils { | ||||
| 
 | ||||
|     public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException { | ||||
|         return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) throws IOException { | ||||
|     public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException { | ||||
|         return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType); | ||||
|     } | ||||
| 
 | ||||
|     public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName, MediaType mediaType) throws IOException { | ||||
| 
 | ||||
|         // Return the PDF as a response | ||||
|         HttpHeaders headers = new HttpHeaders(); | ||||
|         headers.setContentType(MediaType.APPLICATION_PDF); | ||||
|         headers.setContentType(mediaType); | ||||
|         headers.setContentLength(bytes.length); | ||||
|         headers.setContentDispositionFormData("attachment", docName); | ||||
|         String encodedDocName = URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20"); | ||||
|         headers.setContentDispositionFormData("attachment", encodedDocName); | ||||
|         return new ResponseEntity<>(bytes, headers, HttpStatus.OK); | ||||
|     } | ||||
| 
 | ||||
|     public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) throws IOException { | ||||
|         return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF); | ||||
|     } | ||||
| 
 | ||||
|     public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI) throws IOException, Exception { | ||||
|         try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) { | ||||
|             PDFRenderer pdfRenderer = new PDFRenderer(document); | ||||
|  | ||||
| @ -13,7 +13,7 @@ import java.util.concurrent.Semaphore; | ||||
| public class ProcessExecutor { | ||||
| 
 | ||||
|     public enum Processes { | ||||
|         LIBRE_OFFICE, OCR_MY_PDF | ||||
|         LIBRE_OFFICE, OCR_MY_PDF, PYTHON_OPENCV, GHOSTSCRIPT | ||||
|     } | ||||
| 
 | ||||
|     private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>(); | ||||
| @ -23,6 +23,8 @@ public class ProcessExecutor { | ||||
|             int semaphoreLimit = switch (key) { | ||||
|             case LIBRE_OFFICE -> 1; | ||||
|             case OCR_MY_PDF -> 2; | ||||
|             case PYTHON_OPENCV -> 8; | ||||
|             case GHOSTSCRIPT -> 16; | ||||
|             }; | ||||
|             return new ProcessExecutor(semaphoreLimit); | ||||
|         }); | ||||
|  | ||||
| @ -31,10 +31,10 @@ navbar.convert=تحويل | ||||
| navbar.security=الأمان | ||||
| navbar.other=أخرى | ||||
| navbar.darkmode=الوضع الداكن | ||||
| navbar.pageOps = عمليات الصفحة | ||||
| navbar.pageOps=عمليات الصفحة | ||||
| 
 | ||||
| home.multiTool.title = أداة متعددة PDF | ||||
| home.multiTool.desc = دمج الصفحات وتدويرها وإعادة ترتيبها وإزالتها | ||||
| home.multiTool.title=أداة متعددة PDF | ||||
| home.multiTool.desc=دمج الصفحات وتدويرها وإعادة ترتيبها وإزالتها | ||||
| 
 | ||||
| home.merge.title=دمج ملفات | ||||
| home.merge.desc=دمج ملفات PDF متعددة في ملف واحد بسهولة. | ||||
| @ -91,24 +91,40 @@ home.ocr.desc=\u064A\u0642\u0648\u0645 \u0628\u0631\u0646\u0627\u0645\u062C \u06 | ||||
| home.extractImages.title=\u0627\u0633\u062A\u062E\u0631\u0627\u062C \u0627\u0644\u0635\u0648\u0631 | ||||
| home.extractImages.desc=\u064A\u0633\u062A\u062E\u0631\u062C \u062C\u0645\u064A\u0639 \u0627\u0644\u0635\u0648\u0631 \u0645\u0646 \u0645\u0644\u0641 PDF \u0648\u064A\u062D\u0641\u0638\u0647\u0627 \u0641\u064A \u0627\u0644\u0631\u0645\u0632 \u0627\u0644\u0628\u0631\u064A\u062F\u064A | ||||
| 
 | ||||
| home.pdfToPDFA.title = \u062A\u062D\u0648\u064A\u0644 \u0645\u0644\u0641\u0627\u062A PDF \u0625\u0644\u0649 PDF / A | ||||
| home.pdfToPDFA.desc = \u062A\u062D\u0648\u064A\u0644 PDF \u0625\u0644\u0649 PDF / A \u0644\u0644\u062A\u062E\u0632\u064A\u0646 \u0637\u0648\u064A\u0644 \u0627\u0644\u0645\u062F\u0649 | ||||
| home.pdfToPDFA.title=\u062A\u062D\u0648\u064A\u0644 \u0645\u0644\u0641\u0627\u062A PDF \u0625\u0644\u0649 PDF / A | ||||
| home.pdfToPDFA.desc=\u062A\u062D\u0648\u064A\u0644 PDF \u0625\u0644\u0649 PDF / A \u0644\u0644\u062A\u062E\u0632\u064A\u0646 \u0637\u0648\u064A\u0644 \u0627\u0644\u0645\u062F\u0649 | ||||
| 
 | ||||
| 
 | ||||
| home.PDFToWord.title = تحويل PDF إلى Word | ||||
| home.PDFToWord.desc = تحويل PDF إلى تنسيقات Word (DOC و DOCX و ODT) | ||||
| home.PDFToWord.title=تحويل PDF إلى Word | ||||
| home.PDFToWord.desc=تحويل PDF إلى تنسيقات Word (DOC و DOCX و ODT) | ||||
| 
 | ||||
| home.PDFToPresentation.title = PDF للعرض التقديمي | ||||
| home.PDFToPresentation.desc = تحويل PDF إلى تنسيقات عرض تقديمي (PPT و PPTX و ODP) | ||||
| home.PDFToPresentation.title=PDF للعرض التقديمي | ||||
| home.PDFToPresentation.desc=تحويل PDF إلى تنسيقات عرض تقديمي (PPT و PPTX و ODP) | ||||
| 
 | ||||
| home.PDFToText.title = تحويل PDF إلى نص / RTF | ||||
| home.PDFToText.desc = تحويل PDF إلى تنسيق نص أو RTF | ||||
| home.PDFToText.title=تحويل PDF إلى نص / RTF | ||||
| home.PDFToText.desc=تحويل PDF إلى تنسيق نص أو RTF | ||||
| 
 | ||||
| home.PDFToHTML.title = تحويل PDF إلى HTML | ||||
| home.PDFToHTML.desc = تحويل PDF إلى تنسيق HTML | ||||
| home.PDFToHTML.title=تحويل PDF إلى HTML | ||||
| home.PDFToHTML.desc=تحويل PDF إلى تنسيق HTML | ||||
| 
 | ||||
| home.PDFToXML.title=تحويل PDF إلى XML | ||||
| home.PDFToXML.desc=تحويل PDF إلى تنسيق XML | ||||
| 
 | ||||
| 
 | ||||
| home.ScannerImageSplit.title=كشف / انقسام الصور الممسوحة ضوئيًا | ||||
| home.ScannerImageSplit.desc=تقسيم عدة صور من داخل صورة / ملف PDF | ||||
| 
 | ||||
| ScannerImageSplit.selectText.1=عتبة الزاوية: | ||||
| ScannerImageSplit.selectText.2=تعيين الحد الأدنى للزاوية المطلقة المطلوبة لتدوير الصورة (افتراضي: 10). | ||||
| ScannerImageSplit.selectText.3=التسامح: | ||||
| ScannerImageSplit.selectText.4=يحدد نطاق تباين اللون حول لون الخلفية المقدر (الافتراضي: 30). | ||||
| ScannerImageSplit.selectText.5=أدنى مساحة: | ||||
| ScannerImageSplit.selectText.6=تعيين الحد الأدنى لمنطقة الصورة (الافتراضي: 10000). | ||||
| ScannerImageSplit.selectText.7=الحد الأدنى لمنطقة المحيط: | ||||
| ScannerImageSplit.selectText.8=تعيين الحد الأدنى لمنطقة المحيط للصورة | ||||
| ScannerImageSplit.selectText.9=حجم الحدود: | ||||
| ScannerImageSplit.selectText.10=يضبط حجم الحدود المضافة والمزالة لمنع الحدود البيضاء في الإخراج (الافتراضي: 1). | ||||
| 
 | ||||
| home.PDFToXML.title = تحويل PDF إلى XML | ||||
| home.PDFToXML.desc = تحويل PDF إلى تنسيق XML | ||||
| 
 | ||||
| navbar.settings=\u0625\u0639\u062F\u0627\u062F\u0627\u062A | ||||
| settings.title=\u0627\u0644\u0625\u0639\u062F\u0627\u062F\u0627\u062A | ||||
| @ -133,6 +149,8 @@ ocr.selectText.7=\u0641\u0631\u0636 \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\ | ||||
| ocr.selectText.8=\u0639\u0627\u062F\u064A (\u062E\u0637\u0623 \u0625\u0630\u0627 \u0643\u0627\u0646 PDF \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0646\u0635) | ||||
| ocr.selectText.9=\u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0625\u0636\u0627\u0641\u064A\u0629 | ||||
| ocr.selectText.10=\u0648\u0636\u0639 \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641 | ||||
| ocr.selectText.11 = إزالة الصور بعد التعرف الضوئي على الحروف (يزيل كل الصور ، يكون مفيدًا فقط إذا كان جزءًا من خطوة التحويل) | ||||
| ocr.selectText.12 = نوع العرض (متقدم) | ||||
| ocr.help=\u064A\u0631\u062C\u0649 \u0642\u0631\u0627\u0621\u0629 \u0647\u0630\u0647 \u0627\u0644\u0648\u062B\u0627\u0626\u0642 \u062D\u0648\u0644 \u0643\u064A\u0641\u064A\u0629 \u0627\u0633\u062A\u062E\u062F\u0627\u0645 \u0647\u0630\u0627 \u0644\u0644\u063A\u0627\u062A \u0623\u062E\u0631\u0649 \u0648 / \u0623\u0648 \u0627\u0644\u0627\u0633\u062A\u062E\u062F\u0627\u0645 \u0644\u064A\u0633 \u0641\u064A \u0639\u0627\u0645\u0644 \u0627\u0644\u0625\u0631\u0633\u0627\u0621 | ||||
| ocr.credit=\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\u0644\u062E\u062F\u0645\u0629 OCRmyPDF \u0648 Tesseract \u0644 OCR. | ||||
| ocr.submit=\u0645\u0639\u0627\u0644\u062C\u0629 PDF \u0628\u0627\u0633\u062A\u062E\u062F\u0627\u0645 OCR | ||||
| @ -182,8 +200,8 @@ pdfOrganiser.header=منظم صفحات PDF | ||||
| pdfOrganiser.submit=إعادة ترتيب الصفحات | ||||
| 
 | ||||
| #multiTool | ||||
| multiTool.title = أداة متعددة PDF | ||||
| multiTool.header = أداة متعددة PDF | ||||
| multiTool.title=أداة متعددة PDF | ||||
| multiTool.header=أداة متعددة PDF | ||||
| 
 | ||||
| #pageRemover | ||||
| pageRemover.title=مزيل الصفحة | ||||
| @ -329,32 +347,32 @@ pdfToPDFA.credit=\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\ | ||||
| pdfToPDFA.submit=\u062A\u062D\u0648\u064A\u0644 | ||||
| 
 | ||||
| 
 | ||||
| PDFToWord.title = تحويل PDF إلى Word | ||||
| PDFToWord.header = تحويل PDF إلى Word | ||||
| PDFToWord.selectText.1 = تنسيق ملف الإخراج | ||||
| PDFToWord.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات. | ||||
| PDFToWord.submit = تحويل | ||||
| PDFToWord.title=تحويل PDF إلى Word | ||||
| PDFToWord.header=تحويل PDF إلى Word | ||||
| PDFToWord.selectText.1=تنسيق ملف الإخراج | ||||
| PDFToWord.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات. | ||||
| PDFToWord.submit=تحويل | ||||
| 
 | ||||
| PDFToPresentation.title = PDF للعرض التقديمي | ||||
| PDFToPresentation.header = PDF للعرض التقديمي | ||||
| PDFToPresentation.selectText.1 = تنسيق ملف الإخراج | ||||
| PDFToPresentation.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملف. | ||||
| PDFToPresentation.submit = تحويل | ||||
| PDFToPresentation.title=PDF للعرض التقديمي | ||||
| PDFToPresentation.header=PDF للعرض التقديمي | ||||
| PDFToPresentation.selectText.1=تنسيق ملف الإخراج | ||||
| PDFToPresentation.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملف. | ||||
| PDFToPresentation.submit=تحويل | ||||
| 
 | ||||
| 
 | ||||
| PDFToText.title = تحويل PDF إلى نص / RTF | ||||
| PDFToText.header = تحويل PDF إلى نص / RTF | ||||
| PDFToText.selectText.1 = تنسيق ملف الإخراج | ||||
| PDFToText.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات. | ||||
| PDFToText.submit = تحويل | ||||
| PDFToText.title=تحويل PDF إلى نص / RTF | ||||
| PDFToText.header=تحويل PDF إلى نص / RTF | ||||
| PDFToText.selectText.1=تنسيق ملف الإخراج | ||||
| PDFToText.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات. | ||||
| PDFToText.submit=تحويل | ||||
| 
 | ||||
| 
 | ||||
| PDFToHTML.title = PDF إلى HTML | ||||
| PDFToHTML.header = PDF إلى HTML | ||||
| PDFToHTML.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات. | ||||
| PDFToHTML.submit = تحويل | ||||
| PDFToHTML.title=PDF إلى HTML | ||||
| PDFToHTML.header=PDF إلى HTML | ||||
| PDFToHTML.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات. | ||||
| PDFToHTML.submit=تحويل | ||||
| 
 | ||||
| PDFToXML.title = تحويل PDF إلى XML | ||||
| PDFToXML.header = تحويل PDF إلى XML | ||||
| PDFToXML.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات. | ||||
| PDFToXML.submit = تحويل | ||||
| PDFToXML.title=تحويل PDF إلى XML | ||||
| PDFToXML.header=تحويل PDF إلى XML | ||||
| PDFToXML.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات. | ||||
| PDFToXML.submit=تحويل | ||||
|  | ||||
| @ -104,6 +104,20 @@ home.PDFToHTML.desc=PDF in HTML-Format konvertieren | ||||
| home.PDFToXML.title=PDF in XML | ||||
| home.PDFToXML.desc=PDF in XML-Format konvertieren | ||||
| 
 | ||||
| home.ScannerImageSplit.title=Gescannte Fotos erkennen/aufteilen | ||||
| home.ScannerImageSplit.desc=Teilt mehrere Fotos innerhalb eines Fotos/PDF | ||||
| 
 | ||||
| ScannerImageSplit.selectText.1=Winkelschwelle: | ||||
| ScannerImageSplit.selectText.2=Legt den minimalen absoluten Winkel fest, der erforderlich ist, damit das Bild gedreht werden kann (Standard: 10). | ||||
| ScannerImageSplit.selectText.3=Toleranz: | ||||
| ScannerImageSplit.selectText.4=Bestimmt den Bereich der Farbvariation um die geschätzte Hintergrundfarbe herum (Standard: 30). | ||||
| ScannerImageSplit.selectText.5=Mindestbereich: | ||||
| ScannerImageSplit.selectText.6=Legt den minimalen Bereichsschwellenwert für ein Foto fest (Standard: 10000). | ||||
| ScannerImageSplit.selectText.7=Minimaler Konturbereich: | ||||
| ScannerImageSplit.selectText.8=Legt den minimalen Konturbereichsschwellenwert für ein Foto fest | ||||
| ScannerImageSplit.selectText.9=Randgröße: | ||||
| ScannerImageSplit.selectText.10=Legt die Größe des hinzugefügten und entfernten Randes fest, um weiße Ränder in der Ausgabe zu verhindern (Standard: 1). | ||||
| 
 | ||||
| 
 | ||||
| navbar.settings=Einstellungen | ||||
| settings.title=Einstellungen | ||||
| @ -128,6 +142,8 @@ ocr.selectText.7=OCR erzwingen, OCR wird jede Seite entfernen und alle ursprüng | ||||
| ocr.selectText.8=Normal (Fehler, wenn PDF Text enthält) | ||||
| ocr.selectText.9=Zusätzliche Einstellungen | ||||
| ocr.selectText.10=OCR-Modus | ||||
| ocr.selectText.11=Bilder nach OCR entfernen (Entfernt ALLE Bilder, nur sinnvoll, wenn Teil des Konvertierungsschritts) | ||||
| ocr.selectText.12=Rendertyp (Erweitert) | ||||
| ocr.help=Bitte lesen Sie diese Dokumentation, um zu erfahren, wie Sie dies für andere Sprachen verwenden und/oder nicht in Docker verwenden können | ||||
| ocr.credit=Dieser Dienst verwendet OCRmyPDF und Tesseract für OCR. | ||||
| ocr.submit=PDF mit OCR verarbeiten | ||||
|  | ||||
| @ -105,8 +105,22 @@ home.PDFToHTML.desc=Convert PDF to HTML format | ||||
| home.PDFToXML.title=PDF to XML | ||||
| home.PDFToXML.desc=Convert PDF to XML format | ||||
| 
 | ||||
| home.ScannerImageSplit.title=Detect/Split Scanned photos | ||||
| home.ScannerImageSplit.desc=Splits multiple photos from within a photo/PDF | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ScannerImageSplit.selectText.1=Angle Threshold: | ||||
| ScannerImageSplit.selectText.2=Sets the minimum absolute angle required for the image to be rotated (default: 10). | ||||
| ScannerImageSplit.selectText.3=Tolerance: | ||||
| ScannerImageSplit.selectText.4=Determines the range of color variation around the estimated background color (default: 30). | ||||
| ScannerImageSplit.selectText.5=Minimum Area: | ||||
| ScannerImageSplit.selectText.6=Sets the minimum area threshold for a photo (default: 10000). | ||||
| ScannerImageSplit.selectText.7=Minimum Contour Area: | ||||
| ScannerImageSplit.selectText.8=Sets the minimum contour area threshold for a photo | ||||
| ScannerImageSplit.selectText.9=Border Size: | ||||
| ScannerImageSplit.selectText.10=Sets the size of the border added and removed to prevent white borders in the output (default: 1). | ||||
| 
 | ||||
| navbar.settings=Settings | ||||
| settings.title=Settings | ||||
| settings.update=Update available | ||||
| @ -118,6 +132,7 @@ settings.downloadOption.3=Download file | ||||
| settings.zipThreshold=Zip files when the number of downloaded files exceeds | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|                              | ||||
|                              | ||||
| #OCR | ||||
| @ -133,6 +148,8 @@ ocr.selectText.7=Force OCR, will OCR Every page removing all original text eleme | ||||
| ocr.selectText.8=Normal (Will error if PDF contains text) | ||||
| ocr.selectText.9=Additional Settings | ||||
| ocr.selectText.10=OCR Mode | ||||
| ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step) | ||||
| ocr.selectText.12=Render Type (Advanced) | ||||
| ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker | ||||
| ocr.credit=This service uses OCRmyPDF and Tesseract for OCR. | ||||
| ocr.submit=Process PDF with OCR | ||||
|  | ||||
| @ -104,6 +104,19 @@ home.PDFToHTML.desc=Convertir PDF a formato HTML | ||||
| home.PDFToXML.title=PDF a XML | ||||
| home.PDFToXML.desc=Convertir PDF a formato XML | ||||
| 
 | ||||
| home.ScannerImageSplit.title=Detectar/Dividir fotos escaneadas | ||||
| home.ScannerImageSplit.desc=Dividir varias fotos dentro de una foto/PDF | ||||
| 
 | ||||
| ScannerImageSplit.selectText.1=Umbral de ángulo: | ||||
| ScannerImageSplit.selectText.2=Establece el ángulo absoluto mínimo requerido para rotar la imagen (predeterminado: 10). | ||||
| ScannerImageSplit.selectText.3=Tolerancia: | ||||
| ScannerImageSplit.selectText.4=Determina el rango de variación de color alrededor del color de fondo estimado (predeterminado: 30). | ||||
| ScannerImageSplit.selectText.5=Área mínima: | ||||
| ScannerImageSplit.selectText.6=Establece el umbral mínimo de área para una foto (predeterminado: 10000). | ||||
| ScannerImageSplit.selectText.7=Área de contorno mínima: | ||||
| ScannerImageSplit.selectText.8=Establece el umbral mínimo del área de contorno para una foto | ||||
| ScannerImageSplit.selectText.9=Tamaño del borde: | ||||
| ScannerImageSplit.selectText.10=Establece el tamaño del borde agregado y eliminado para evitar bordes blancos en la salida (predeterminado: 1). | ||||
| 
 | ||||
| navbar.settings=Ajustes | ||||
| settings.title=Ajustes | ||||
| @ -131,6 +144,8 @@ ocr.selectText.7=Fuerza OCR, OCR eliminará en cada página todo el texto origin | ||||
| ocr.selectText.8=Normal (Se producirá un error si el PDF contiene texto) | ||||
| ocr.selectText.9=Ajustes Adicionales | ||||
| ocr.selectText.10=Modo OCR | ||||
| ocr.selectText.11=Eliminar imágenes después de OCR (Elimina TODAS las imágenes, solo es útil si es parte del paso de conversión) | ||||
| ocr.selectText.12=Tipo de procesamiento (avanzado) | ||||
| ocr.help=Lea esta documentación sobre cómo usar esto para otros idiomas y/o no usarlo en docker | ||||
| ocr.credit=Este servicio utiliza OCRmyPDF y Tesseract para OCR. | ||||
| ocr.submit=Procesa PDF con OCR | ||||
|  | ||||
| @ -110,6 +110,20 @@ home.PDFToHTML.desc=Convertir le PDF au format HTML | ||||
| home.PDFToXML.title=PDF vers XML | ||||
| home.PDFToXML.desc=Convertir le PDF au format XML | ||||
| 
 | ||||
| home.ScannerImageSplit.title=Détecter/diviser les photos numérisées | ||||
| home.ScannerImageSplit.desc=Divise plusieurs photos à partir d'une photo/PDF | ||||
| 
 | ||||
| ScannerImageSplit.selectText.1=Seuil d'angle : | ||||
| ScannerImageSplit.selectText.2=Définit l'angle absolu minimum requis pour la rotation de l'image (par défaut : 10). | ||||
| ScannerImageSplit.selectText.3=Tolérance : | ||||
| ScannerImageSplit.selectText.4=Détermine la plage de variation de couleur autour de la couleur d'arrière-plan estimée (par défaut : 30). | ||||
| ScannerImageSplit.selectText.5=Zone minimale : | ||||
| ScannerImageSplit.selectText.6=Définit le seuil de zone minimum pour une photo (par défaut : 10000). | ||||
| ScannerImageSplit.selectText.7=Zone de contour minimale : | ||||
| ScannerImageSplit.selectText.8=Définit le seuil de zone de contour minimum pour une photo | ||||
| ScannerImageSplit.selectText.9=Taille de la bordure : | ||||
| ScannerImageSplit.selectText.10=Définit la taille de la bordure ajoutée et supprimée pour éviter les bordures blanches dans la sortie (par défaut : 1). | ||||
| 
 | ||||
| navbar.settings=Paramètres | ||||
| settings.title=Paramètres | ||||
| settings.update=Mise à jour disponible | ||||
| @ -134,6 +148,8 @@ ocr.selectText.7=Forcer l'OCR, OCR chaque page supprimera tous les éléments de | ||||
| ocr.selectText.8=Normal (Erreur si le PDF contient du texte) | ||||
| ocr.selectText.9=Paramètres supplémentaires | ||||
| ocr.selectText.10=Mode ROC | ||||
| ocr.selectText.11=Supprimer les images après l'OCR (Supprime TOUTES les images, utile uniquement si elles font partie de l'étape de conversion) | ||||
| ocr.selectText.12=Type de rendu (avancé) | ||||
| ocr.help=Veuillez lire cette documentation pour savoir comment l'utiliser pour d'autres langues et/ou une utilisation non dans docker | ||||
| ocr.credit=Ce service utilise OCRmyPDF et Tesseract pour l'OCR. | ||||
| ocr.submit=Traiter PDF avec OCR | ||||
|  | ||||
							
								
								
									
										35
									
								
								src/main/resources/static/css/rainbow-mode.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/main/resources/static/css/rainbow-mode.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| /* Rainbow Mode Styles */ | ||||
| body { | ||||
|     background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%); | ||||
|     color: #fff !important; | ||||
| } | ||||
| 
 | ||||
| .dark-card { | ||||
|     background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important; | ||||
|     color: white !important; | ||||
| } | ||||
| .jumbotron { | ||||
|     background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%); | ||||
|     color: #fff !important; | ||||
| } | ||||
| 
 | ||||
| .list-group { | ||||
|     background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important; | ||||
|     color: fff !important; | ||||
| } | ||||
| .list-group-item { | ||||
|     background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important; | ||||
|     color: fff !important; | ||||
| } | ||||
| #support-section { | ||||
|    background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #pages-container-wrapper { | ||||
|   --background-color: rgba(255, 255, 255, 0.046) !important; | ||||
|   --scroll-bar-color: #4c4c4c !important; | ||||
|   --scroll-bar-thumb: #d3d3d3 !important; | ||||
|   --scroll-bar-thumb-hover: #ffffff !important; | ||||
| } | ||||
|      | ||||
							
								
								
									
										13
									
								
								src/main/resources/static/images/scanner.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/main/resources/static/images/scanner.svg
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| 
 | ||||
| <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> | ||||
| <svg version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  | ||||
| 	 viewBox="0 0 32 32" xml:space="preserve"> | ||||
| <style type="text/css"> | ||||
| 	.st0{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;} | ||||
| 	.st1{fill:none;stroke:#000000;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:10;} | ||||
| </style> | ||||
| <path class="st0" d="M30,20H3v6c0,1.1,0.9,2,2,2h23c1.1,0,2-0.9,2-2V20z"/> | ||||
| <line class="st0" x1="30" y1="20" x2="3" y2="4"/> | ||||
| <line class="st0" x1="7" y1="24" x2="10" y2="24"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 691 B | 
							
								
								
									
										286
									
								
								src/main/resources/static/js/game.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								src/main/resources/static/js/game.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,286 @@ | ||||
| function initializeGame() { | ||||
|     const gameContainer = document.getElementById('game-container'); | ||||
|     const player = document.getElementById('player'); | ||||
|      | ||||
|     let playerSize = gameContainer.clientWidth * 0.0625; // 5% of container width
 | ||||
|     player.style.width = playerSize + 'px'; | ||||
|     player.style.height = playerSize + 'px'; | ||||
|      | ||||
|     let playerX = gameContainer.clientWidth / 2 - playerSize / 2; | ||||
|     let playerY = gameContainer.clientHeight * 0.1; | ||||
|     const scoreElement = document.getElementById('score'); | ||||
|     const levelElement = document.getElementById('level'); | ||||
|     const livesElement = document.getElementById('lives'); | ||||
|     const highScoreElement = document.getElementById('high-score'); | ||||
| 
 | ||||
|     let pdfSize = gameContainer.clientWidth * 0.0625; // 5% of container width
 | ||||
|     let projectileWidth = gameContainer.clientWidth * 0.00625; // 0.5% of container width
 | ||||
|     let projectileHeight = gameContainer.clientHeight * 0.01667; // 1% of container height
 | ||||
| 
 | ||||
|     let paused = false; | ||||
|     const fireRate = 200; // Time between shots in milliseconds
 | ||||
|     let lastProjectileTime = 0; | ||||
|     let lives = 3; | ||||
|     let highScore = localStorage.getItem('highScore') ? parseInt(localStorage.getItem('highScore')) : 0; | ||||
|     updateHighScore(); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     const keysPressed = {}; | ||||
|     const pdfs = []; | ||||
|     const projectiles = []; | ||||
|     let score = 0; | ||||
|     let level = 1; | ||||
|     let pdfSpeed = 1; | ||||
|     let gameOver = false; | ||||
| 
 | ||||
|     function handleKeys() { | ||||
| 	    if (keysPressed['ArrowLeft']) { | ||||
| 	        playerX -= 10; | ||||
| 	    } | ||||
| 	    if (keysPressed['ArrowRight']) { | ||||
| 	        playerX += 10; | ||||
| 	    } | ||||
| 	    if (keysPressed[' '] && !gameOver) { | ||||
| 	        const currentTime = new Date().getTime(); | ||||
| 	        if (currentTime - lastProjectileTime >= fireRate) { | ||||
| 	            shootProjectile(); | ||||
| 	            lastProjectileTime = currentTime; | ||||
| 	        } | ||||
| 	    } | ||||
| 	    updatePlayerPosition(); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     document.addEventListener('keydown', (event) => { | ||||
|         if (event.key === ' ') { | ||||
|             event.preventDefault(); | ||||
|         } | ||||
|         keysPressed[event.key] = true; | ||||
|         handleKeys(); | ||||
|     }); | ||||
| 
 | ||||
|     document.addEventListener('keyup', (event) => { | ||||
|         keysPressed[event.key] = false; | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     function updatePlayerPosition() { | ||||
|         player.style.left = playerX + 'px'; | ||||
|         player.style.bottom = playerY + 'px'; | ||||
|     } | ||||
| 
 | ||||
|     function updateLives() { | ||||
|         livesElement.textContent = 'Lives: ' + lives; | ||||
|     } | ||||
| 
 | ||||
|     function updateHighScore() { | ||||
|         highScoreElement.textContent = 'High Score: ' + highScore; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     function shootProjectile() { | ||||
|         const projectile = document.createElement('div'); | ||||
|         projectile.classList.add('projectile'); | ||||
|         projectile.style.backgroundColor = 'black'; | ||||
|         projectile.style.width = projectileWidth + 'px'; | ||||
|         projectile.style.height = projectileHeight + 'px'; | ||||
|         projectile.style.left = (playerX + playerSize / 2 - projectileWidth / 2) + 'px'; | ||||
|         projectile.style.top = (gameContainer.clientHeight - playerY - playerSize) + 'px'; | ||||
|         gameContainer.appendChild(projectile); | ||||
|         projectiles.push(projectile); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     function spawnPdf() { | ||||
|         const pdf = document.createElement('img'); | ||||
|         pdf.src = 'images/file-earmark-pdf.svg'; | ||||
|         pdf.classList.add('pdf'); | ||||
|         pdf.style.width = pdfSize + 'px'; | ||||
|         pdf.style.height = pdfSize + 'px'; | ||||
|         pdf.style.left = Math.floor(Math.random() * (gameContainer.clientWidth - pdfSize)) + 'px'; | ||||
|         pdf.style.top = '0px'; | ||||
|         gameContainer.appendChild(pdf); | ||||
|         pdfs.push(pdf); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| function resetEnemies() { | ||||
|     pdfs.forEach((pdf) => gameContainer.removeChild(pdf)); | ||||
|     pdfs.length = 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|     function updateGame() { | ||||
|         if (gameOver || paused) return; | ||||
| 
 | ||||
|         pdfs.forEach((pdf, pdfIndex) => { | ||||
|             const pdfY = parseInt(pdf.style.top) + pdfSpeed; | ||||
|             if (pdfY + 50 > gameContainer.clientHeight) { | ||||
|                 gameContainer.removeChild(pdf); | ||||
|                 pdfs.splice(pdfIndex, 1); | ||||
| 
 | ||||
|                 // Deduct 2 points when a PDF gets past the player
 | ||||
|                 score -= 0; | ||||
|                 updateScore(); | ||||
| 
 | ||||
|                 // Decrease lives and check if game over
 | ||||
|                 lives--; | ||||
|                 updateLives(); | ||||
|                 if (lives <= 0) { | ||||
|                     endGame(); | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|             } else { | ||||
|                 pdf.style.top = pdfY + 'px'; | ||||
| 
 | ||||
|                 // Check for collision with player
 | ||||
|                 if (collisionDetected(player, pdf)) { | ||||
|                     lives--; | ||||
|                     updateLives(); | ||||
|                     resetEnemies(); | ||||
|                     if (lives <= 0) { | ||||
|                         endGame(); | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         projectiles.forEach((projectile, projectileIndex) => { | ||||
|             const projectileY = parseInt(projectile.style.top) - 10; | ||||
|             if (projectileY < 0) { | ||||
|                 gameContainer.removeChild(projectile); | ||||
|                 projectiles.splice(projectileIndex, 1); | ||||
|             } else { | ||||
|                 projectile.style.top = projectileY + 'px'; | ||||
|             } | ||||
| 
 | ||||
|             for (let pdfIndex = 0; pdfIndex < pdfs.length; pdfIndex++) { | ||||
|                 const pdf = pdfs[pdfIndex]; | ||||
|                 if (collisionDetected(projectile, pdf)) { | ||||
|                     gameContainer.removeChild(pdf); | ||||
|                     gameContainer.removeChild(projectile); | ||||
|                     pdfs.splice(pdfIndex, 1); | ||||
|                     projectiles.splice(projectileIndex, 1); | ||||
|                     score = score + 10; | ||||
|                     updateScore(); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         setTimeout(updateGame, 1000 / 60); | ||||
|     } | ||||
| function resetGame() { | ||||
|     playerX = gameContainer.clientWidth / 2; | ||||
|     playerY = 50; | ||||
|     updatePlayerPosition(); | ||||
| 
 | ||||
|     pdfs.forEach((pdf) => gameContainer.removeChild(pdf)); | ||||
|     projectiles.forEach((projectile) => gameContainer.removeChild(projectile)); | ||||
| 
 | ||||
|     pdfs.length = 0; | ||||
|     projectiles.length = 0; | ||||
| 
 | ||||
|     score = 0; | ||||
|     level = 1; | ||||
|     lives = 3; | ||||
|      | ||||
|     gameOver = false; | ||||
| 
 | ||||
|     updateScore(); | ||||
|     updateLives(); | ||||
|     levelElement.textContent = 'Level: ' + level; | ||||
|     pdfSpeed = 1; | ||||
|     clearTimeout(spawnPdfTimeout); // Clear the existing spawnPdfTimeout
 | ||||
|     setTimeout(updateGame, 1000 / 60); | ||||
|     spawnPdfInterval(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     function updateScore() { | ||||
| 	    scoreElement.textContent = 'Score: ' + score; | ||||
| 	    checkLevelUp(); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     function checkLevelUp() { | ||||
|         const newLevel = Math.floor(score / 100) + 1; | ||||
|         if (newLevel > level) { | ||||
|             level = newLevel; | ||||
|             levelElement.textContent = 'Level: ' + level; | ||||
|             pdfSpeed += 1; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function collisionDetected(a, b) { | ||||
|         const rectA = a.getBoundingClientRect(); | ||||
|         const rectB = b.getBoundingClientRect(); | ||||
|         return ( | ||||
|             rectA.left < rectB.right && | ||||
|             rectA.right > rectB.left && | ||||
|             rectA.top < rectB.bottom && | ||||
|             rectA.bottom > rectB.top | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     function endGame() { | ||||
|         gameOver = true; | ||||
|         if (score > highScore) { | ||||
|             highScore = score; | ||||
|             localStorage.setItem('highScore', highScore); | ||||
|             updateHighScore(); | ||||
|         } | ||||
|         alert('Game Over! Your final score is: ' + score); | ||||
|         setTimeout(() => { // Wrap the resetGame() call in a setTimeout
 | ||||
|             resetGame(); | ||||
|         }, 0); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     let spawnPdfTimeout; | ||||
| 
 | ||||
|     function spawnPdfInterval() { | ||||
|          console.log("spawnPdfInterval"); | ||||
|         if (gameOver || paused) { | ||||
|             console.log("spawnPdfInterval 2"); | ||||
|             clearTimeout(spawnPdfTimeout); | ||||
|             return; | ||||
|         } | ||||
|         console.log("spawnPdfInterval 3"); | ||||
|         spawnPdf(); | ||||
|         spawnPdfTimeout = setTimeout(spawnPdfInterval, 1000 - level * 50); | ||||
|     } | ||||
| 
 | ||||
|     updatePlayerPosition(); | ||||
|     updateGame(); | ||||
|     spawnPdfInterval(); | ||||
| 
 | ||||
| 
 | ||||
|     document.addEventListener('visibilitychange', function() { | ||||
|         if (document.hidden) { | ||||
|             paused = true; | ||||
|         } else { | ||||
|             paused = false; | ||||
|             updateGame(); | ||||
|             spawnPdfInterval(); | ||||
|         } | ||||
| 
 | ||||
|     }); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| window.initializeGame = initializeGame; | ||||
							
								
								
									
										3
									
								
								src/main/resources/static/rainbow.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/main/resources/static/rainbow.svg
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-rainbow" viewBox="0 0 16 16"> | ||||
|   <path d="M8 4.5a7 7 0 0 0-7 7 .5.5 0 0 1-1 0 8 8 0 1 1 16 0 .5.5 0 0 1-1 0 7 7 0 0 0-7-7zm0 2a5 5 0 0 0-5 5 .5.5 0 0 1-1 0 6 6 0 1 1 12 0 .5.5 0 0 1-1 0 5 5 0 0 0-5-5zm0 2a3 3 0 0 0-3 3 .5.5 0 0 1-1 0 4 4 0 1 1 8 0 .5.5 0 0 1-1 0 3 3 0 0 0-3-3zm0 2a1 1 0 0 0-1 1 .5.5 0 0 1-1 0 2 2 0 1 1 4 0 .5.5 0 0 1-1 0 1 1 0 0 0-1-1z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 459 B | 
							
								
								
									
										22
									
								
								src/main/resources/templates/about.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/main/resources/templates/about.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| <!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='<3')}"></th:block> | ||||
| 
 | ||||
| <body> | ||||
|   <div id="page-container"> | ||||
|     <div id="content-wrap"> | ||||
|       <div th:insert="~{fragments/navbar.html :: navbar}"></div> | ||||
|       <br> <br> | ||||
|       <div class="container"> | ||||
|         <div class="row justify-content-center"> | ||||
|           <div class="col-md-6"> | ||||
|              | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div th:insert="~{fragments/footer.html :: footer}"></div> | ||||
|   </div> | ||||
| </body> | ||||
| </html> | ||||
| 
 | ||||
| @ -28,193 +28,235 @@ | ||||
| <!-- Custom --> | ||||
| <link rel="stylesheet" href="css/general.css"> | ||||
| <link rel="stylesheet" th:href="@{css/dark-mode.css}" id="dark-mode-styles"> | ||||
| 
 | ||||
| <link rel="stylesheet" th:href="@{css/rainbow-mode.css}" id="rainbow-mode-styles" disabled="true"> | ||||
| <script> | ||||
| function toggleDarkMode() { | ||||
| 	  var darkModeStyles = document.getElementById("dark-mode-styles"); | ||||
| 	  var darkModeIcon = document.getElementById("dark-mode-icon"); | ||||
| var toggleCount = 0; | ||||
| var lastToggleTime = Date.now(); | ||||
| 
 | ||||
| 	  if (localStorage.getItem("dark-mode") == "on") { | ||||
| 	    localStorage.setItem("dark-mode", "off"); | ||||
| 	    darkModeStyles.disabled = true; | ||||
| 	    darkModeIcon.src = "sun.svg"; | ||||
| 	  } else { | ||||
| 	    localStorage.setItem("dark-mode", "on"); | ||||
| 	    darkModeStyles.disabled = false; | ||||
| 	    darkModeIcon.src = "moon.svg"; | ||||
| 	  } | ||||
| 	} | ||||
| function toggleDarkMode() { | ||||
|   var currentTime = Date.now(); | ||||
|   if (currentTime - lastToggleTime < 1000) { | ||||
|     toggleCount++; | ||||
|   } else { | ||||
|     toggleCount = 1; | ||||
|   } | ||||
|   lastToggleTime = currentTime; | ||||
| 
 | ||||
|   var darkModeStyles = document.getElementById("dark-mode-styles"); | ||||
|   var rainbowModeStyles = document.getElementById("rainbow-mode-styles"); | ||||
|   var darkModeIcon = document.getElementById("dark-mode-icon"); | ||||
| 
 | ||||
|   if (toggleCount >= 18) { | ||||
|     localStorage.setItem("dark-mode", "rainbow"); | ||||
|     darkModeStyles.disabled = true; | ||||
|     rainbowModeStyles.disabled = false; | ||||
|     darkModeIcon.src = "rainbow.svg"; | ||||
|   } else if (localStorage.getItem("dark-mode") == "on") { | ||||
|     localStorage.setItem("dark-mode", "off"); | ||||
|     darkModeStyles.disabled = true; | ||||
|     rainbowModeStyles.disabled = true; | ||||
|     darkModeIcon.src = "sun.svg"; | ||||
|   } else { | ||||
|     localStorage.setItem("dark-mode", "on"); | ||||
|     darkModeStyles.disabled = false; | ||||
|     rainbowModeStyles.disabled = true; | ||||
|     darkModeIcon.src = "moon.svg"; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| document.addEventListener("DOMContentLoaded", function () { | ||||
| 	  var darkModeStyles = document.getElementById("dark-mode-styles"); | ||||
| 	  var darkModeIcon = document.getElementById("dark-mode-icon"); | ||||
|   var darkModeStyles = document.getElementById("dark-mode-styles"); | ||||
|   var rainbowModeStyles = document.getElementById("rainbow-mode-styles"); | ||||
|   var darkModeIcon = document.getElementById("dark-mode-icon"); | ||||
| 
 | ||||
| 	  // Check if the user has already set a preference | ||||
| 	  if (localStorage.getItem("dark-mode") == "on") { | ||||
| 	    darkModeStyles.disabled = false; | ||||
| 	    darkModeIcon.src = "moon.svg"; | ||||
| 	  } else if (localStorage.getItem("dark-mode") == "off") { | ||||
| 	    darkModeStyles.disabled = true; | ||||
| 	    darkModeIcon.src = "sun.svg"; | ||||
| 	  } else { | ||||
| 	    // Check the OS's default dark mode setting | ||||
| 	    if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { | ||||
| 	      darkModeStyles.disabled = false; | ||||
| 	      darkModeIcon.src = "moon.svg"; | ||||
| 	    } else { | ||||
| 	      darkModeStyles.disabled = true; | ||||
| 	      darkModeIcon.src = "sun.svg"; | ||||
| 	    } | ||||
| 	  } | ||||
|   if (localStorage.getItem("dark-mode") == "on") { | ||||
|     darkModeStyles.disabled = false; | ||||
|     rainbowModeStyles.disabled = true; | ||||
|     darkModeIcon.src = "moon.svg"; | ||||
|   } else if (localStorage.getItem("dark-mode") == "off") { | ||||
|     darkModeStyles.disabled = true; | ||||
|     rainbowModeStyles.disabled = true; | ||||
|     darkModeIcon.src = "sun.svg"; | ||||
|   } else if (localStorage.getItem("dark-mode") == "rainbow") { | ||||
|     darkModeStyles.disabled = true; | ||||
|     rainbowModeStyles.disabled = false; | ||||
|     darkModeIcon.src = "rainbow.svg"; | ||||
|   } else { | ||||
|     if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { | ||||
|       darkModeStyles.disabled = false; | ||||
|       rainbowModeStyles.disabled = true; | ||||
|       darkModeIcon.src = "moon.svg"; | ||||
|     } else { | ||||
|       darkModeStyles.disabled = true; | ||||
|       rainbowModeStyles.disabled = true; | ||||
|       darkModeIcon.src = "sun.svg"; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 	  // Attach the toggleDarkMode function to the click event of the dark mode toggle | ||||
| 	  document.getElementById("dark-mode-toggle").addEventListener("click", function (event) { | ||||
| 	    event.preventDefault(); | ||||
| 	    toggleDarkMode(); | ||||
| 	  }); | ||||
| 	}); | ||||
|   document.getElementById("dark-mode-toggle").addEventListener("click", function (event) { | ||||
|     event.preventDefault(); | ||||
|     toggleDarkMode(); | ||||
|   }); | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| 	</script> | ||||
| </head> | ||||
| <th:block th:fragment="fileSelector(name, multiple)"  th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}"> | ||||
|     <div class="custom-file-chooser"> | ||||
|   <div class="custom-file"> | ||||
|     <input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple> | ||||
|     <label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label> | ||||
|   </div> | ||||
|   <div class="selected-files"></div> | ||||
| </div> | ||||
| <br> | ||||
|     <div id="progressBarContainer" style="display: none; position: relative;"> | ||||
|                 <div class="progress" style="height: 1rem;"> | ||||
|                     <div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"> | ||||
|                         <span class="sr-only">Loading...</span> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <script> | ||||
| 
 | ||||
| <script> | ||||
| 
 | ||||
| 
 | ||||
|              | ||||
|             $('form').submit(function(event) { | ||||
|             	var processing = "Processing..." | ||||
|            		var submitButtonText = $('#submitBtn').text() | ||||
|             		 | ||||
|             	$('#submitBtn').text('Processing...'); | ||||
|             	console.log("start download code") | ||||
|             	var files = $('#fileInput-input')[0].files; | ||||
|             	var url = this.action; | ||||
|             	console.log(url) | ||||
|             	event.preventDefault(); // Prevent the default form handling behavior | ||||
|                  /* Check if ${multiple} is false */ | ||||
|                  var multiple = [[${multiple}]] || false; | ||||
|                  var override = $('#override').val() || ''; | ||||
|                  console.log("override=" + override) | ||||
|                  if (override === 'multi' || (!multiple && files.length > 1) && override !== 'single' ) { | ||||
|                 	 console.log("multi parallel download") | ||||
|                      submitMultiPdfForm(event,url); | ||||
|                  } else { | ||||
|                      console.log("start single download") | ||||
|             $(document).ready(function() { | ||||
|             	 | ||||
|             	function loadGameScript(callback) { | ||||
|             	    console.log('loadGameScript called'); | ||||
|             	    const script = document.createElement('script'); | ||||
|             	    script.src = 'js/game.js'; | ||||
|             	    script.onload = callback; | ||||
|             	    document.body.appendChild(script); | ||||
|             	} | ||||
|             	let gameScriptLoaded = false; | ||||
|             	$('#show-game-btn').on('click', function() { | ||||
|             	    console.log('Show game button clicked'); | ||||
|             	    if (!gameScriptLoaded) { | ||||
|             	        console.log('Show game button load'); | ||||
|             	        loadGameScript(function() { | ||||
|             	            console.log('Game script loaded'); | ||||
|             	            window.initializeGame(); | ||||
|             	            gameScriptLoaded = true; | ||||
|             	        }); | ||||
|             	    } | ||||
|             	    $('#game-container-wrapper').show(); | ||||
|             	}); | ||||
| 
 | ||||
|                      // Get the selected download option from localStorage | ||||
|                      const downloadOption = localStorage.getItem('downloadOption'); | ||||
| 
 | ||||
|                      var formData = new FormData($('form')[0]); | ||||
|                       | ||||
|                      // Send the request to the server using the fetch() API | ||||
|                      fetch(url, { | ||||
|                          method: 'POST', | ||||
|                          body: formData | ||||
|                      }).then(response => { | ||||
|                          if (!response) { | ||||
|                              throw new Error('Received null response for file ' + i); | ||||
|                          } | ||||
|                          console.log("load single download") | ||||
| 
 | ||||
|                           | ||||
|                          // Extract the filename from the Content-Disposition header, if present | ||||
|                         let filename = null; | ||||
|                         const contentDispositionHeader = response.headers.get('Content-Disposition'); | ||||
|                         console.log(contentDispositionHeader) | ||||
|                         if (contentDispositionHeader && contentDispositionHeader.indexOf('attachment') !== -1) { | ||||
|                         	filename = contentDispositionHeader.split('filename=')[1].replace(/"/g, ''); | ||||
|                         } else { | ||||
|                             // If the Content-Disposition header is not present or does not contain the filename, use a default filename | ||||
|                             filename = 'download'; | ||||
|                         } | ||||
|                         console.log("filename=" + filename) | ||||
| 
 | ||||
|                 $('form').submit(function(event) { | ||||
|                      | ||||
|                         const contentType = response.headers.get('Content-Type'); | ||||
|                         console.log("contentType=" + contentType) | ||||
|                          // Check if the response is a PDF or an image | ||||
|                          if (contentType.includes('pdf') || contentType.includes('image')) { | ||||
|                         	 response.blob().then(blob => { | ||||
|                                  console.log("pdf/image") | ||||
|                     const boredWaiting = localStorage.getItem('boredWaiting'); | ||||
|                     if (boredWaiting === 'enabled') { | ||||
|                           $('#show-game-btn').show(); | ||||
|                     } | ||||
|                     var processing = "Processing..." | ||||
|                     var submitButtonText = $('#submitBtn').text() | ||||
|                          | ||||
|                     $('#submitBtn').text('Processing...'); | ||||
|                     console.log("start download code") | ||||
|                     var files = $('#fileInput-input')[0].files; | ||||
|                     var url = this.action; | ||||
|                     console.log(url) | ||||
|                     event.preventDefault(); // Prevent the default form handling behavior | ||||
|                      /* Check if ${multiple} is false */ | ||||
|                      var multiple = [[${multiple}]] || false; | ||||
|                      var override = $('#override').val() || ''; | ||||
|                      console.log("override=" + override) | ||||
|                      if (override === 'multi' || (!multiple && files.length > 1) && override !== 'single' ) { | ||||
|                          console.log("multi parallel download") | ||||
|                          submitMultiPdfForm(event,url); | ||||
|                      } else { | ||||
|                          console.log("start single download") | ||||
|      | ||||
|                                  // Perform the appropriate action based on the download option | ||||
|                                  if (downloadOption === 'sameWindow') { | ||||
|                                      console.log("same window") | ||||
|                          // Get the selected download option from localStorage | ||||
|                          const downloadOption = localStorage.getItem('downloadOption'); | ||||
|      | ||||
|                                      // Open the file in the same window | ||||
|                                      window.location.href = URL.createObjectURL(blob); | ||||
|                                  } else if (downloadOption === 'newWindow') { | ||||
|                                      console.log("new window") | ||||
|                          var formData = new FormData($('form')[0]); | ||||
|                           | ||||
|                          // Send the request to the server using the fetch() API | ||||
|                          fetch(url, { | ||||
|                              method: 'POST', | ||||
|                              body: formData | ||||
|                          }).then(response => { | ||||
|                              if (!response) { | ||||
|                                  throw new Error('Received null response for file ' + i); | ||||
|                              } | ||||
|                              console.log("load single download") | ||||
|      | ||||
|                                      // Open the file in a new window | ||||
|                                      window.open(URL.createObjectURL(blob), '_blank'); | ||||
|                                  } else { | ||||
|                                      console.log("else save") | ||||
|                               | ||||
|                              // Extract the filename from the Content-Disposition header, if present | ||||
|                             let filename = null; | ||||
|                             const contentDispositionHeader = response.headers.get('Content-Disposition'); | ||||
|                             console.log(contentDispositionHeader) | ||||
|                             if (contentDispositionHeader && contentDispositionHeader.indexOf('attachment') !== -1) { | ||||
|                                 filename = decodeURIComponent(contentDispositionHeader.split('filename=')[1].replace(/"/g, '')); | ||||
|                             } else { | ||||
|                                 // If the Content-Disposition header is not present or does not contain the filename, use a default filename | ||||
|                                 filename = 'download'; | ||||
|                             } | ||||
|                             console.log("filename=" + filename) | ||||
|      | ||||
|                                      // Download the file | ||||
|                          | ||||
|                             const contentType = response.headers.get('Content-Type'); | ||||
|                             console.log("contentType=" + contentType) | ||||
|                              // Check if the response is a PDF or an image | ||||
|                              if (contentType.includes('pdf') || contentType.includes('image')) { | ||||
|                                  response.blob().then(blob => { | ||||
|                                      console.log("pdf/image") | ||||
|          | ||||
|                                      // Perform the appropriate action based on the download option | ||||
|                                      if (downloadOption === 'sameWindow') { | ||||
|                                          console.log("same window") | ||||
|          | ||||
|                                          // Open the file in the same window | ||||
|                                          window.location.href = URL.createObjectURL(blob); | ||||
|                                      } else if (downloadOption === 'newWindow') { | ||||
|                                          console.log("new window") | ||||
|          | ||||
|                                          // Open the file in a new window | ||||
|                                          window.open(URL.createObjectURL(blob), '_blank'); | ||||
|                                      } else { | ||||
|                                          console.log("else save") | ||||
|          | ||||
|                                          // Download the file | ||||
|                                          const link = document.createElement('a'); | ||||
|                                          link.href = URL.createObjectURL(blob); | ||||
|                                          link.download = filename; | ||||
|                                          link.click(); | ||||
|                                      } | ||||
|                                  }); | ||||
|                              } else if (contentType.includes('json')) { | ||||
|                                 // Handle the JSON response | ||||
|                                     response.json().then(data => { | ||||
|                                       // Format the error message | ||||
|                                       const errorMessage = JSON.stringify(data, null, 2); | ||||
|                                        | ||||
|                                       // Display the error message in an alert | ||||
|                                       alert(`An error occurred: ${errorMessage}`); | ||||
|                                     }); | ||||
|                                   | ||||
|                              } else { | ||||
|                                  response.blob().then(blob => { | ||||
|                                      console.log("else save 2 zip") | ||||
|          | ||||
|                                      // For ZIP files or other file types, just download the file | ||||
|                                      const link = document.createElement('a'); | ||||
|                                      link.href = URL.createObjectURL(blob); | ||||
|                                      link.download = filename; | ||||
|                                      link.click(); | ||||
|                                  } | ||||
|                         	 }); | ||||
|                          } else if (contentType.includes('json')) { | ||||
|                         	// Handle the JSON response | ||||
|                         	    response.json().then(data => { | ||||
|                         	      // Format the error message | ||||
|                         	      const errorMessage = JSON.stringify(data, null, 2); | ||||
|                         	       | ||||
|                         	      // Display the error message in an alert | ||||
|                         	      alert(`An error occurred: ${errorMessage}`); | ||||
|                         	    }); | ||||
|                         	  | ||||
|                          } else { | ||||
|                         	 response.blob().then(blob => { | ||||
|                                  console.log("else save 2 zip") | ||||
|                                  }); | ||||
|                              } | ||||
|                         }) | ||||
|                          .catch(error => { | ||||
|                              console.log("error listener") | ||||
|      | ||||
|                                  // For ZIP files or other file types, just download the file | ||||
|                                  const link = document.createElement('a'); | ||||
|                                  link.href = URL.createObjectURL(blob); | ||||
|                                  link.download = filename; | ||||
|                                  link.click(); | ||||
|                         	 }); | ||||
|                          } | ||||
|                     }) | ||||
|                      .catch(error => { | ||||
|                          console.log("error listener") | ||||
| 
 | ||||
|                          // Extract the error message and stack trace from the response | ||||
|                          const errorMessage = error.message; | ||||
|                          const stackTrace = error.stack; | ||||
| 
 | ||||
|                          // Create an error message to display to the user | ||||
|                          const message = `${errorMessage}\n\n${stackTrace}`; | ||||
| 						 | ||||
|                          $('#submitBtn').text(submitButtonText); | ||||
|                              // Extract the error message and stack trace from the response | ||||
|                              const errorMessage = error.message; | ||||
|                              const stackTrace = error.stack; | ||||
|      | ||||
|                              // Create an error message to display to the user | ||||
|                              const message = `${errorMessage}\n\n${stackTrace}`; | ||||
|                              | ||||
|                              $('#submitBtn').text(submitButtonText); | ||||
|                               | ||||
|                              // Display the error message to the user | ||||
|                              alert(message); | ||||
|                               | ||||
|                          }); | ||||
|                           | ||||
|                          // Display the error message to the user | ||||
|                          alert(message); | ||||
|                           | ||||
|                      }); | ||||
|                       | ||||
|                  } | ||||
|                  $('#submitBtn').text(submitButtonText); | ||||
|              }); | ||||
| 
 | ||||
|                      } | ||||
|                      $('#submitBtn').text(submitButtonText); | ||||
|                  }); | ||||
|             }); | ||||
|             async function submitMultiPdfForm(event, url) { | ||||
|                 // Get the selected PDF files | ||||
|                 let files = $('#fileInput-input')[0].files; | ||||
| @ -272,7 +314,7 @@ document.addEventListener("DOMContentLoaded", function () { | ||||
|                             if (!contentDisposition) { | ||||
|                                 //throw new Error('Content-Disposition header not found for file ' + i); | ||||
|                             } else { | ||||
|                             	fileName = contentDisposition.split('filename=')[1].replace(/"/g, ''); | ||||
|                                 fileName = contentDisposition.split('filename=')[1].replace(/"/g, ''); | ||||
|                             } | ||||
|                             console.log('Received response for file ' + i + ': ' + response); | ||||
| 
 | ||||
| @ -346,10 +388,10 @@ document.addEventListener("DOMContentLoaded", function () { | ||||
|                 } | ||||
|             } | ||||
|             function updateProgressBar(progressBar, files) { | ||||
|             	let progress = ((progressBar.attr('aria-valuenow') / files.length) * 100) + (100 / files.length); | ||||
|             	progressBar.css('width', progress + '%'); | ||||
|             	progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1); | ||||
|         	} | ||||
|                 let progress = ((progressBar.attr('aria-valuenow') / files.length) * 100) + (100 / files.length); | ||||
|                 progressBar.css('width', progress + '%'); | ||||
|                 progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1); | ||||
|             } | ||||
| 
 | ||||
|              | ||||
|              | ||||
| @ -357,6 +399,92 @@ document.addEventListener("DOMContentLoaded", function () { | ||||
|             </script> | ||||
|              | ||||
|              | ||||
|              | ||||
|              | ||||
|     <div class="custom-file-chooser"> | ||||
|   <div class="custom-file"> | ||||
|     <input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple> | ||||
|     <label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label> | ||||
|   </div> | ||||
|   <div class="selected-files"></div> | ||||
| </div> | ||||
| <br> | ||||
|     <div id="progressBarContainer" style="display: none; position: relative;"> | ||||
|                 <div class="progress" style="height: 1rem;"> | ||||
|                     <div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"> | ||||
|                         <span class="sr-only">Loading...</span> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
| <button type="button" class="btn btn-primary" id="show-game-btn" style="display:none;">Bored waiting?</button> | ||||
| 
 | ||||
| <div id="game-container-wrapper" class="game-container-wrapper" style="display: none;"> | ||||
|               <div id="game-container"> | ||||
|                 <div id="lives">Lives: 3</div> | ||||
|                 <div id="score">Score: 0</div> | ||||
|                 <div id="high-score">High Score: 0</div> | ||||
|                 <div id="level">Level: 1</div> | ||||
|                 <img src="favicon.svg" class="player" id="player"> | ||||
|             </div> | ||||
|               <style> | ||||
|                 #game-container { | ||||
|                     position: relative; | ||||
|                     width: 100%; | ||||
|                     height: 0; | ||||
|                     padding-bottom: 75%; /* 4:3 aspect ratio */ | ||||
|                     background-color: transparent; | ||||
|                     margin: auto; | ||||
|                     overflow: hidden; | ||||
|                     border: 2px solid black; /* Add border */ | ||||
|                 } | ||||
| 
 | ||||
|                 .pdf, .player, .projectile { | ||||
|                     position: absolute; | ||||
|                 } | ||||
|                 .pdf { | ||||
|                     width: 50px; | ||||
|                     height: 50px; | ||||
|                 } | ||||
|                 .player { | ||||
|                     width: 50px; | ||||
|                     height: 50px; | ||||
|                 } | ||||
|                 .projectile { | ||||
|                     background-color: black  !important; | ||||
|                     width: 5px; | ||||
|                     height: 10px; | ||||
|                 } | ||||
|               #score, #level, #lives, #high-score { | ||||
|     color: black; | ||||
|     font-family: sans-serif; | ||||
|     position: absolute; | ||||
|     font-size: calc(14px + 0.25vw); /* Reduced font size */ | ||||
| } | ||||
| #score { | ||||
|     top: 10px; | ||||
|     left: 10px; | ||||
| } | ||||
| #lives { | ||||
|     top: 10px; | ||||
|     left: calc(7vw); /* Adjusted position */ | ||||
| } | ||||
| #high-score { | ||||
|     top: 10px; | ||||
|     left: calc(14vw); /* Adjusted position */ | ||||
| } | ||||
| #level { | ||||
|     top: 10px; | ||||
|     right: 10px; | ||||
| } | ||||
| 
 | ||||
|               </style> | ||||
|             </div> | ||||
|   | ||||
|            | ||||
|            | ||||
|            | ||||
|              | ||||
|              | ||||
|     <script th:inline="javascript"> | ||||
|      | ||||
|     document.addEventListener('DOMContentLoaded', function () { | ||||
|  | ||||
| @ -138,7 +138,7 @@ function compareVersions(version1, version2) { | ||||
|                 <ul class="navbar-nav mr-auto flex-nowrap"> | ||||
|                      | ||||
|                     <li class="nav-item"> | ||||
|                         <a class="nav-link" href="#" th:href="@{multi-tool}" th:classappend="${currentPage}=='multi-tool' ? 'active' : ''"> | ||||
|                         <a class="nav-link" href="#" th:href="@{multi-tool}" th:classappend="${currentPage}=='multi-tool' ? 'active' : ''" th:title="#{home.multiTool.desc}"> | ||||
|                             <img class="icon" src="images/tools.svg" alt="icon"> | ||||
|                             <span class="icon-text"  th:text="#{home.multiTool.title}"></span> | ||||
|                         </a> | ||||
| @ -151,23 +151,23 @@ function compareVersions(version1, version2) { | ||||
|     </a> | ||||
|     <div class="dropdown-menu" aria-labelledby="navbarDropdown"> | ||||
|         <!-- Existing menu items --> | ||||
|         <a class="dropdown-item" href="#" th:href="@{merge-pdfs}" th:classappend="${currentPage}=='merge-pdfs' ? 'active' : ''"> | ||||
|         <a class="dropdown-item" href="#" th:href="@{merge-pdfs}" th:classappend="${currentPage}=='merge-pdfs' ? 'active' : ''" th:title="#{home.merge.desc}"> | ||||
|             <img class="icon" src="images/union.svg" alt="icon"> | ||||
|             <span class="icon-text"  th:text="#{home.merge.title}"></span> | ||||
|         </a> | ||||
|         <a class="dropdown-item" href="#" th:href="@{split-pdfs}" th:classappend="${currentPage}=='split-pdfs' ? 'active' : ''"> | ||||
|         <a class="dropdown-item" href="#" th:href="@{split-pdfs}" th:classappend="${currentPage}=='split-pdfs' ? 'active' : ''" th:title="#{home.split.desc}"> | ||||
|             <img class="icon" src="images/layout-split.svg" alt="icon"> | ||||
|             <span class="icon-text"  th:text="#{home.split.title}"></span> | ||||
|         </a> | ||||
|         <a class="dropdown-item" href="#" th:href="@{pdf-organizer}" th:classappend="${currentPage}=='pdf-organizer' ? 'active' : ''"> | ||||
|         <a class="dropdown-item" href="#" th:href="@{pdf-organizer}" th:classappend="${currentPage}=='pdf-organizer' ? 'active' : ''" th:title="#{home.pdfOrganiser.desc}"> | ||||
|             <img class="icon" src="images/sort-numeric-down.svg" alt="icon"> | ||||
|             <span class="icon-text"  th:text="#{home.pdfOrganiser.title}"></span> | ||||
|         </a> | ||||
|         <a class="dropdown-item" href="#" th:href="@{rotate-pdf}" th:classappend="${currentPage}=='rotate-pdf' ? 'active' : ''"> | ||||
|         <a class="dropdown-item" href="#" th:href="@{rotate-pdf}" th:classappend="${currentPage}=='rotate-pdf' ? 'active' : ''" th:title="#{home.rotate.desc}"> | ||||
|             <img class="icon" src="images/arrow-clockwise.svg" alt="icon"> | ||||
|             <span class="icon-text"  th:text="#{home.rotate.title}"></span> | ||||
|         </a> | ||||
|         <a class="dropdown-item" href="#" th:href="@{remove-pages}" th:classappend="${currentPage}=='remove-pages' ? 'active' : ''"> | ||||
|         <a class="dropdown-item" href="#" th:href="@{remove-pages}" th:classappend="${currentPage}=='remove-pages' ? 'active' : ''" th:title="#{home.removePages.desc}"> | ||||
|             <img class="icon" src="images/file-earmark-x.svg" alt="icon"> | ||||
|             <span class="icon-text"  th:text="#{home.removePages.title}"></span> | ||||
|         </a> | ||||
| @ -181,51 +181,51 @@ function compareVersions(version1, version2) { | ||||
|                     </a> | ||||
|                         <div class="dropdown-menu" aria-labelledby="navbarDropdown"> | ||||
|                             <!-- Existing menu items --> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{img-to-pdf}" th:classappend="${currentPage}=='img-to-pdf' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{img-to-pdf}" th:classappend="${currentPage}=='img-to-pdf' ? 'active' : ''" th:title="#{home.imageToPdf.desc}"> | ||||
|                                 <img class="icon" src="images/image.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> | ||||
|                                 <span class="icon-text"  th:text="#{home.imageToPdf.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{file-to-pdf}" th:classappend="${currentPage}=='file-to-pdf' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{file-to-pdf}" th:classappend="${currentPage}=='file-to-pdf' ? 'active' : ''" th:title="#{home.fileToPDF.desc}"> | ||||
|                                 <img class="icon" src="images/file.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> | ||||
|                                 <span class="icon-text"  th:text="#{home.fileToPDF.title}"></span> | ||||
|                             </a> | ||||
|                         	<hr class="dropdown-divider"> | ||||
|                            <a class="dropdown-item" href="#" th:href="@{pdf-to-img}" th:classappend="${currentPage}=='pdf-to-img' ? 'active' : ''"> | ||||
|                            <a class="dropdown-item" href="#" th:href="@{pdf-to-img}" th:classappend="${currentPage}=='pdf-to-img' ? 'active' : ''" th:title="#{home.pdfToImage.desc}"> | ||||
|                             | ||||
|                          | ||||
|                                 <img class="icon" src="images/image.svg" alt="icon"> | ||||
|                                 <span class="icon-text" th:text="#{home.pdfToImage.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-word}" th:classappend="${currentPage}=='pdf-to-word' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-word}" th:classappend="${currentPage}=='pdf-to-word' ? 'active' : ''" th:title="#{home.PDFToWord.desc}"> | ||||
|                        | ||||
|                         | ||||
|                                 <img class="icon" src="images/file-earmark-word.svg" alt="icon"> | ||||
|                                 <span class="icon-text" th:text="#{home.PDFToWord.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-presentation}" th:classappend="${currentPage}=='pdf-to-presentation' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-presentation}" th:classappend="${currentPage}=='pdf-to-presentation' ? 'active' : ''" th:title="#{home.PDFToPresentation.desc}"> | ||||
|                              | ||||
|                     | ||||
|                                 <img class="icon" src="images/file-earmark-ppt.svg" alt="icon"> | ||||
|                                 <span class="icon-text" th:text="#{home.PDFToPresentation.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-text}" th:classappend="${currentPage}=='pdf-to-text' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-text}" th:classappend="${currentPage}=='pdf-to-text' ? 'active' : ''" th:title="#{home.PDFToText.desc}"> | ||||
|                           | ||||
|                         | ||||
|                                 <img class="icon" src="images/filetype-txt.svg" alt="icon"> | ||||
|                                 <span class="icon-text" th:text="#{home.PDFToText.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-html}" th:classappend="${currentPage}=='pdf-to-html' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-html}" th:classappend="${currentPage}=='pdf-to-html' ? 'active' : ''" th:title="#{home.PDFToHTML.desc}"> | ||||
|                            | ||||
|                               | ||||
|                                 <img class="icon" src="images/filetype-html.svg" alt="icon"> | ||||
|                                 <span class="icon-text" th:text="#{home.PDFToHTML.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-xml}" th:classappend="${currentPage}=='pdf-to-xml' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-xml}" th:classappend="${currentPage}=='pdf-to-xml' ? 'active' : ''" th:title="#{home.PDFToXML.desc}"> | ||||
|                            | ||||
|                                 <img class="icon" src="images/filetype-xml.svg" alt="icon"> | ||||
|                                 <span class="icon-text" th:text="#{home.PDFToXML.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-pdfa}" th:classappend="${currentPage}=='pdf-to-pdfa' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{pdf-to-pdfa}" th:classappend="${currentPage}=='pdf-to-pdfa' ? 'active' : ''" th:title="#{home.pdfToPDFA.desc}"> | ||||
|            | ||||
|                                 <img class="icon" src="images/file-earmark-pdf.svg" alt="icon"> | ||||
|                                 <span class="icon-text" th:text="#{home.pdfToPDFA.title}"></span> | ||||
| @ -243,16 +243,16 @@ function compareVersions(version1, version2) { | ||||
|                         <img class="icon" src="images/shield-check.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{navbar.security}"></span> | ||||
|                     </a> | ||||
|                     <div class="dropdown-menu" aria-labelledby="navbarDropdown"> | ||||
|                         <a class="dropdown-item" href="#" th:href="@{add-password}" th:classappend="${currentPage}=='add-password' ? 'active' : ''"> | ||||
|                         <a class="dropdown-item" href="#" th:href="@{add-password}" th:classappend="${currentPage}=='add-password' ? 'active' : ''" th:title="#{home.addPassword.desc}"> | ||||
|                             <img class="icon" src="images/lock.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{home.addPassword.title}"></span> | ||||
|                         </a> | ||||
|                         <a class="dropdown-item" href="#" th:href="@{remove-password}" th:classappend="${currentPage}=='remove-password' ? 'active' : ''"> | ||||
|                         <a class="dropdown-item" href="#" th:href="@{remove-password}" th:classappend="${currentPage}=='remove-password' ? 'active' : ''" th:title="#{home.removePassword.desc}"> | ||||
|                             <img class="icon" src="images/unlock.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{home.removePassword.title}"></span> | ||||
|                         </a> | ||||
|                         <a class="dropdown-item" href="#" th:href="@{change-permissions}" th:classappend="${currentPage}=='change-permissions' ? 'active' : ''"> | ||||
|                         <a class="dropdown-item" href="#" th:href="@{change-permissions}" th:classappend="${currentPage}=='change-permissions' ? 'active' : ''" th:title="#{home.permissions.desc}"> | ||||
|                             <img class="icon" src="images/shield-lock.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{home.permissions.title}"></span> | ||||
|                         </a> | ||||
|                         <a class="dropdown-item" href="#" th:href="@{add-watermark}" th:classappend="${currentPage}=='add-watermark' ? 'active' : ''"> | ||||
|                         <a class="dropdown-item" href="#" th:href="@{add-watermark}" th:classappend="${currentPage}=='add-watermark' ? 'active' : ''" th:title="#{home.watermark.desc}"> | ||||
|                             <img class="icon" src="images/droplet.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{home.watermark.title}"></span> | ||||
|                         </a> | ||||
|                     </div> | ||||
| @ -266,21 +266,24 @@ function compareVersions(version1, version2) { | ||||
|                          | ||||
|                         </a> | ||||
|                         <div class="dropdown-menu" aria-labelledby="navbarDropdown"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{ocr-pdf}" th:classappend="${currentPage}=='ocr-pdf' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{ocr-pdf}" th:classappend="${currentPage}=='ocr-pdf' ? 'active' : ''" th:title="#{home.ocr.desc}"> | ||||
|                                 <img class="icon" src="images/search.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{home.ocr.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{add-image}" th:classappend="${currentPage}=='add-image' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{add-image}" th:classappend="${currentPage}=='add-image' ? 'active' : ''" th:title="#{home.addImage.desc}"> | ||||
|                                 <img class="icon" src="images/file-earmark-richtext.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{home.addImage.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''" th:title="#{home.compressPdfs.desc}"> | ||||
|                                 <img class="icon" src="images/file-zip.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{home.compressPdfs.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{extract-images}" th:classappend="${currentPage}=='extract-images' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{extract-images}" th:classappend="${currentPage}=='extract-images' ? 'active' : ''" th:title="#{home.extractImages.desc}"> | ||||
|                                 <img class="icon" src="images/images.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{home.extractImages.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{change-metadata}" th:classappend="${currentPage}=='change-metadata' ? 'active' : ''"> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{change-metadata}" th:classappend="${currentPage}=='change-metadata' ? 'active' : ''" th:title="#{home.changeMetadata.desc}"> | ||||
|                                 <img class="icon" src="images/clipboard-data.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{home.changeMetadata.title}"></span> | ||||
|                             </a> | ||||
|                             <a class="dropdown-item" href="#" th:href="@{extract-image-scans}" th:classappend="${currentPage}=='extract-image-scans' ? 'active' : ''" th:title="#{home.ScannerImageSplit.desc}"> | ||||
|                                 <img class="icon" src="images/scanner.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{home.ScannerImageSplit.title}"></span> | ||||
|                             </a>                 | ||||
|                         </div> | ||||
|                     </li> | ||||
| 
 | ||||
| @ -364,6 +367,12 @@ function compareVersions(version1, version2) { | ||||
|                     <input type="range" class="custom-range" min="1" max="9" step="1" id="zipThreshold" value="4"> | ||||
|                     <span id="zipThresholdValue" class="ml-2"></span> | ||||
|                 </div> | ||||
|                 <div class="form-group"> | ||||
|                   <div class="custom-control custom-checkbox"> | ||||
|                     <input type="checkbox" class="custom-control-input" id="boredWaiting"> | ||||
|                     <label class="custom-control-label" for="boredWaiting">Bored Waiting? :)</label> | ||||
|                   </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="modal-footer"> | ||||
|                 <button type="button" class="btn btn-secondary" data-dismiss="modal" th:text="#{close}"></button> | ||||
| @ -381,6 +390,7 @@ function compareVersions(version1, version2) { | ||||
| 					// Set the selected option in the dropdown | ||||
| 					document.getElementById('downloadOption').value = downloadOption; | ||||
| 
 | ||||
| 					 | ||||
| 					// Save the selected option to local storage when the dropdown value changes | ||||
| 					document.getElementById('downloadOption').addEventListener( | ||||
| 							'change', | ||||
| @ -398,6 +408,8 @@ function compareVersions(version1, version2) { | ||||
| 					document.getElementById('zipThreshold').value = zipThreshold; | ||||
| 					document.getElementById('zipThresholdValue').textContent = zipThreshold; | ||||
| 
 | ||||
| 					 | ||||
| 					 | ||||
| 					// Save the selected value to local storage when the slider value changes | ||||
| 					document.getElementById('zipThreshold').addEventListener('input', function () { | ||||
| 					    zipThreshold = this.value; | ||||
| @ -405,6 +417,15 @@ function compareVersions(version1, version2) { | ||||
| 					    localStorage.setItem('zipThreshold', zipThreshold); | ||||
| 					}); | ||||
| 
 | ||||
| 					 | ||||
| 					var boredWaiting = localStorage.getItem('boredWaiting') || 'disabled'; | ||||
| 					document.getElementById('boredWaiting').checked = boredWaiting === 'enabled'; | ||||
| 					 | ||||
| 					document.getElementById('boredWaiting').addEventListener('change', function() { | ||||
| 					  boredWaiting = this.checked ? 'enabled' : 'disabled'; | ||||
| 					  localStorage.setItem('boredWaiting', boredWaiting); | ||||
| 					}); | ||||
| 
 | ||||
| 				</script> | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -75,42 +75,39 @@ filter: invert(0.2) sepia(2) saturate(50) hue-rotate(190deg); | ||||
| 
 | ||||
|             <!-- Features --> | ||||
|             <div class="features-container container"> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', svgPath='images/tools.svg')}"></div> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs', svgPath='images/union.svg')}"></div> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs', svgPath='images/layout-split.svg')}"></div> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.rotate.title}, cardText=#{home.rotate.desc}, cardLink='rotate-pdf', svgPath='images/arrow-clockwise.svg')}"></div> | ||||
| 
 | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.imageToPdf.title}, cardText=#{home.imageToPdf.desc}, cardLink='img-to-pdf', svgPath='images/image.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfToImage.title}, cardText=#{home.pdfToImage.desc}, cardLink='pdf-to-img', svgPath='images/image.svg')}"></div> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfOrganiser.title}, cardText=#{home.pdfOrganiser.desc}, cardLink='pdf-organizer', svgPath='images/sort-numeric-down.svg')}"></div> | ||||
| 
 | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.addImage.title}, cardText=#{home.addImage.desc}, cardLink='add-image', svgPath='images/file-earmark-richtext.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.watermark.title}, cardText=#{home.watermark.desc}, cardLink='add-watermark', svgPath='images/droplet.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.fileToPDF.title}, cardText=#{home.fileToPDF.desc}, cardLink='file-to-pdf', svgPath='images/file.svg')}"></div> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.fileToPDF.title}, cardText=#{home.fileToPDF.desc}, cardLink='file-to-pdf', svgPath='images/file.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.removePages.title}, cardText=#{home.removePages.desc}, cardLink='remove-pages', svgPath='images/file-earmark-x.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.addPassword.title}, cardText=#{home.addPassword.desc}, cardLink='add-password', svgPath='images/lock.svg')}"></div> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.removePassword.title}, cardText=#{home.removePassword.desc}, cardLink='remove-password', svgPath='images/unlock.svg')}"></div> | ||||
| 
 | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf', svgPath='images/file-zip.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.changeMetadata.title}, cardText=#{home.changeMetadata.desc}, cardLink='change-metadata', svgPath='images/clipboard-data.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions', svgPath='images/shield-lock.svg')}"></div> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions', svgPath='images/shield-lock.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.ocr.title}, cardText=#{home.ocr.desc}, cardLink='ocr-pdf', svgPath='images/search.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.extractImages.title}, cardText=#{home.extractImages.desc}, cardLink='extract-images', svgPath='images/images.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfToPDFA.title}, cardText=#{home.pdfToPDFA.desc}, cardLink='pdf-to-pdfa', svgPath='images/file-earmark-pdf.svg')}"></div> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.pdfToPDFA.title}, cardText=#{home.pdfToPDFA.desc}, cardLink='pdf-to-pdfa', svgPath='images/file-earmark-pdf.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.PDFToWord.title}, cardText=#{home.PDFToWord.desc}, cardLink='pdf-to-word', svgPath='images/file-earmark-word.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.PDFToPresentation.title}, cardText=#{home.PDFToPresentation.desc}, cardLink='pdf-to-presentation', svgPath='images/file-earmark-ppt.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.PDFToText.title}, cardText=#{home.PDFToText.desc}, cardLink='pdf-to-text', svgPath='images/filetype-txt.svg')}"></div> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.PDFToText.title}, cardText=#{home.PDFToText.desc}, cardLink='pdf-to-text', svgPath='images/filetype-txt.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.PDFToHTML.title}, cardText=#{home.PDFToHTML.desc}, cardLink='pdf-to-html', svgPath='images/filetype-html.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.PDFToXML.title}, cardText=#{home.PDFToXML.desc}, cardLink='pdf-to-xml', svgPath='images/filetype-xml.svg')}"></div> | ||||
|                  | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(cardTitle=#{home.ScannerImageSplit.title}, cardText=#{home.ScannerImageSplit.desc}, cardLink='extract-image-scans', svgPath='images/scanner.svg')}"></div> | ||||
|                  | ||||
|                  | ||||
|                  | ||||
|  | ||||
							
								
								
									
										32
									
								
								src/main/resources/templates/other/adjust-contrast.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/main/resources/templates/other/adjust-contrast.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| <!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=#{extractImages.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"> | ||||
|         <div class="row justify-content-center"> | ||||
|           <div class="col-md-6"> | ||||
|             <h2 th:text="#{extractImages.header}"></h2> | ||||
| 
 | ||||
|             <form id="multiPdfForm" th:action="@{adjust-contrast}" method="post" enctype="multipart/form-data"> | ||||
|               <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div> | ||||
|                  <div class="form-group"> | ||||
|                     <label for="contrastRange">Contrast</label> | ||||
|                     <input name="contrastRange" type="range" class="form-control-range" id="contrastRange" min="-100" max="100" value="0" step="1"> | ||||
|                 </div> | ||||
|               <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{extractImages.submit}"></button> | ||||
|             </form> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div th:insert="~{fragments/footer.html :: footer}"></div> | ||||
|   </div> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										54
									
								
								src/main/resources/templates/other/extract-image-scans.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/main/resources/templates/other/extract-image-scans.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| <!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=#{home.ScannerImageSplit.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"> | ||||
|         <div class="row justify-content-center"> | ||||
|           <div class="col-md-6"> | ||||
|             <h2 th:text="#{home.ScannerImageSplit.title}"></h2> | ||||
| 
 | ||||
|             <form id="multiPdfForm" th:action="@{extract-image-scans}" method="post" enctype="multipart/form-data"> | ||||
|               <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='image/*, application/pdf')}"></div> | ||||
|               <div class="form-group"> | ||||
|                 <label for="angleThreshold" th:text="#{ScannerImageSplit.selectText.1}"></label> | ||||
|                 <input type="number" class="form-control" id="angleThreshold" name="angle_threshold" value="5"> | ||||
|                 <small id="angleThresholdHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.2}"></small> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label for="tolerance" th:text="#{ScannerImageSplit.selectText.3}"></label> | ||||
|                 <input type="number" class="form-control" id="tolerance" name="tolerance" value="20"> | ||||
|                 <small id="toleranceHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.4}"></small> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label for="minArea" th:text="#{ScannerImageSplit.selectText.5}"></label> | ||||
|                 <input type="number" class="form-control" id="minArea" name="min_area" value="8000"> | ||||
|                 <small id="minAreaHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.6}"></small> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label for="minContourArea" th:text="#{ScannerImageSplit.selectText.7}"></label> | ||||
|                 <input type="number" class="form-control" id="minContourArea" name="min_contour_area" value="500"> | ||||
|                 <small id="minContourAreaHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.8}"></small> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label for="borderSize" th:text="#{ScannerImageSplit.selectText.9}"></label> | ||||
|                 <input type="number" class="form-control" id="borderSize" name="border_size" value="1"> | ||||
|                 <small id="borderSizeHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.10}"></small> | ||||
|               </div> | ||||
|               <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{genericSubmit}"></button> | ||||
|             </form> | ||||
| 
 | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div th:insert="~{fragments/footer.html :: footer}"></div> | ||||
|   </div> | ||||
| </body> | ||||
| </html> | ||||
| @ -20,9 +20,9 @@ | ||||
|                                 <label for="languages" class="form-label" th:text="#{ocr.selectText.1}"></label> | ||||
|                                 <hr> | ||||
|                                 <div id="languages"> | ||||
|                                     <div th:each="language, iterStat : ${languages}" > | ||||
|                                         <input type="checkbox" class="form-check-input" th:name="languages" th:value="${language}" th:id="${'language-' + language}" /> | ||||
|                                         <label class="form-check-label" th:for="${'language-' + language}" th:text="  ${language}"></label> | ||||
|                                     <div th:each="language, iterStat : ${languages}"> | ||||
|                                         <input type="checkbox" th:name="languages" th:value="${language}" th:id="${'language-' + language}" /> | ||||
|                                         <label class="form-check-label" th:for="${'language-' + language}" th:text="${(language == 'eng') ? 'English' : language}"></label> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                                 <hr> | ||||
| @ -53,6 +53,19 @@ | ||||
|                                 <input type="checkbox" class="form-check-input" name="clean-final" id="clean-final" /> | ||||
|                                 <label class="form-check-label" for="clean-final" th:text="#{ocr.selectText.5}"></label> | ||||
|                             </div> | ||||
|                             <div class="form-check"> | ||||
|                                 <input type="checkbox" class="form-check-input" name="removeImagesAfter" id="removeImagesAfter" /> | ||||
|                                 <label class="form-check-label" for="removeImagesAfter" th:text="#{ocr.selectText.11}"></label> | ||||
|                             </div> | ||||
|                              | ||||
|                              | ||||
|                             <div class="form-group"> | ||||
|                                 <label th:text="#{ocr.selectText.12}"></label>  | ||||
|                                 <select class="form-control" name="ocrRenderType"> | ||||
|                                     <option value="hocr">HOCR (Latin/Roman alphabet only)</option> | ||||
|                                     <option value="sandwich">Sandwich</option> | ||||
|                                 </select> | ||||
|                             </div> | ||||
|                             <br> | ||||
|                             <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{ocr.submit}"></button> | ||||
|                         </form> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user