utf8 bug fix and scan pages (#113)
This commit is contained in:
parent
2d4aff3b08
commit
5bee714437
52 changed files with 2493 additions and 1341 deletions
|
@ -1,5 +1,9 @@
|
||||||
# Build jbig2enc in a separate stage
|
# 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 the application JAR file
|
||||||
COPY build/libs/*.jar app.jar
|
COPY build/libs/*.jar app.jar
|
||||||
|
@ -13,7 +17,8 @@ ENV APP_HOME_NAME="Stirling PDF"
|
||||||
#ENV APP_NAVBAR_NAME="Stirling PDF"
|
#ENV APP_NAVBAR_NAME="Stirling PDF"
|
||||||
|
|
||||||
# Run the application
|
# 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 && \
|
||||||
make install
|
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 && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
libreoffice-core \
|
libreoffice-core \
|
||||||
|
@ -33,12 +32,31 @@ RUN apt-get update && \
|
||||||
libreoffice-calc \
|
libreoffice-calc \
|
||||||
libreoffice-impress \
|
libreoffice-impress \
|
||||||
python3-uno \
|
python3-uno \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
unoconv \
|
unoconv \
|
||||||
pngquant \
|
pngquant \
|
||||||
unpaper \
|
unpaper \
|
||||||
ocrmypdf && \
|
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
|
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.
|
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`
|
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
|
#### 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.
|
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.
|
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)
|
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'
|
group = 'stirling.software'
|
||||||
version = '0.6.0'
|
version = '0.7.0'
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -19,11 +19,11 @@ dependencies {
|
||||||
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
|
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
|
||||||
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
|
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
|
||||||
implementation 'commons-io:commons-io:2.11.0'
|
implementation 'commons-io:commons-io:2.11.0'
|
||||||
|
|
||||||
|
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
|
||||||
|
|
||||||
//general PDF
|
//general PDF
|
||||||
implementation 'org.apache.pdfbox:pdfbox:2.0.28'
|
implementation 'org.apache.pdfbox:pdfbox:2.0.28'
|
||||||
|
|
||||||
implementation 'com.itextpdf:itextpdf:5.5.13.3'
|
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
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
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(localeChangeInterceptor());
|
registry.addInterceptor(localeChangeInterceptor());
|
||||||
|
registry.addInterceptor(new CleanUrlInterceptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
@RestController
|
||||||
|
public class MergeController {
|
||||||
@Controller
|
|
||||||
public class MergeController {
|
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
||||||
|
// Create a new empty document
|
||||||
@GetMapping("/merge-pdfs")
|
PDDocument mergedDoc = new PDDocument();
|
||||||
public String hello(Model model) {
|
|
||||||
model.addAttribute("currentPage", "merge-pdfs");
|
// Iterate over the list of documents and add their pages to the merged document
|
||||||
return "merge-pdfs";
|
for (PDDocument doc : documents) {
|
||||||
}
|
// Get all pages from the current document
|
||||||
|
PDPageTree pages = doc.getPages();
|
||||||
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
// Iterate over the pages and add them to the merged document
|
||||||
// Create a new empty document
|
for (PDPage page : pages) {
|
||||||
PDDocument mergedDoc = new PDDocument();
|
mergedDoc.addPage(page);
|
||||||
|
}
|
||||||
// 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
|
// Return the merged document
|
||||||
for (PDPage page : pages) {
|
return mergedDoc;
|
||||||
mergedDoc.addPage(page);
|
}
|
||||||
}
|
|
||||||
}
|
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
||||||
|
public ResponseEntity<byte[]> mergePdfs(@RequestPart(required = true, value = "fileInput") MultipartFile[] files) throws IOException {
|
||||||
// Return the merged document
|
// Read the input PDF files into PDDocument objects
|
||||||
return mergedDoc;
|
List<PDDocument> documents = new ArrayList<>();
|
||||||
}
|
|
||||||
|
// Loop through the files array and read each file into a PDDocument
|
||||||
@PostMapping("/merge-pdfs")
|
for (MultipartFile file : files) {
|
||||||
public ResponseEntity<byte[]> mergePdfs(@RequestParam("fileInput") MultipartFile[] files) throws IOException {
|
documents.add(PDDocument.load(file.getInputStream()));
|
||||||
// Read the input PDF files into PDDocument objects
|
}
|
||||||
List<PDDocument> documents = new ArrayList<>();
|
|
||||||
|
PDDocument mergedDoc = mergeDocuments(documents);
|
||||||
// Loop through the files array and read each file into a PDDocument
|
|
||||||
for (MultipartFile file : files) {
|
|
||||||
documents.add(PDDocument.load(file.getInputStream()));
|
// Return the merged PDF as a response
|
||||||
}
|
ResponseEntity<byte[]> response = PdfUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
|
||||||
|
|
||||||
PDDocument mergedDoc = mergeDocuments(documents);
|
for (PDDocument doc : documents) {
|
||||||
|
// Close the document after processing
|
||||||
// Return the merged PDF as a response
|
doc.close();
|
||||||
return PdfUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
|
}
|
||||||
}
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,121 +1,109 @@
|
||||||
package stirling.software.SPDF.controller;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
|
||||||
|
@RestController
|
||||||
@Controller
|
public class RearrangePagesPDFController {
|
||||||
public class RearrangePagesPDFController {
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
||||||
@PostMapping("/remove-pages")
|
public ResponseEntity<byte[]> deletePages(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("pagesToDelete") String pagesToDelete)
|
||||||
public ResponseEntity<byte[]> deletePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pagesToDelete") String pagesToDelete) throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
||||||
|
|
||||||
// Split the page order string into an array of page numbers or range of numbers
|
// Split the page order string into an array of page numbers or range of numbers
|
||||||
String[] pageOrderArr = pagesToDelete.split(",");
|
String[] pageOrderArr = pagesToDelete.split(",");
|
||||||
|
|
||||||
List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages());
|
List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages());
|
||||||
|
|
||||||
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
|
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
|
||||||
int pageIndex = pagesToRemove.get(i);
|
int pageIndex = pagesToRemove.get(i);
|
||||||
document.removePage(pageIndex);
|
document.removePage(pageIndex);
|
||||||
}
|
}
|
||||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
|
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/remove-pages")
|
private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) {
|
||||||
public String pageDeleter(Model model) {
|
List<Integer> newPageOrder = new ArrayList<>();
|
||||||
model.addAttribute("currentPage", "remove-pages");
|
// loop through the page order array
|
||||||
return "remove-pages";
|
for (String element : pageOrderArr) {
|
||||||
}
|
// check if the element contains a range of pages
|
||||||
|
if (element.contains("-")) {
|
||||||
private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) {
|
// split the range into start and end page
|
||||||
List<Integer> newPageOrder = new ArrayList<>();
|
String[] range = element.split("-");
|
||||||
// loop through the page order array
|
int start = Integer.parseInt(range[0]);
|
||||||
for (String element : pageOrderArr) {
|
int end = Integer.parseInt(range[1]);
|
||||||
// check if the element contains a range of pages
|
// check if the end page is greater than total pages
|
||||||
if (element.contains("-")) {
|
if (end > totalPages) {
|
||||||
// split the range into start and end page
|
end = totalPages;
|
||||||
String[] range = element.split("-");
|
}
|
||||||
int start = Integer.parseInt(range[0]);
|
// loop through the range of pages
|
||||||
int end = Integer.parseInt(range[1]);
|
for (int j = start; j <= end; j++) {
|
||||||
// check if the end page is greater than total pages
|
// print the current index
|
||||||
if (end > totalPages) {
|
newPageOrder.add(j - 1);
|
||||||
end = totalPages;
|
}
|
||||||
}
|
} else {
|
||||||
// loop through the range of pages
|
// if the element is a single page
|
||||||
for (int j = start; j <= end; j++) {
|
newPageOrder.add(Integer.parseInt(element) - 1);
|
||||||
// print the current index
|
}
|
||||||
newPageOrder.add(j - 1);
|
}
|
||||||
}
|
|
||||||
} else {
|
return newPageOrder;
|
||||||
// if the element is a single page
|
}
|
||||||
newPageOrder.add(Integer.parseInt(element) - 1);
|
|
||||||
}
|
@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
|
||||||
}
|
public ResponseEntity<byte[]> rearrangePages(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) {
|
||||||
|
try {
|
||||||
return newPageOrder;
|
// Load the input PDF
|
||||||
}
|
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||||
|
|
||||||
@GetMapping("/pdf-organizer")
|
// Split the page order string into an array of page numbers or range of numbers
|
||||||
public String pageOrganizer(Model model) {
|
String[] pageOrderArr = pageOrder.split(",");
|
||||||
model.addAttribute("currentPage", "pdf-organizer");
|
// int[] newPageOrder = new int[pageOrderArr.length];
|
||||||
return "pdf-organizer";
|
int totalPages = document.getNumberOfPages();
|
||||||
}
|
|
||||||
|
List<Integer> newPageOrder = pageOrderToString(pageOrderArr, totalPages);
|
||||||
@PostMapping("/rearrange-pages")
|
|
||||||
public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) {
|
// Create a new list to hold the pages in the new order
|
||||||
try {
|
List<PDPage> newPages = new ArrayList<>();
|
||||||
// Load the input PDF
|
for (int i = 0; i < newPageOrder.size(); i++) {
|
||||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
newPages.add(document.getPage(newPageOrder.get(i)));
|
||||||
|
}
|
||||||
// Split the page order string into an array of page numbers or range of numbers
|
|
||||||
String[] pageOrderArr = pageOrder.split(",");
|
// Remove all the pages from the original document
|
||||||
// int[] newPageOrder = new int[pageOrderArr.length];
|
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
|
||||||
int totalPages = document.getNumberOfPages();
|
document.removePage(i);
|
||||||
|
}
|
||||||
List<Integer> newPageOrder = pageOrderToString(pageOrderArr, totalPages);
|
|
||||||
|
// Add the pages in the new order
|
||||||
// Create a new list to hold the pages in the new order
|
for (PDPage page : newPages) {
|
||||||
List<PDPage> newPages = new ArrayList<>();
|
document.addPage(page);
|
||||||
for (int i = 0; i < newPageOrder.size(); i++) {
|
}
|
||||||
newPages.add(document.getPage(newPageOrder.get(i)));
|
|
||||||
}
|
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf");
|
||||||
|
} catch (IOException e) {
|
||||||
// Remove all the pages from the original document
|
|
||||||
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
|
logger.error("Failed rearranging documents", e);
|
||||||
document.removePage(i);
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 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;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
|
||||||
|
@RestController
|
||||||
@Controller
|
public class RotationController {
|
||||||
public class RotationController {
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
|
||||||
@PostMapping("/rotate-pdf")
|
public ResponseEntity<byte[]> rotatePDF(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("angle") Integer angle) throws IOException {
|
||||||
public ResponseEntity<byte[]> rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("angle") Integer angle) throws IOException {
|
|
||||||
|
// Load the PDF document
|
||||||
// Load the PDF document
|
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
||||||
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
|
||||||
|
// Get the list of pages in the document
|
||||||
// Get the list of pages in the document
|
PDPageTree pages = document.getPages();
|
||||||
PDPageTree pages = document.getPages();
|
|
||||||
|
for (PDPage page : pages) {
|
||||||
for (PDPage page : pages) {
|
page.setRotation(page.getRotation() + angle);
|
||||||
page.setRotation(page.getRotation() + angle);
|
}
|
||||||
}
|
|
||||||
|
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf");
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,135 +1,129 @@
|
||||||
package stirling.software.SPDF.controller;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.core.io.ByteArrayResource;
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
@RestController
|
||||||
@Controller
|
public class SplitPDFController {
|
||||||
public class SplitPDFController {
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
||||||
@PostMapping("/split-pages")
|
public ResponseEntity<Resource> splitPdf(@RequestPart(required = true, value = "fileInput") MultipartFile file, @RequestParam("pages") String pages) throws IOException {
|
||||||
public ResponseEntity<Resource> splitPdf(@RequestParam("fileInput") MultipartFile file, @RequestParam("pages") String pages) throws IOException {
|
// parse user input
|
||||||
// parse user input
|
|
||||||
|
// open the pdf document
|
||||||
// open the pdf document
|
InputStream inputStream = file.getInputStream();
|
||||||
InputStream inputStream = file.getInputStream();
|
PDDocument document = PDDocument.load(inputStream);
|
||||||
PDDocument document = PDDocument.load(inputStream);
|
|
||||||
|
List<Integer> pageNumbers = new ArrayList<>();
|
||||||
List<Integer> pageNumbers = new ArrayList<>();
|
pages = pages.replaceAll("\\s+", ""); // remove whitespaces
|
||||||
pages = pages.replaceAll("\\s+", ""); // remove whitespaces
|
if (pages.toLowerCase().equals("all")) {
|
||||||
if (pages.toLowerCase().equals("all")) {
|
for (int i = 0; i < document.getNumberOfPages(); i++) {
|
||||||
for (int i = 0; i < document.getNumberOfPages(); i++) {
|
pageNumbers.add(i);
|
||||||
pageNumbers.add(i);
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(",")));
|
||||||
List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(",")));
|
if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) {
|
||||||
if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) {
|
String lastpage = String.valueOf(document.getNumberOfPages());
|
||||||
String lastpage = String.valueOf(document.getNumberOfPages());
|
pageNumbersStr.add(lastpage);
|
||||||
pageNumbersStr.add(lastpage);
|
}
|
||||||
}
|
for (String page : pageNumbersStr) {
|
||||||
for (String page : pageNumbersStr) {
|
if (page.contains("-")) {
|
||||||
if (page.contains("-")) {
|
String[] range = page.split("-");
|
||||||
String[] range = page.split("-");
|
int start = Integer.parseInt(range[0]);
|
||||||
int start = Integer.parseInt(range[0]);
|
int end = Integer.parseInt(range[1]);
|
||||||
int end = Integer.parseInt(range[1]);
|
for (int i = start; i <= end; i++) {
|
||||||
for (int i = start; i <= end; i++) {
|
pageNumbers.add(i);
|
||||||
pageNumbers.add(i);
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
pageNumbers.add(Integer.parseInt(page));
|
||||||
pageNumbers.add(Integer.parseInt(page));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||||
logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
|
||||||
|
// split the document
|
||||||
// split the document
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
int currentPage = 0;
|
||||||
int currentPage = 0;
|
for (int pageNumber : pageNumbers) {
|
||||||
for (int pageNumber : pageNumbers) {
|
try (PDDocument splitDocument = new PDDocument()) {
|
||||||
try (PDDocument splitDocument = new PDDocument()) {
|
for (int i = currentPage; i < pageNumber; i++) {
|
||||||
for (int i = currentPage; i < pageNumber; i++) {
|
PDPage page = document.getPage(i);
|
||||||
PDPage page = document.getPage(i);
|
splitDocument.addPage(page);
|
||||||
splitDocument.addPage(page);
|
logger.debug("Adding page {} to split document", i);
|
||||||
logger.debug("Adding page {} to split document", i);
|
}
|
||||||
}
|
currentPage = pageNumber;
|
||||||
currentPage = pageNumber;
|
logger.debug("Setting current page to {}", currentPage);
|
||||||
logger.debug("Setting current page to {}", currentPage);
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
splitDocument.save(baos);
|
||||||
splitDocument.save(baos);
|
|
||||||
|
splitDocumentsBoas.add(baos);
|
||||||
splitDocumentsBoas.add(baos);
|
} catch (Exception e) {
|
||||||
} catch (Exception e) {
|
logger.error("Failed splitting documents and saving them", e);
|
||||||
logger.error("Failed splitting documents and saving them", e);
|
throw e;
|
||||||
throw e;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// closing the original document
|
||||||
// closing the original document
|
document.close();
|
||||||
document.close();
|
|
||||||
|
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
||||||
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
|
||||||
|
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
// loop through the split documents and write them to the zip file
|
||||||
// loop through the split documents and write them to the zip file
|
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
||||||
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
String fileName = "split_document_" + (i + 1) + ".pdf";
|
||||||
String fileName = "split_document_" + (i + 1) + ".pdf";
|
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||||
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
byte[] pdf = baos.toByteArray();
|
||||||
byte[] pdf = baos.toByteArray();
|
|
||||||
|
// Add PDF file to the zip
|
||||||
// Add PDF file to the zip
|
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||||
ZipEntry pdfEntry = new ZipEntry(fileName);
|
zipOut.putNextEntry(pdfEntry);
|
||||||
zipOut.putNextEntry(pdfEntry);
|
zipOut.write(pdf);
|
||||||
zipOut.write(pdf);
|
zipOut.closeEntry();
|
||||||
zipOut.closeEntry();
|
|
||||||
|
logger.info("Wrote split document {} to zip file", fileName);
|
||||||
logger.info("Wrote split document {} to zip file", fileName);
|
}
|
||||||
}
|
} catch (Exception e) {
|
||||||
} catch (Exception e) {
|
logger.error("Failed writing to zip", e);
|
||||||
logger.error("Failed writing to zip", e);
|
throw e;
|
||||||
throw e;
|
}
|
||||||
}
|
|
||||||
|
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
||||||
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
byte[] data = Files.readAllBytes(zipFile);
|
||||||
byte[] data = Files.readAllBytes(zipFile);
|
ByteArrayResource resource = new ByteArrayResource(data);
|
||||||
ByteArrayResource resource = new ByteArrayResource(data);
|
Files.delete(zipFile);
|
||||||
Files.delete(zipFile);
|
|
||||||
|
// return the Resource in the response
|
||||||
// return the Resource in the response
|
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_split.zip")
|
||||||
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_split.zip")
|
.contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource);
|
||||||
.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";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +1,86 @@
|
||||||
package stirling.software.SPDF.controller.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.rendering.ImageType;
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.core.io.ByteArrayResource;
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
|
||||||
|
@RestController
|
||||||
@Controller
|
public class ConvertImgPDFController {
|
||||||
public class ConvertImgPDFController {
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-img")
|
||||||
@PostMapping("/pdf-to-img")
|
public ResponseEntity<Resource> convertToImage(@RequestPart(required = true, value = "fileInput") MultipartFile file, @RequestParam("imageFormat") String imageFormat,
|
||||||
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 {
|
||||||
@RequestParam("singleOrMultiple") String singleOrMultiple, @RequestParam("colorType") String colorType, @RequestParam("dpi") String dpi) throws IOException {
|
|
||||||
|
byte[] pdfBytes = file.getBytes();
|
||||||
byte[] pdfBytes = file.getBytes();
|
ImageType colorTypeResult = ImageType.RGB;
|
||||||
ImageType colorTypeResult = ImageType.RGB;
|
if ("greyscale".equals(colorType)) {
|
||||||
if ("greyscale".equals(colorType)) {
|
colorTypeResult = ImageType.GRAY;
|
||||||
colorTypeResult = ImageType.GRAY;
|
} else if ("blackwhite".equals(colorType)) {
|
||||||
} else if ("blackwhite".equals(colorType)) {
|
colorTypeResult = ImageType.BINARY;
|
||||||
colorTypeResult = ImageType.BINARY;
|
}
|
||||||
}
|
// returns bytes for image
|
||||||
// returns bytes for image
|
boolean singleImage = singleOrMultiple.equals("single");
|
||||||
boolean singleImage = singleOrMultiple.equals("single");
|
byte[] result = null;
|
||||||
byte[] result = null;
|
try {
|
||||||
try {
|
result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toUpperCase(), colorTypeResult, singleImage, Integer.valueOf(dpi));
|
||||||
result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toUpperCase(), colorTypeResult, singleImage, Integer.valueOf(dpi));
|
} catch (IOException e) {
|
||||||
} catch (IOException e) {
|
// TODO Auto-generated catch block
|
||||||
// TODO Auto-generated catch block
|
e.printStackTrace();
|
||||||
e.printStackTrace();
|
} catch (Exception e) {
|
||||||
} catch (Exception e) {
|
// TODO Auto-generated catch block
|
||||||
// TODO Auto-generated catch block
|
e.printStackTrace();
|
||||||
e.printStackTrace();
|
}
|
||||||
}
|
if (singleImage) {
|
||||||
if (singleImage) {
|
HttpHeaders headers = new HttpHeaders();
|
||||||
HttpHeaders headers = new HttpHeaders();
|
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
|
||||||
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
|
ResponseEntity<Resource> response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
|
||||||
ResponseEntity<Resource> response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
|
return response;
|
||||||
return response;
|
} else {
|
||||||
} else {
|
ByteArrayResource resource = new ByteArrayResource(result);
|
||||||
ByteArrayResource resource = new ByteArrayResource(result);
|
// return the Resource in the response
|
||||||
// return the Resource in the response
|
return ResponseEntity.ok()
|
||||||
return ResponseEntity.ok()
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToImages.zip")
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToImages.zip")
|
.contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource);
|
||||||
.contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/img-to-pdf")
|
||||||
@PostMapping("/img-to-pdf")
|
public ResponseEntity<byte[]> convertToPdf(@RequestPart(required = true, value = "fileInput") MultipartFile[] file,
|
||||||
public ResponseEntity<byte[]> convertToPdf(@RequestParam("fileInput") MultipartFile[] file, @RequestParam(defaultValue = "false", name = "stretchToFit") boolean stretchToFit,
|
@RequestParam(defaultValue = "false", name = "stretchToFit") boolean stretchToFit, @RequestParam(defaultValue = "true", name = "autoRotate") boolean autoRotate)
|
||||||
@RequestParam(defaultValue = "true", name = "autoRotate") boolean autoRotate) throws IOException {
|
throws IOException {
|
||||||
// Convert the file to PDF and get the resulting bytes
|
// Convert the file to PDF and get the resulting bytes
|
||||||
System.out.println(stretchToFit);
|
System.out.println(stretchToFit);
|
||||||
byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate);
|
byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate);
|
||||||
return PdfUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_coverted.pdf");
|
return PdfUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_coverted.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/img-to-pdf")
|
private String getMediaType(String imageFormat) {
|
||||||
public String convertToPdfForm(Model model) {
|
if (imageFormat.equalsIgnoreCase("PNG"))
|
||||||
model.addAttribute("currentPage", "img-to-pdf");
|
return "image/png";
|
||||||
return "convert/img-to-pdf";
|
else if (imageFormat.equalsIgnoreCase("JPEG") || imageFormat.equalsIgnoreCase("JPG"))
|
||||||
}
|
return "image/jpeg";
|
||||||
|
else if (imageFormat.equalsIgnoreCase("GIF"))
|
||||||
private String getMediaType(String imageFormat) {
|
return "image/gif";
|
||||||
if (imageFormat.equalsIgnoreCase("PNG"))
|
else
|
||||||
return "image/png";
|
return "application/octet-stream";
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package stirling.software.SPDF.controller.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -10,17 +10,15 @@ import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.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.multipart.MultipartFile;
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
|
||||||
@Controller
|
@RestController
|
||||||
public class ConvertOfficeController {
|
public class ConvertOfficeController {
|
||||||
|
|
||||||
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
||||||
|
@ -50,20 +48,13 @@ public class ConvertOfficeController {
|
||||||
|
|
||||||
return pdfBytes;
|
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) {
|
private boolean isValidFileExtension(String fileExtension) {
|
||||||
String extensionPattern = "^(?i)[a-z0-9]{2,4}$";
|
String extensionPattern = "^(?i)[a-z0-9]{2,4}$";
|
||||||
return fileExtension.matches(extensionPattern);
|
return fileExtension.matches(extensionPattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/file-to-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/file-to-pdf")
|
||||||
public ResponseEntity<byte[]> processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException {
|
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
|
// unused but can start server instance if startup time is to long
|
||||||
// LibreOfficeListener.getInstance().start();
|
// 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.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -6,23 +6,20 @@ import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
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.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.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
|
||||||
@Controller
|
@RestController
|
||||||
public class ConvertPDFToPDFA {
|
public class ConvertPDFToPDFA {
|
||||||
|
|
||||||
@PostMapping("/pdf-to-pdfa")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa")
|
||||||
public ResponseEntity<byte[]> pdfToPdfA(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException {
|
public ResponseEntity<byte[]> pdfToPdfA(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile) throws IOException, InterruptedException {
|
||||||
|
|
||||||
// Save the uploaded file to a temporary location
|
// Save the uploaded file to a temporary location
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
|
@ -52,16 +49,7 @@ public class ConvertPDFToPDFA {
|
||||||
|
|
||||||
// Return the optimized PDF as a response
|
// Return the optimized PDF as a response
|
||||||
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf";
|
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf";
|
||||||
HttpHeaders headers = new HttpHeaders();
|
return PdfUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,87 +1,76 @@
|
||||||
package stirling.software.SPDF.controller.other;
|
package stirling.software.SPDF.controller.api.other;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
@RestController
|
||||||
|
public class CompressController {
|
||||||
@Controller
|
|
||||||
public class CompressController {
|
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
||||||
|
|
||||||
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,
|
||||||
@GetMapping("/compress-pdf")
|
@RequestParam(name = "fastWebView", required = false) Boolean fastWebView, @RequestParam(name = "jbig2Lossy", required = false) Boolean jbig2Lossy)
|
||||||
public String compressPdfForm(Model model) {
|
throws IOException, InterruptedException {
|
||||||
model.addAttribute("currentPage", "compress-pdf");
|
|
||||||
return "other/compress-pdf";
|
// Save the uploaded file to a temporary location
|
||||||
}
|
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
|
inputFile.transferTo(tempInputFile.toFile());
|
||||||
@PostMapping("/compress-pdf")
|
|
||||||
public ResponseEntity<byte[]> optimizePdf(@RequestParam("fileInput") MultipartFile inputFile, @RequestParam("optimizeLevel") int optimizeLevel,
|
// Prepare the output file path
|
||||||
@RequestParam(name = "fastWebView", required = false) Boolean fastWebView, @RequestParam(name = "jbig2Lossy", required = false) Boolean jbig2Lossy)
|
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
throws IOException, InterruptedException {
|
|
||||||
|
// Prepare the OCRmyPDF command
|
||||||
// Save the uploaded file to a temporary location
|
List<String> command = new ArrayList<>();
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
command.add("ocrmypdf");
|
||||||
inputFile.transferTo(tempInputFile.toFile());
|
command.add("--skip-text");
|
||||||
|
command.add("--tesseract-timeout=0");
|
||||||
// Prepare the output file path
|
command.add("--optimize");
|
||||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
command.add(String.valueOf(optimizeLevel));
|
||||||
|
command.add("--output-type");
|
||||||
// Prepare the OCRmyPDF command
|
command.add("pdf");
|
||||||
List<String> command = new ArrayList<>();
|
|
||||||
command.add("ocrmypdf");
|
if (fastWebView != null && fastWebView) {
|
||||||
command.add("--skip-text");
|
long fileSize = inputFile.getSize();
|
||||||
command.add("--tesseract-timeout=0");
|
long fastWebViewSize = (long) (fileSize * 1.25); // 25% higher than file size
|
||||||
command.add("--optimize");
|
command.add("--fast-web-view");
|
||||||
command.add(String.valueOf(optimizeLevel));
|
command.add(String.valueOf(fastWebViewSize));
|
||||||
command.add("--output-type");
|
}
|
||||||
command.add("pdf");
|
|
||||||
|
if (jbig2Lossy != null && jbig2Lossy) {
|
||||||
if (fastWebView != null && fastWebView) {
|
command.add("--jbig2-lossy");
|
||||||
long fileSize = inputFile.getSize();
|
}
|
||||||
long fastWebViewSize = (long) (fileSize * 1.25); // 25% higher than file size
|
|
||||||
command.add("--fast-web-view");
|
command.add(tempInputFile.toString());
|
||||||
command.add(String.valueOf(fastWebViewSize));
|
command.add(tempOutputFile.toString());
|
||||||
}
|
|
||||||
|
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
|
||||||
if (jbig2Lossy != null && jbig2Lossy) {
|
|
||||||
command.add("--jbig2-lossy");
|
// Read the optimized PDF file
|
||||||
}
|
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
|
||||||
command.add(tempInputFile.toString());
|
// Clean up the temporary files
|
||||||
command.add(tempOutputFile.toString());
|
Files.delete(tempInputFile);
|
||||||
|
Files.delete(tempOutputFile);
|
||||||
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
|
|
||||||
|
// Return the optimized PDF as a response
|
||||||
// Read the optimized PDF file
|
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_Optimized.pdf";
|
||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
return PdfUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
|
}
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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.Graphics2D;
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
|
@ -18,26 +18,23 @@ import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
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.multipart.MultipartFile;
|
||||||
|
|
||||||
@Controller
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
|
||||||
|
@RestController
|
||||||
public class ExtractImagesController {
|
public class ExtractImagesController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
|
||||||
|
|
||||||
@PostMapping("/extract-images")
|
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
||||||
public ResponseEntity<Resource> extractImages(@RequestParam("fileInput") MultipartFile file, @RequestParam("format") String format) throws IOException {
|
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);
|
System.out.println(System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
|
||||||
PDDocument document = PDDocument.load(file.getBytes());
|
PDDocument document = PDDocument.load(file.getBytes());
|
||||||
|
@ -98,24 +95,8 @@ public class ExtractImagesController {
|
||||||
|
|
||||||
// Create ByteArrayResource from byte array
|
// Create ByteArrayResource from byte array
|
||||||
byte[] zipContents = baos.toByteArray();
|
byte[] zipContents = baos.toByteArray();
|
||||||
ByteArrayResource resource = new ByteArrayResource(zipContents);
|
|
||||||
|
|
||||||
// Set content disposition header to indicate that the response should be
|
return PdfUtils.boasToWebResponse(baos, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted-images.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
// 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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package stirling.software.SPDF.controller.other;
|
package stirling.software.SPDF.controller.api.other;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.ParseException;
|
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.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
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.multipart.MultipartFile;
|
||||||
|
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
|
||||||
@Controller
|
@RestController
|
||||||
public class MetadataController {
|
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) {
|
private String checkUndefined(String entry) {
|
||||||
// Check if the string is "undefined"
|
// Check if the string is "undefined"
|
||||||
|
@ -40,8 +34,8 @@ public class MetadataController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/update-metadata")
|
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
|
||||||
public ResponseEntity<byte[]> metadata(@RequestParam("fileInput") MultipartFile pdfFile,
|
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 = "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 = "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,
|
@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.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
@ -10,26 +10,24 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
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.multipart.MultipartFile;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
|
||||||
|
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
|
||||||
@Controller
|
@RestController
|
||||||
public class OCRController {
|
public class OCRController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
|
private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
|
||||||
|
@ -44,28 +42,29 @@ public class OCRController {
|
||||||
.filter(lang -> !lang.equalsIgnoreCase("osd")).collect(Collectors.toList());
|
.filter(lang -> !lang.equalsIgnoreCase("osd")).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/ocr-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
||||||
public ModelAndView ocrPdfPage() {
|
public ResponseEntity<byte[]> processPdfWithOCR(@RequestPart(required = true, value = "fileInput") MultipartFile inputFile,
|
||||||
ModelAndView modelAndView = new ModelAndView("other/ocr-pdf");
|
@RequestParam("languages") List<String> selectedLanguages, @RequestParam(name = "sidecar", required = false) Boolean sidecar,
|
||||||
modelAndView.addObject("languages", getAvailableTesseractLanguages());
|
@RequestParam(name = "deskew", required = false) Boolean deskew, @RequestParam(name = "clean", required = false) Boolean clean,
|
||||||
modelAndView.addObject("currentPage", "ocr-pdf");
|
@RequestParam(name = "clean-final", required = false) Boolean cleanFinal, @RequestParam(name = "ocrType", required = false) String ocrType,
|
||||||
return modelAndView;
|
@RequestParam(name = "ocrRenderType", required = false, defaultValue = "hocr") String ocrRenderType,
|
||||||
}
|
@RequestParam(name = "removeImagesAfter", required = false) Boolean removeImagesAfter)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
@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 {
|
|
||||||
|
|
||||||
// --output-type pdfa
|
// --output-type pdfa
|
||||||
if (selectedLanguages == null || selectedLanguages.size() < 1) {
|
if (selectedLanguages == null || selectedLanguages.isEmpty()) {
|
||||||
throw new IOException("Please select at least one language.");
|
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
|
// Validate selected languages
|
||||||
String languagePattern = "^[a-zA-Z]{3}$"; // Regex pattern for three-letter language codes
|
selectedLanguages = selectedLanguages.stream().filter(availableLanguages::contains).toList();
|
||||||
selectedLanguages = selectedLanguages.stream().filter(lang -> Pattern.matches(languagePattern, lang)).collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (selectedLanguages.isEmpty()) {
|
if (selectedLanguages.isEmpty()) {
|
||||||
throw new IOException("None of the selected languages are valid.");
|
throw new IOException("None of the selected languages are valid.");
|
||||||
|
@ -83,7 +82,8 @@ public class OCRController {
|
||||||
// Run OCR Command
|
// Run OCR Command
|
||||||
String languageOption = String.join("+", selectedLanguages);
|
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) {
|
if (sidecar != null && sidecar) {
|
||||||
sidecarTextPath = Files.createTempFile("sidecar", ".txt");
|
sidecarTextPath = Files.createTempFile("sidecar", ".txt");
|
||||||
|
@ -115,16 +115,27 @@ public class OCRController {
|
||||||
// Run CLI command
|
// Run CLI command
|
||||||
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(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
|
// Read the OCR processed PDF file
|
||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
|
||||||
// Clean up the temporary files
|
// Clean up the temporary files
|
||||||
Files.delete(tempInputFile);
|
Files.delete(tempInputFile);
|
||||||
|
|
||||||
// Return the OCR processed PDF as a response
|
// Return the OCR processed PDF as a response
|
||||||
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf";
|
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf";
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
|
|
||||||
if (sidecar != null && sidecar) {
|
if (sidecar != null && sidecar) {
|
||||||
// Create a zip file containing both the PDF and the text file
|
// Create a zip file containing both the PDF and the text file
|
||||||
String outputZipFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip";
|
String outputZipFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip";
|
||||||
|
@ -152,15 +163,11 @@ public class OCRController {
|
||||||
Files.delete(sidecarTextPath);
|
Files.delete(sidecarTextPath);
|
||||||
|
|
||||||
// Return the zip file containing both the PDF and the text file
|
// Return the zip file containing both the PDF and the text file
|
||||||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
return PdfUtils.bytesToWebResponse(pdfBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||||
headers.setContentDispositionFormData("attachment", outputZipFilename);
|
|
||||||
return ResponseEntity.ok().headers(headers).body(zipBytes);
|
|
||||||
} else {
|
} else {
|
||||||
// Return the OCR processed PDF as a response
|
// Return the OCR processed PDF as a response
|
||||||
Files.delete(tempOutputFile);
|
Files.delete(tempOutputFile);
|
||||||
headers.setContentType(MediaType.APPLICATION_PDF);
|
return PdfUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
headers.setContentDispositionFormData("attachment", outputFilename);
|
|
||||||
return ResponseEntity.ok().headers(headers).body(pdfBytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,43 +1,36 @@
|
||||||
package stirling.software.SPDF.controller.other;
|
package stirling.software.SPDF.controller.api.other;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
|
||||||
|
@RestController
|
||||||
@Controller
|
public class OverlayImageController {
|
||||||
public class OverlayImageController {
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
||||||
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/add-image")
|
||||||
@GetMapping("/add-image")
|
public ResponseEntity<byte[]> overlayImage(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("fileInput2") MultipartFile imageFile,
|
||||||
public String overlayImage(Model model) {
|
@RequestParam("x") float x, @RequestParam("y") float y, @RequestParam("everyPage") boolean everyPage) {
|
||||||
model.addAttribute("currentPage", "add-image");
|
try {
|
||||||
return "other/add-image";
|
byte[] pdfBytes = pdfFile.getBytes();
|
||||||
}
|
byte[] imageBytes = imageFile.getBytes();
|
||||||
|
byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y, everyPage);
|
||||||
@PostMapping("/add-image")
|
|
||||||
public ResponseEntity<byte[]> overlayImage(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("fileInput2") MultipartFile imageFile, @RequestParam("x") float x,
|
return PdfUtils.bytesToWebResponse(result, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf");
|
||||||
@RequestParam("y") float y, @RequestParam("everyPage") boolean everyPage) {
|
} catch (IOException e) {
|
||||||
try {
|
logger.error("Failed to add image to PDF", e);
|
||||||
byte[] pdfBytes = pdfFile.getBytes();
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
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;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
||||||
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
|
||||||
|
@RestController
|
||||||
@Controller
|
public class PasswordController {
|
||||||
public class PasswordController {
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
|
|
||||||
|
|
||||||
@GetMapping("/add-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
||||||
public String addPasswordForm(Model model) {
|
public ResponseEntity<byte[]> compressPDF(@RequestPart(required = true, value = "fileInput") MultipartFile fileInput, @RequestParam(name = "password") String password)
|
||||||
model.addAttribute("currentPage", "add-password");
|
throws IOException {
|
||||||
return "security/add-password";
|
PDDocument document = PDDocument.load(fileInput.getBytes(), password);
|
||||||
}
|
document.setAllSecurityToBeRemoved(true);
|
||||||
|
return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_password_removed.pdf");
|
||||||
@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);
|
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
||||||
document.setAllSecurityToBeRemoved(true);
|
public ResponseEntity<byte[]> compressPDF(@RequestPart(required = true, value = "fileInput") MultipartFile fileInput,
|
||||||
return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_password_removed.pdf");
|
@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,
|
||||||
@PostMapping("/add-password")
|
@RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility,
|
||||||
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(defaultValue = "", name = "password") String password,
|
@RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
|
||||||
@RequestParam(defaultValue = "128", name = "keyLength") int keyLength, @RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument,
|
@RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations,
|
||||||
@RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent,
|
@RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, @RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
|
||||||
@RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility,
|
throws IOException {
|
||||||
@RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
|
|
||||||
@RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations,
|
PDDocument document = PDDocument.load(fileInput.getBytes());
|
||||||
@RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, @RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
|
AccessPermission ap = new AccessPermission();
|
||||||
throws IOException {
|
|
||||||
|
ap.setCanAssembleDocument(!canAssembleDocument);
|
||||||
PDDocument document = PDDocument.load(fileInput.getBytes());
|
ap.setCanExtractContent(!canExtractContent);
|
||||||
AccessPermission ap = new AccessPermission();
|
ap.setCanExtractForAccessibility(!canExtractForAccessibility);
|
||||||
|
ap.setCanFillInForm(!canFillInForm);
|
||||||
ap.setCanAssembleDocument(!canAssembleDocument);
|
ap.setCanModify(!canModify);
|
||||||
ap.setCanExtractContent(!canExtractContent);
|
ap.setCanModifyAnnotations(!canModifyAnnotations);
|
||||||
ap.setCanExtractForAccessibility(!canExtractForAccessibility);
|
ap.setCanPrint(!canPrint);
|
||||||
ap.setCanFillInForm(!canFillInForm);
|
ap.setCanPrintFaithful(!canPrintFaithful);
|
||||||
ap.setCanModify(!canModify);
|
StandardProtectionPolicy spp = new StandardProtectionPolicy(password, password, ap);
|
||||||
ap.setCanModifyAnnotations(!canModifyAnnotations);
|
spp.setEncryptionKeyLength(keyLength);
|
||||||
ap.setCanPrint(!canPrint);
|
|
||||||
ap.setCanPrintFaithful(!canPrintFaithful);
|
spp.setPermissions(ap);
|
||||||
StandardProtectionPolicy spp = new StandardProtectionPolicy(password, password, ap);
|
|
||||||
spp.setEncryptionKeyLength(keyLength);
|
document.protect(spp);
|
||||||
|
|
||||||
spp.setPermissions(ap);
|
return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf");
|
||||||
|
}
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,150 +1,140 @@
|
||||||
package stirling.software.SPDF.controller.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||||
import org.apache.pdfbox.util.Matrix;
|
import org.apache.pdfbox.util.Matrix;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.WatermarkRemover;
|
||||||
import stirling.software.SPDF.utils.WatermarkRemover;
|
|
||||||
|
@RestController
|
||||||
@Controller
|
public class WatermarkController {
|
||||||
public class WatermarkController {
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
||||||
@PostMapping("/add-watermark")
|
public ResponseEntity<byte[]> addWatermark(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText,
|
||||||
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 = "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 = "0.5", name = "opacity") float opacity, @RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer,
|
@RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer) throws IOException {
|
||||||
@RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer) throws IOException {
|
|
||||||
|
// Load the input PDF
|
||||||
// Load the input PDF
|
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
|
||||||
|
// Create a page in the document
|
||||||
// Create a page in the document
|
for (PDPage page : document.getPages()) {
|
||||||
for (PDPage page : document.getPages()) {
|
|
||||||
|
// Get the page's content stream
|
||||||
// Get the page's content stream
|
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
|
||||||
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
|
|
||||||
|
// Set transparency
|
||||||
// Set transparency
|
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
||||||
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
graphicsState.setNonStrokingAlphaConstant(opacity);
|
||||||
graphicsState.setNonStrokingAlphaConstant(opacity);
|
contentStream.setGraphicsStateParameters(graphicsState);
|
||||||
contentStream.setGraphicsStateParameters(graphicsState);
|
|
||||||
|
// Set font of watermark
|
||||||
// Set font of watermark
|
PDFont font = PDType1Font.HELVETICA_BOLD;
|
||||||
PDFont font = PDType1Font.HELVETICA_BOLD;
|
contentStream.beginText();
|
||||||
contentStream.beginText();
|
contentStream.setFont(font, fontSize);
|
||||||
contentStream.setFont(font, fontSize);
|
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
|
||||||
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
|
|
||||||
|
// Set size and location of watermark
|
||||||
// Set size and location of watermark
|
float pageWidth = page.getMediaBox().getWidth();
|
||||||
float pageWidth = page.getMediaBox().getWidth();
|
float pageHeight = page.getMediaBox().getHeight();
|
||||||
float pageHeight = page.getMediaBox().getHeight();
|
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
|
||||||
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
|
float watermarkHeight = heightSpacer + fontSize;
|
||||||
float watermarkHeight = heightSpacer + fontSize;
|
int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
|
||||||
int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
|
int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
|
||||||
int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
|
|
||||||
|
// Add the watermark text
|
||||||
// Add the watermark text
|
for (int i = 0; i < watermarkRows; i++) {
|
||||||
for (int i = 0; i < watermarkRows; i++) {
|
for (int j = 0; j < watermarkCols; j++) {
|
||||||
for (int j = 0; j < watermarkCols; j++) {
|
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), j * watermarkWidth, i * watermarkHeight));
|
||||||
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), j * watermarkWidth, i * watermarkHeight));
|
contentStream.showTextWithPositioning(new Object[] { watermarkText });
|
||||||
contentStream.showTextWithPositioning(new Object[] { watermarkText });
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
contentStream.endText();
|
||||||
contentStream.endText();
|
|
||||||
|
// Close the content stream
|
||||||
// Close the content stream
|
contentStream.close();
|
||||||
contentStream.close();
|
}
|
||||||
}
|
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf");
|
||||||
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/add-watermark")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-watermark")
|
||||||
public String addWatermarkForm(Model model) {
|
public ResponseEntity<byte[]> removeWatermark(@RequestPart(required = true, value = "fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText)
|
||||||
model.addAttribute("currentPage", "add-watermark");
|
throws Exception {
|
||||||
return "security/add-watermark";
|
|
||||||
}
|
// Load the input PDF
|
||||||
|
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||||
@PostMapping("/remove-watermark")
|
|
||||||
public ResponseEntity<byte[]> removeWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText) throws Exception {
|
// Create a new PDF document for the output
|
||||||
|
PDDocument outputDocument = new PDDocument();
|
||||||
// Load the input PDF
|
|
||||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
// Loop through the pages
|
||||||
|
int numPages = document.getNumberOfPages();
|
||||||
// Create a new PDF document for the output
|
for (int i = 0; i < numPages; i++) {
|
||||||
PDDocument outputDocument = new PDDocument();
|
PDPage page = document.getPage(i);
|
||||||
|
|
||||||
// Loop through the pages
|
// Process the content stream to remove the watermark text
|
||||||
int numPages = document.getNumberOfPages();
|
WatermarkRemover editor = new WatermarkRemover(watermarkText) {
|
||||||
for (int i = 0; i < numPages; i++) {
|
};
|
||||||
PDPage page = document.getPage(i);
|
editor.processPage(page);
|
||||||
|
editor.processPage(page);
|
||||||
// Process the content stream to remove the watermark text
|
// Add the page to the output document
|
||||||
WatermarkRemover editor = new WatermarkRemover(watermarkText) {
|
outputDocument.addPage(page);
|
||||||
};
|
}
|
||||||
editor.processPage(page);
|
|
||||||
editor.processPage(page);
|
for (PDPage page : outputDocument.getPages()) {
|
||||||
// Add the page to the output document
|
List<PDAnnotation> annotations = page.getAnnotations();
|
||||||
outputDocument.addPage(page);
|
List<PDAnnotation> annotationsToRemove = new ArrayList<>();
|
||||||
}
|
|
||||||
|
for (PDAnnotation annotation : annotations) {
|
||||||
for (PDPage page : outputDocument.getPages()) {
|
if (annotation instanceof PDAnnotationMarkup) {
|
||||||
List<PDAnnotation> annotations = page.getAnnotations();
|
PDAnnotationMarkup markup = (PDAnnotationMarkup) annotation;
|
||||||
List<PDAnnotation> annotationsToRemove = new ArrayList<>();
|
String contents = markup.getContents();
|
||||||
|
if (contents != null && contents.contains(watermarkText)) {
|
||||||
for (PDAnnotation annotation : annotations) {
|
annotationsToRemove.add(markup);
|
||||||
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) {
|
||||||
annotations.removeAll(annotationsToRemove);
|
List<PDField> fields = acroForm.getFields();
|
||||||
}
|
for (PDField field : fields) {
|
||||||
PDDocumentCatalog catalog = outputDocument.getDocumentCatalog();
|
String fieldValue = field.getValueAsString();
|
||||||
PDAcroForm acroForm = catalog.getAcroForm();
|
if (fieldValue.contains(watermarkText)) {
|
||||||
if (acroForm != null) {
|
field.setValue(fieldValue.replace(watermarkText, ""));
|
||||||
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");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
}
|
||||||
return PdfUtils.pdfDocToWebResponse(outputDocument, "removed.pdf");
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/remove-watermark")
|
|
||||||
public String removeWatermarkForm(Model model) {
|
|
||||||
model.addAttribute("currentPage", "remove-watermark");
|
|
||||||
return "security/remove-watermark";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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.FileUtils;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
@ -41,8 +40,7 @@ public class PDFToFile {
|
||||||
Path tempInputFile = null;
|
Path tempInputFile = null;
|
||||||
Path tempOutputDir = null;
|
Path tempOutputDir = null;
|
||||||
byte[] fileBytes;
|
byte[] fileBytes;
|
||||||
// Prepare response
|
String fileName = "temp.file";
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Save the uploaded file to a temporary location
|
// Save the uploaded file to a temporary location
|
||||||
|
@ -63,16 +61,14 @@ public class PDFToFile {
|
||||||
if (outputFiles.size() == 1) {
|
if (outputFiles.size() == 1) {
|
||||||
// Return single output file
|
// Return single output file
|
||||||
File outputFile = outputFiles.get(0);
|
File outputFile = outputFiles.get(0);
|
||||||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
|
||||||
if (outputFormat.equals("txt:Text")) {
|
if (outputFormat.equals("txt:Text")) {
|
||||||
outputFormat = "txt";
|
outputFormat = "txt";
|
||||||
}
|
}
|
||||||
headers.setContentDispositionFormData("attachment", pdfBaseName + "." + outputFormat);
|
fileName = pdfBaseName + "." + outputFormat;
|
||||||
fileBytes = FileUtils.readFileToByteArray(outputFile);
|
fileBytes = FileUtils.readFileToByteArray(outputFile);
|
||||||
} else {
|
} else {
|
||||||
// Return output files in a ZIP archive
|
// Return output files in a ZIP archive
|
||||||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
fileName = pdfBaseName + "To" + outputFormat + ".zip";
|
||||||
headers.setContentDispositionFormData("attachment", pdfBaseName + "To" + outputFormat + ".zip");
|
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
|
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
|
||||||
|
|
||||||
|
@ -96,6 +92,6 @@ public class PDFToFile {
|
||||||
if (tempOutputDir != null)
|
if (tempOutputDir != null)
|
||||||
FileUtils.deleteDirectory(tempOutputDir.toFile());
|
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.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
|
@ -43,19 +45,27 @@ public class PdfUtils {
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException {
|
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException {
|
||||||
return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName);
|
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
|
// Return the PDF as a response
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.APPLICATION_PDF);
|
headers.setContentType(mediaType);
|
||||||
headers.setContentLength(bytes.length);
|
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);
|
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 {
|
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))) {
|
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
|
|
|
@ -13,7 +13,7 @@ import java.util.concurrent.Semaphore;
|
||||||
public class ProcessExecutor {
|
public class ProcessExecutor {
|
||||||
|
|
||||||
public enum Processes {
|
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<>();
|
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
|
||||||
|
@ -23,6 +23,8 @@ public class ProcessExecutor {
|
||||||
int semaphoreLimit = switch (key) {
|
int semaphoreLimit = switch (key) {
|
||||||
case LIBRE_OFFICE -> 1;
|
case LIBRE_OFFICE -> 1;
|
||||||
case OCR_MY_PDF -> 2;
|
case OCR_MY_PDF -> 2;
|
||||||
|
case PYTHON_OPENCV -> 8;
|
||||||
|
case GHOSTSCRIPT -> 16;
|
||||||
};
|
};
|
||||||
return new ProcessExecutor(semaphoreLimit);
|
return new ProcessExecutor(semaphoreLimit);
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,10 +31,10 @@ navbar.convert=تحويل
|
||||||
navbar.security=الأمان
|
navbar.security=الأمان
|
||||||
navbar.other=أخرى
|
navbar.other=أخرى
|
||||||
navbar.darkmode=الوضع الداكن
|
navbar.darkmode=الوضع الداكن
|
||||||
navbar.pageOps = عمليات الصفحة
|
navbar.pageOps=عمليات الصفحة
|
||||||
|
|
||||||
home.multiTool.title = أداة متعددة PDF
|
home.multiTool.title=أداة متعددة PDF
|
||||||
home.multiTool.desc = دمج الصفحات وتدويرها وإعادة ترتيبها وإزالتها
|
home.multiTool.desc=دمج الصفحات وتدويرها وإعادة ترتيبها وإزالتها
|
||||||
|
|
||||||
home.merge.title=دمج ملفات
|
home.merge.title=دمج ملفات
|
||||||
home.merge.desc=دمج ملفات PDF متعددة في ملف واحد بسهولة.
|
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.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.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.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.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.title=تحويل PDF إلى Word
|
||||||
home.PDFToWord.desc = تحويل PDF إلى تنسيقات Word (DOC و DOCX و ODT)
|
home.PDFToWord.desc=تحويل PDF إلى تنسيقات Word (DOC و DOCX و ODT)
|
||||||
|
|
||||||
home.PDFToPresentation.title = PDF للعرض التقديمي
|
home.PDFToPresentation.title=PDF للعرض التقديمي
|
||||||
home.PDFToPresentation.desc = تحويل PDF إلى تنسيقات عرض تقديمي (PPT و PPTX و ODP)
|
home.PDFToPresentation.desc=تحويل PDF إلى تنسيقات عرض تقديمي (PPT و PPTX و ODP)
|
||||||
|
|
||||||
home.PDFToText.title = تحويل PDF إلى نص / RTF
|
home.PDFToText.title=تحويل PDF إلى نص / RTF
|
||||||
home.PDFToText.desc = تحويل PDF إلى تنسيق نص أو RTF
|
home.PDFToText.desc=تحويل PDF إلى تنسيق نص أو RTF
|
||||||
|
|
||||||
home.PDFToHTML.title = تحويل PDF إلى HTML
|
home.PDFToHTML.title=تحويل PDF إلى HTML
|
||||||
home.PDFToHTML.desc = تحويل 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
|
navbar.settings=\u0625\u0639\u062F\u0627\u062F\u0627\u062A
|
||||||
settings.title=\u0627\u0644\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.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.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.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.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.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
|
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=إعادة ترتيب الصفحات
|
pdfOrganiser.submit=إعادة ترتيب الصفحات
|
||||||
|
|
||||||
#multiTool
|
#multiTool
|
||||||
multiTool.title = أداة متعددة PDF
|
multiTool.title=أداة متعددة PDF
|
||||||
multiTool.header = أداة متعددة PDF
|
multiTool.header=أداة متعددة PDF
|
||||||
|
|
||||||
#pageRemover
|
#pageRemover
|
||||||
pageRemover.title=مزيل الصفحة
|
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
|
pdfToPDFA.submit=\u062A\u062D\u0648\u064A\u0644
|
||||||
|
|
||||||
|
|
||||||
PDFToWord.title = تحويل PDF إلى Word
|
PDFToWord.title=تحويل PDF إلى Word
|
||||||
PDFToWord.header = تحويل PDF إلى Word
|
PDFToWord.header=تحويل PDF إلى Word
|
||||||
PDFToWord.selectText.1 = تنسيق ملف الإخراج
|
PDFToWord.selectText.1=تنسيق ملف الإخراج
|
||||||
PDFToWord.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
|
PDFToWord.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
|
||||||
PDFToWord.submit = تحويل
|
PDFToWord.submit=تحويل
|
||||||
|
|
||||||
PDFToPresentation.title = PDF للعرض التقديمي
|
PDFToPresentation.title=PDF للعرض التقديمي
|
||||||
PDFToPresentation.header = PDF للعرض التقديمي
|
PDFToPresentation.header=PDF للعرض التقديمي
|
||||||
PDFToPresentation.selectText.1 = تنسيق ملف الإخراج
|
PDFToPresentation.selectText.1=تنسيق ملف الإخراج
|
||||||
PDFToPresentation.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملف.
|
PDFToPresentation.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملف.
|
||||||
PDFToPresentation.submit = تحويل
|
PDFToPresentation.submit=تحويل
|
||||||
|
|
||||||
|
|
||||||
PDFToText.title = تحويل PDF إلى نص / RTF
|
PDFToText.title=تحويل PDF إلى نص / RTF
|
||||||
PDFToText.header = تحويل PDF إلى نص / RTF
|
PDFToText.header=تحويل PDF إلى نص / RTF
|
||||||
PDFToText.selectText.1 = تنسيق ملف الإخراج
|
PDFToText.selectText.1=تنسيق ملف الإخراج
|
||||||
PDFToText.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
|
PDFToText.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
|
||||||
PDFToText.submit = تحويل
|
PDFToText.submit=تحويل
|
||||||
|
|
||||||
|
|
||||||
PDFToHTML.title = PDF إلى HTML
|
PDFToHTML.title=PDF إلى HTML
|
||||||
PDFToHTML.header = PDF إلى HTML
|
PDFToHTML.header=PDF إلى HTML
|
||||||
PDFToHTML.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
|
PDFToHTML.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
|
||||||
PDFToHTML.submit = تحويل
|
PDFToHTML.submit=تحويل
|
||||||
|
|
||||||
PDFToXML.title = تحويل PDF إلى XML
|
PDFToXML.title=تحويل PDF إلى XML
|
||||||
PDFToXML.header = تحويل PDF إلى XML
|
PDFToXML.header=تحويل PDF إلى XML
|
||||||
PDFToXML.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
|
PDFToXML.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
|
||||||
PDFToXML.submit = تحويل
|
PDFToXML.submit=تحويل
|
||||||
|
|
|
@ -104,6 +104,20 @@ home.PDFToHTML.desc=PDF in HTML-Format konvertieren
|
||||||
home.PDFToXML.title=PDF in XML
|
home.PDFToXML.title=PDF in XML
|
||||||
home.PDFToXML.desc=PDF in XML-Format konvertieren
|
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
|
navbar.settings=Einstellungen
|
||||||
settings.title=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.8=Normal (Fehler, wenn PDF Text enthält)
|
||||||
ocr.selectText.9=Zusätzliche Einstellungen
|
ocr.selectText.9=Zusätzliche Einstellungen
|
||||||
ocr.selectText.10=OCR-Modus
|
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.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.credit=Dieser Dienst verwendet OCRmyPDF und Tesseract für OCR.
|
||||||
ocr.submit=PDF mit OCR verarbeiten
|
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.title=PDF to XML
|
||||||
home.PDFToXML.desc=Convert PDF to XML format
|
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
|
navbar.settings=Settings
|
||||||
settings.title=Settings
|
settings.title=Settings
|
||||||
settings.update=Update available
|
settings.update=Update available
|
||||||
|
@ -118,6 +132,7 @@ settings.downloadOption.3=Download file
|
||||||
settings.zipThreshold=Zip files when the number of downloaded files exceeds
|
settings.zipThreshold=Zip files when the number of downloaded files exceeds
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#OCR
|
#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.8=Normal (Will error if PDF contains text)
|
||||||
ocr.selectText.9=Additional Settings
|
ocr.selectText.9=Additional Settings
|
||||||
ocr.selectText.10=OCR Mode
|
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.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.credit=This service uses OCRmyPDF and Tesseract for OCR.
|
||||||
ocr.submit=Process PDF with 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.title=PDF a XML
|
||||||
home.PDFToXML.desc=Convertir PDF a formato 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
|
navbar.settings=Ajustes
|
||||||
settings.title=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.8=Normal (Se producirá un error si el PDF contiene texto)
|
||||||
ocr.selectText.9=Ajustes Adicionales
|
ocr.selectText.9=Ajustes Adicionales
|
||||||
ocr.selectText.10=Modo OCR
|
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.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.credit=Este servicio utiliza OCRmyPDF y Tesseract para OCR.
|
||||||
ocr.submit=Procesa PDF con 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.title=PDF vers XML
|
||||||
home.PDFToXML.desc=Convertir le PDF au format 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
|
navbar.settings=Paramètres
|
||||||
settings.title=Paramètres
|
settings.title=Paramètres
|
||||||
settings.update=Mise à jour disponible
|
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.8=Normal (Erreur si le PDF contient du texte)
|
||||||
ocr.selectText.9=Paramètres supplémentaires
|
ocr.selectText.9=Paramètres supplémentaires
|
||||||
ocr.selectText.10=Mode ROC
|
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.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.credit=Ce service utilise OCRmyPDF et Tesseract pour l'OCR.
|
||||||
ocr.submit=Traiter PDF avec 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 -->
|
<!-- Custom -->
|
||||||
<link rel="stylesheet" href="css/general.css">
|
<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/dark-mode.css}" id="dark-mode-styles">
|
||||||
|
<link rel="stylesheet" th:href="@{css/rainbow-mode.css}" id="rainbow-mode-styles" disabled="true">
|
||||||
<script>
|
<script>
|
||||||
function toggleDarkMode() {
|
var toggleCount = 0;
|
||||||
var darkModeStyles = document.getElementById("dark-mode-styles");
|
var lastToggleTime = Date.now();
|
||||||
var darkModeIcon = document.getElementById("dark-mode-icon");
|
|
||||||
|
|
||||||
if (localStorage.getItem("dark-mode") == "on") {
|
function toggleDarkMode() {
|
||||||
localStorage.setItem("dark-mode", "off");
|
var currentTime = Date.now();
|
||||||
darkModeStyles.disabled = true;
|
if (currentTime - lastToggleTime < 1000) {
|
||||||
darkModeIcon.src = "sun.svg";
|
toggleCount++;
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem("dark-mode", "on");
|
toggleCount = 1;
|
||||||
darkModeStyles.disabled = false;
|
}
|
||||||
darkModeIcon.src = "moon.svg";
|
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 () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
var darkModeStyles = document.getElementById("dark-mode-styles");
|
var darkModeStyles = document.getElementById("dark-mode-styles");
|
||||||
var darkModeIcon = document.getElementById("dark-mode-icon");
|
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") {
|
||||||
if (localStorage.getItem("dark-mode") == "on") {
|
darkModeStyles.disabled = false;
|
||||||
darkModeStyles.disabled = false;
|
rainbowModeStyles.disabled = true;
|
||||||
darkModeIcon.src = "moon.svg";
|
darkModeIcon.src = "moon.svg";
|
||||||
} else if (localStorage.getItem("dark-mode") == "off") {
|
} else if (localStorage.getItem("dark-mode") == "off") {
|
||||||
darkModeStyles.disabled = true;
|
darkModeStyles.disabled = true;
|
||||||
darkModeIcon.src = "sun.svg";
|
rainbowModeStyles.disabled = true;
|
||||||
} else {
|
darkModeIcon.src = "sun.svg";
|
||||||
// Check the OS's default dark mode setting
|
} else if (localStorage.getItem("dark-mode") == "rainbow") {
|
||||||
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
darkModeStyles.disabled = true;
|
||||||
darkModeStyles.disabled = false;
|
rainbowModeStyles.disabled = false;
|
||||||
darkModeIcon.src = "moon.svg";
|
darkModeIcon.src = "rainbow.svg";
|
||||||
} else {
|
} else {
|
||||||
darkModeStyles.disabled = true;
|
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
darkModeIcon.src = "sun.svg";
|
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) {
|
||||||
document.getElementById("dark-mode-toggle").addEventListener("click", function (event) {
|
event.preventDefault();
|
||||||
event.preventDefault();
|
toggleDarkMode();
|
||||||
toggleDarkMode();
|
});
|
||||||
});
|
});
|
||||||
});
|
</script>
|
||||||
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}">
|
<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) {
|
$(document).ready(function() {
|
||||||
var processing = "Processing..."
|
|
||||||
var submitButtonText = $('#submitBtn').text()
|
function loadGameScript(callback) {
|
||||||
|
console.log('loadGameScript called');
|
||||||
$('#submitBtn').text('Processing...');
|
const script = document.createElement('script');
|
||||||
console.log("start download code")
|
script.src = 'js/game.js';
|
||||||
var files = $('#fileInput-input')[0].files;
|
script.onload = callback;
|
||||||
var url = this.action;
|
document.body.appendChild(script);
|
||||||
console.log(url)
|
}
|
||||||
event.preventDefault(); // Prevent the default form handling behavior
|
let gameScriptLoaded = false;
|
||||||
/* Check if ${multiple} is false */
|
$('#show-game-btn').on('click', function() {
|
||||||
var multiple = [[${multiple}]] || false;
|
console.log('Show game button clicked');
|
||||||
var override = $('#override').val() || '';
|
if (!gameScriptLoaded) {
|
||||||
console.log("override=" + override)
|
console.log('Show game button load');
|
||||||
if (override === 'multi' || (!multiple && files.length > 1) && override !== 'single' ) {
|
loadGameScript(function() {
|
||||||
console.log("multi parallel download")
|
console.log('Game script loaded');
|
||||||
submitMultiPdfForm(event,url);
|
window.initializeGame();
|
||||||
} else {
|
gameScriptLoaded = true;
|
||||||
console.log("start single download")
|
});
|
||||||
|
}
|
||||||
|
$('#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');
|
const boredWaiting = localStorage.getItem('boredWaiting');
|
||||||
console.log("contentType=" + contentType)
|
if (boredWaiting === 'enabled') {
|
||||||
// Check if the response is a PDF or an image
|
$('#show-game-btn').show();
|
||||||
if (contentType.includes('pdf') || contentType.includes('image')) {
|
}
|
||||||
response.blob().then(blob => {
|
var processing = "Processing..."
|
||||||
console.log("pdf/image")
|
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
|
// Get the selected download option from localStorage
|
||||||
if (downloadOption === 'sameWindow') {
|
const downloadOption = localStorage.getItem('downloadOption');
|
||||||
console.log("same window")
|
|
||||||
|
|
||||||
// Open the file in the same window
|
var formData = new FormData($('form')[0]);
|
||||||
window.location.href = URL.createObjectURL(blob);
|
|
||||||
} else if (downloadOption === 'newWindow') {
|
// Send the request to the server using the fetch() API
|
||||||
console.log("new window")
|
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');
|
// Extract the filename from the Content-Disposition header, if present
|
||||||
} else {
|
let filename = null;
|
||||||
console.log("else save")
|
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');
|
const link = document.createElement('a');
|
||||||
link.href = URL.createObjectURL(blob);
|
link.href = URL.createObjectURL(blob);
|
||||||
link.download = filename;
|
link.download = filename;
|
||||||
link.click();
|
link.click();
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
} else if (contentType.includes('json')) {
|
})
|
||||||
// Handle the JSON response
|
.catch(error => {
|
||||||
response.json().then(data => {
|
console.log("error listener")
|
||||||
// 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
|
// Extract the error message and stack trace from the response
|
||||||
const link = document.createElement('a');
|
const errorMessage = error.message;
|
||||||
link.href = URL.createObjectURL(blob);
|
const stackTrace = error.stack;
|
||||||
link.download = filename;
|
|
||||||
link.click();
|
// Create an error message to display to the user
|
||||||
});
|
const message = `${errorMessage}\n\n${stackTrace}`;
|
||||||
}
|
|
||||||
})
|
$('#submitBtn').text(submitButtonText);
|
||||||
.catch(error => {
|
|
||||||
console.log("error listener")
|
// Display the error message to the user
|
||||||
|
alert(message);
|
||||||
// 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);
|
$('#submitBtn').text(submitButtonText);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
|
||||||
$('#submitBtn').text(submitButtonText);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function submitMultiPdfForm(event, url) {
|
async function submitMultiPdfForm(event, url) {
|
||||||
// Get the selected PDF files
|
// Get the selected PDF files
|
||||||
let files = $('#fileInput-input')[0].files;
|
let files = $('#fileInput-input')[0].files;
|
||||||
|
@ -272,7 +314,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
if (!contentDisposition) {
|
if (!contentDisposition) {
|
||||||
//throw new Error('Content-Disposition header not found for file ' + i);
|
//throw new Error('Content-Disposition header not found for file ' + i);
|
||||||
} else {
|
} else {
|
||||||
fileName = contentDisposition.split('filename=')[1].replace(/"/g, '');
|
fileName = contentDisposition.split('filename=')[1].replace(/"/g, '');
|
||||||
}
|
}
|
||||||
console.log('Received response for file ' + i + ': ' + response);
|
console.log('Received response for file ' + i + ': ' + response);
|
||||||
|
|
||||||
|
@ -346,10 +388,10 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function updateProgressBar(progressBar, files) {
|
function updateProgressBar(progressBar, files) {
|
||||||
let progress = ((progressBar.attr('aria-valuenow') / files.length) * 100) + (100 / files.length);
|
let progress = ((progressBar.attr('aria-valuenow') / files.length) * 100) + (100 / files.length);
|
||||||
progressBar.css('width', progress + '%');
|
progressBar.css('width', progress + '%');
|
||||||
progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1);
|
progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -357,6 +399,92 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
</script>
|
</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">
|
<script th:inline="javascript">
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
|
|
@ -138,7 +138,7 @@ function compareVersions(version1, version2) {
|
||||||
<ul class="navbar-nav mr-auto flex-nowrap">
|
<ul class="navbar-nav mr-auto flex-nowrap">
|
||||||
|
|
||||||
<li class="nav-item">
|
<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">
|
<img class="icon" src="images/tools.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.multiTool.title}"></span>
|
<span class="icon-text" th:text="#{home.multiTool.title}"></span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -151,23 +151,23 @@ function compareVersions(version1, version2) {
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<!-- Existing menu items -->
|
<!-- 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">
|
<img class="icon" src="images/union.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.merge.title}"></span>
|
<span class="icon-text" th:text="#{home.merge.title}"></span>
|
||||||
</a>
|
</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">
|
<img class="icon" src="images/layout-split.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.split.title}"></span>
|
<span class="icon-text" th:text="#{home.split.title}"></span>
|
||||||
</a>
|
</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">
|
<img class="icon" src="images/sort-numeric-down.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.pdfOrganiser.title}"></span>
|
<span class="icon-text" th:text="#{home.pdfOrganiser.title}"></span>
|
||||||
</a>
|
</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">
|
<img class="icon" src="images/arrow-clockwise.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.rotate.title}"></span>
|
<span class="icon-text" th:text="#{home.rotate.title}"></span>
|
||||||
</a>
|
</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">
|
<img class="icon" src="images/file-earmark-x.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.removePages.title}"></span>
|
<span class="icon-text" th:text="#{home.removePages.title}"></span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -181,51 +181,51 @@ function compareVersions(version1, version2) {
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<!-- Existing menu items -->
|
<!-- 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;">
|
<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>
|
<span class="icon-text" th:text="#{home.imageToPdf.title}"></span>
|
||||||
</a>
|
</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;">
|
<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>
|
<span class="icon-text" th:text="#{home.fileToPDF.title}"></span>
|
||||||
</a>
|
</a>
|
||||||
<hr class="dropdown-divider">
|
<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">
|
<img class="icon" src="images/image.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.pdfToImage.title}"></span>
|
<span class="icon-text" th:text="#{home.pdfToImage.title}"></span>
|
||||||
</a>
|
</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">
|
<img class="icon" src="images/file-earmark-word.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.PDFToWord.title}"></span>
|
<span class="icon-text" th:text="#{home.PDFToWord.title}"></span>
|
||||||
</a>
|
</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">
|
<img class="icon" src="images/file-earmark-ppt.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.PDFToPresentation.title}"></span>
|
<span class="icon-text" th:text="#{home.PDFToPresentation.title}"></span>
|
||||||
</a>
|
</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">
|
<img class="icon" src="images/filetype-txt.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.PDFToText.title}"></span>
|
<span class="icon-text" th:text="#{home.PDFToText.title}"></span>
|
||||||
</a>
|
</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">
|
<img class="icon" src="images/filetype-html.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.PDFToHTML.title}"></span>
|
<span class="icon-text" th:text="#{home.PDFToHTML.title}"></span>
|
||||||
</a>
|
</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">
|
<img class="icon" src="images/filetype-xml.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.PDFToXML.title}"></span>
|
<span class="icon-text" th:text="#{home.PDFToXML.title}"></span>
|
||||||
</a>
|
</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">
|
<img class="icon" src="images/file-earmark-pdf.svg" alt="icon">
|
||||||
<span class="icon-text" th:text="#{home.pdfToPDFA.title}"></span>
|
<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>
|
<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>
|
</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<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>
|
<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>
|
||||||
<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>
|
<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>
|
||||||
<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>
|
<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>
|
||||||
<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>
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -266,21 +266,24 @@ function compareVersions(version1, version2) {
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<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>
|
<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>
|
||||||
<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>
|
<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>
|
||||||
<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>
|
<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>
|
||||||
<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>
|
<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>
|
||||||
<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>
|
<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>
|
||||||
|
<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>
|
</div>
|
||||||
</li>
|
</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">
|
<input type="range" class="custom-range" min="1" max="9" step="1" id="zipThreshold" value="4">
|
||||||
<span id="zipThresholdValue" class="ml-2"></span>
|
<span id="zipThresholdValue" class="ml-2"></span>
|
||||||
</div>
|
</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>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal" th:text="#{close}"></button>
|
<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
|
// Set the selected option in the dropdown
|
||||||
document.getElementById('downloadOption').value = downloadOption;
|
document.getElementById('downloadOption').value = downloadOption;
|
||||||
|
|
||||||
|
|
||||||
// Save the selected option to local storage when the dropdown value changes
|
// Save the selected option to local storage when the dropdown value changes
|
||||||
document.getElementById('downloadOption').addEventListener(
|
document.getElementById('downloadOption').addEventListener(
|
||||||
'change',
|
'change',
|
||||||
|
@ -398,6 +408,8 @@ function compareVersions(version1, version2) {
|
||||||
document.getElementById('zipThreshold').value = zipThreshold;
|
document.getElementById('zipThreshold').value = zipThreshold;
|
||||||
document.getElementById('zipThresholdValue').textContent = zipThreshold;
|
document.getElementById('zipThresholdValue').textContent = zipThreshold;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Save the selected value to local storage when the slider value changes
|
// Save the selected value to local storage when the slider value changes
|
||||||
document.getElementById('zipThreshold').addEventListener('input', function () {
|
document.getElementById('zipThreshold').addEventListener('input', function () {
|
||||||
zipThreshold = this.value;
|
zipThreshold = this.value;
|
||||||
|
@ -405,6 +417,15 @@ function compareVersions(version1, version2) {
|
||||||
localStorage.setItem('zipThreshold', zipThreshold);
|
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>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -75,42 +75,39 @@ filter: invert(0.2) sepia(2) saturate(50) hue-rotate(190deg);
|
||||||
|
|
||||||
<!-- Features -->
|
<!-- Features -->
|
||||||
<div class="features-container container">
|
<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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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>
|
<label for="languages" class="form-label" th:text="#{ocr.selectText.1}"></label>
|
||||||
<hr>
|
<hr>
|
||||||
<div id="languages">
|
<div id="languages">
|
||||||
<div th:each="language, iterStat : ${languages}" >
|
<div th:each="language, iterStat : ${languages}">
|
||||||
<input type="checkbox" class="form-check-input" th:name="languages" th:value="${language}" th:id="${'language-' + language}" />
|
<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}"></label>
|
<label class="form-check-label" th:for="${'language-' + language}" th:text="${(language == 'eng') ? 'English' : language}"></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -53,6 +53,19 @@
|
||||||
<input type="checkbox" class="form-check-input" name="clean-final" id="clean-final" />
|
<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>
|
<label class="form-check-label" for="clean-final" th:text="#{ocr.selectText.5}"></label>
|
||||||
</div>
|
</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>
|
<br>
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{ocr.submit}"></button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{ocr.submit}"></button>
|
||||||
</form>
|
</form>
|
||||||
|
|
Loading…
Reference in a new issue