Merge pull request #797 from Stirling-Tools/fixes
Fixes for docker changes plus others
This commit is contained in:
commit
8a5883501a
25 changed files with 249 additions and 401 deletions
33
Dockerfile
33
Dockerfile
|
@ -1,5 +1,32 @@
|
||||||
# Use the base image
|
# Main stage
|
||||||
FROM frooodle/stirling-pdf-base:version8
|
FROM alpine:3.19.0
|
||||||
|
|
||||||
|
# JDK for app
|
||||||
|
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||||
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||||
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||||
|
apk add --no-cache \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
tini \
|
||||||
|
bash \
|
||||||
|
curl \
|
||||||
|
openjdk17-jre \
|
||||||
|
# Doc conversion
|
||||||
|
libreoffice@testing \
|
||||||
|
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||||
|
ocrmypdf \
|
||||||
|
tesseract-ocr-data-eng \
|
||||||
|
# CV
|
||||||
|
py3-opencv \
|
||||||
|
# python3/pip
|
||||||
|
python3 && \
|
||||||
|
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
|
||||||
|
# uno unoconv and HTML
|
||||||
|
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
|
||||||
|
mv /usr/share/tessdata /usr/share/tessdata-original
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
|
@ -24,7 +51,7 @@ COPY build/libs/*.jar app.jar
|
||||||
## useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \
|
## useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \
|
||||||
## mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME && \
|
## mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME && \
|
||||||
# Set up necessary directories and permissions
|
# Set up necessary directories and permissions
|
||||||
RUN mkdir /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
RUN mkdir -p /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
||||||
##&& \
|
##&& \
|
||||||
## chown -R stirlingpdfuser:stirlingpdfgroup /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /customFiles && \
|
## chown -R stirlingpdfuser:stirlingpdfgroup /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /customFiles && \
|
||||||
## chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tesseract-ocr-original && \
|
## chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tesseract-ocr-original && \
|
||||||
|
|
|
@ -20,17 +20,19 @@ COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto
|
||||||
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto
|
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto
|
||||||
COPY build/libs/*.jar app.jar
|
COPY build/libs/*.jar app.jar
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||||
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||||
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||||
|
apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
tini \
|
tini \
|
||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
openjdk17-jre && \
|
openjdk17-jre \
|
||||||
# Doc conversion
|
# Doc conversion
|
||||||
apk add --no-cache libreoffice --repository http://dl-cdn.alpinelinux.org/alpine/edge/community && \
|
libreoffice@testing \
|
||||||
# python and pip
|
# python and pip
|
||||||
apk add --no-cache \
|
|
||||||
python3 && \
|
python3 && \
|
||||||
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
|
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
|
||||||
# uno unoconv and HTML
|
# uno unoconv and HTML
|
||||||
|
@ -40,14 +42,11 @@ RUN apk add --no-cache \
|
||||||
# useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \
|
# useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \
|
||||||
# mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
|
# mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
|
||||||
# Set up necessary directories and permissions
|
# Set up necessary directories and permissions
|
||||||
mkdir /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
mkdir -p /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
||||||
# chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/fonts/opentype/noto /configs /customFiles
|
# chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/fonts/opentype/noto /configs /customFiles
|
||||||
# Set font cache and permissions
|
# Set font cache and permissions
|
||||||
fc-cache -f -v && \
|
fc-cache -f -v && \
|
||||||
chmod +x /scripts/*.sh && \
|
chmod +x /scripts/*.sh
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories
|
|
||||||
# chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
# chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
# Main stage
|
|
||||||
FROM alpine:3.19.0
|
|
||||||
|
|
||||||
# JDK for app
|
|
||||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
|
||||||
apk add --no-cache \
|
|
||||||
ca-certificates \
|
|
||||||
tzdata \
|
|
||||||
tini \
|
|
||||||
bash \
|
|
||||||
curl \
|
|
||||||
openjdk17-jre \
|
|
||||||
# Doc conversion
|
|
||||||
libreoffice@testing \
|
|
||||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
|
||||||
ocrmypdf \
|
|
||||||
tesseract-ocr-data-eng \
|
|
||||||
# CV
|
|
||||||
py3-opencv \
|
|
||||||
# python3/pip
|
|
||||||
python3 && \
|
|
||||||
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
|
|
||||||
# uno unoconv and HTML
|
|
||||||
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
|
|
||||||
mv /usr/share/tessdata /usr/share/tessdata-original
|
|
||||||
|
|
|
@ -77,16 +77,11 @@ public class AppConfig {
|
||||||
return Files.exists(Paths.get("/.dockerenv"));
|
return Files.exists(Paths.get("/.dockerenv"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "bookFormatsInstalled")
|
@Bean(name = "bookAndHtmlFormatsInstalled")
|
||||||
public boolean bookFormatsInstalled() {
|
public boolean bookAndHtmlFormatsInstalled() {
|
||||||
return applicationProperties.getSystem().getCustomApplications().isInstallBookFormats();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(name = "htmlFormatsInstalled")
|
|
||||||
public boolean htmlFormatsInstalled() {
|
|
||||||
return applicationProperties
|
return applicationProperties
|
||||||
.getSystem()
|
.getSystem()
|
||||||
.getCustomApplications()
|
.getCustomApplications()
|
||||||
.isInstallAdvancedHtmlToPDF();
|
.isInstallBookAndHtmlFormats();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import org.springframework.stereotype.Service;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@DependsOn({"bookFormatsInstalled"})
|
@DependsOn({"bookAndHtmlFormatsInstalled"})
|
||||||
public class EndpointConfiguration {
|
public class EndpointConfiguration {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
|
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
|
||||||
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
||||||
|
@ -24,14 +24,14 @@ public class EndpointConfiguration {
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private boolean bookFormatsInstalled;
|
private boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public EndpointConfiguration(
|
public EndpointConfiguration(
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
@Qualifier("bookFormatsInstalled") boolean bookFormatsInstalled) {
|
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
this.bookFormatsInstalled = bookFormatsInstalled;
|
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
|
||||||
init();
|
init();
|
||||||
processEnvironmentConfigs();
|
processEnvironmentConfigs();
|
||||||
}
|
}
|
||||||
|
@ -229,7 +229,7 @@ public class EndpointConfiguration {
|
||||||
private void processEnvironmentConfigs() {
|
private void processEnvironmentConfigs() {
|
||||||
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
|
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
|
||||||
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
|
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
|
||||||
if (!bookFormatsInstalled) {
|
if (!bookAndHtmlFormatsInstalled) {
|
||||||
groupsToRemove.add("Calibre");
|
groupsToRemove.add("Calibre");
|
||||||
}
|
}
|
||||||
if (endpointsToRemove != null) {
|
if (endpointsToRemove != null) {
|
||||||
|
|
|
@ -26,12 +26,8 @@ public class PostStartupProcesses {
|
||||||
private boolean runningInDocker;
|
private boolean runningInDocker;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("bookFormatsInstalled")
|
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||||
private boolean bookFormatsInstalled;
|
private boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("htmlFormatsInstalled")
|
|
||||||
private boolean htmlFormatsInstalled;
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PostStartupProcesses.class);
|
private static final Logger logger = LoggerFactory.getLogger(PostStartupProcesses.class);
|
||||||
|
|
||||||
|
@ -39,34 +35,11 @@ public class PostStartupProcesses {
|
||||||
public void runInstallCommandBasedOnEnvironment() throws IOException, InterruptedException {
|
public void runInstallCommandBasedOnEnvironment() throws IOException, InterruptedException {
|
||||||
List<List<String>> commands = new ArrayList<>();
|
List<List<String>> commands = new ArrayList<>();
|
||||||
// Checking for DOCKER_INSTALL_BOOK_FORMATS environment variable
|
// Checking for DOCKER_INSTALL_BOOK_FORMATS environment variable
|
||||||
if (bookFormatsInstalled) {
|
if (bookAndHtmlFormatsInstalled) {
|
||||||
List<String> tmpList = new ArrayList<>();
|
List<String> tmpList = new ArrayList<>();
|
||||||
// Set up the timezone configuration commands
|
|
||||||
tmpList.addAll(
|
|
||||||
Arrays.asList(
|
|
||||||
"sh",
|
|
||||||
"-c",
|
|
||||||
"echo 'tzdata tzdata/Areas select Europe' | debconf-set-selections; "
|
|
||||||
+ "echo 'tzdata tzdata/Zones/Europe select Berlin' | debconf-set-selections"));
|
|
||||||
commands.add(tmpList);
|
|
||||||
|
|
||||||
// Install calibre with DEBIAN_FRONTEND set to noninteractive
|
|
||||||
tmpList = new ArrayList<>();
|
tmpList = new ArrayList<>();
|
||||||
tmpList.addAll(
|
tmpList.addAll(Arrays.asList("apk add --no-cache calibre"));
|
||||||
Arrays.asList(
|
|
||||||
"sh",
|
|
||||||
"-c",
|
|
||||||
"DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends calibre"));
|
|
||||||
commands.add(tmpList);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checking for DOCKER_INSTALL_HTML_FORMATS environment variable
|
|
||||||
if (htmlFormatsInstalled) {
|
|
||||||
List<String> tmpList = new ArrayList<>();
|
|
||||||
// Add -y flag for automatic yes to prompts and --no-install-recommends to reduce size
|
|
||||||
tmpList.addAll(
|
|
||||||
Arrays.asList(
|
|
||||||
"apt-get", "install", "wkhtmltopdf", "-y", "--no-install-recommends"));
|
|
||||||
commands.add(tmpList);
|
commands.add(tmpList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,8 +47,6 @@ public class PostStartupProcesses {
|
||||||
// Run the command
|
// Run the command
|
||||||
if (runningInDocker) {
|
if (runningInDocker) {
|
||||||
List<String> tmpList = new ArrayList<>();
|
List<String> tmpList = new ArrayList<>();
|
||||||
tmpList.addAll(Arrays.asList("apt-get", "update"));
|
|
||||||
commands.add(0, tmpList);
|
|
||||||
|
|
||||||
for (List<String> list : commands) {
|
for (List<String> list : commands) {
|
||||||
ProcessExecutorResult returnCode =
|
ProcessExecutorResult returnCode =
|
||||||
|
|
|
@ -23,21 +23,21 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
public class ConvertBookToPDFController {
|
public class ConvertBookToPDFController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("bookFormatsInstalled")
|
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||||
private boolean bookFormatsInstalled;
|
private boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/book/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/book/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary =
|
summary =
|
||||||
"Convert a BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) to PDF",
|
"Convert a BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) to PDF",
|
||||||
description =
|
description =
|
||||||
"(Requires bookFormatsInstalled flag and Calibre installed) This endpoint takes an BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) input and converts it to PDF format.")
|
"(Requires bookAndHtmlFormatsInstalled flag and Calibre installed) This endpoint takes an BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) input and converts it to PDF format.")
|
||||||
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception {
|
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
|
|
||||||
if (!bookFormatsInstalled) {
|
if (!bookAndHtmlFormatsInstalled) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"bookFormatsInstalled flag is False, this functionality is not avaiable");
|
"bookAndHtmlFormatsInstalled flag is False, this functionality is not avaiable");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileInput == null) {
|
if (fileInput == null) {
|
||||||
|
|
|
@ -23,8 +23,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
public class ConvertHtmlToPDF {
|
public class ConvertHtmlToPDF {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("htmlFormatsInstalled")
|
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||||
private boolean htmlFormatsInstalled;
|
private boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -47,7 +47,10 @@ public class ConvertHtmlToPDF {
|
||||||
}
|
}
|
||||||
byte[] pdfBytes =
|
byte[] pdfBytes =
|
||||||
FileToPdf.convertHtmlToPdf(
|
FileToPdf.convertHtmlToPdf(
|
||||||
request, fileInput.getBytes(), originalFilename, htmlFormatsInstalled);
|
request,
|
||||||
|
fileInput.getBytes(),
|
||||||
|
originalFilename,
|
||||||
|
bookAndHtmlFormatsInstalled);
|
||||||
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||||
|
|
|
@ -33,8 +33,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
public class ConvertMarkdownToPdf {
|
public class ConvertMarkdownToPdf {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("htmlFormatsInstalled")
|
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||||
private boolean htmlFormatsInstalled;
|
private boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -69,7 +69,10 @@ public class ConvertMarkdownToPdf {
|
||||||
|
|
||||||
byte[] pdfBytes =
|
byte[] pdfBytes =
|
||||||
FileToPdf.convertHtmlToPdf(
|
FileToPdf.convertHtmlToPdf(
|
||||||
null, htmlContent.getBytes(), "converted.html", htmlFormatsInstalled);
|
null,
|
||||||
|
htmlContent.getBytes(),
|
||||||
|
"converted.html",
|
||||||
|
bookAndHtmlFormatsInstalled);
|
||||||
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||||
|
|
|
@ -30,22 +30,22 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
public class ConvertPDFToBookController {
|
public class ConvertPDFToBookController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("bookFormatsInstalled")
|
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||||
private boolean bookFormatsInstalled;
|
private boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/book")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/book")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary =
|
summary =
|
||||||
"Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF",
|
"Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF",
|
||||||
description =
|
description =
|
||||||
"(Requires bookFormatsInstalled flag and Calibre installed) This endpoint Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF")
|
"(Requires bookAndHtmlFormatsInstalled flag and Calibre installed) This endpoint Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF")
|
||||||
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute PdfToBookRequest request)
|
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute PdfToBookRequest request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
|
|
||||||
if (!bookFormatsInstalled) {
|
if (!bookAndHtmlFormatsInstalled) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"bookFormatsInstalled flag is False, this functionality is not avaiable");
|
"bookAndHtmlFormatsInstalled flag is False, this functionality is not avaiable");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileInput == null) {
|
if (fileInput == null) {
|
||||||
|
|
|
@ -2,6 +2,10 @@ package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
@ -9,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.github.pixee.security.Filenames;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
@ -17,6 +22,7 @@ import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest;
|
||||||
import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
|
import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
|
||||||
import stirling.software.SPDF.model.api.converters.PdfToWordRequest;
|
import stirling.software.SPDF.model.api.converters.PdfToWordRequest;
|
||||||
import stirling.software.SPDF.utils.PDFToFile;
|
import stirling.software.SPDF.utils.PDFToFile;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
|
@ -59,9 +65,21 @@ public class ConvertPDFToOffice {
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String outputFormat = request.getOutputFormat();
|
String outputFormat = request.getOutputFormat();
|
||||||
|
if ("txt".equals(request.getOutputFormat())) {
|
||||||
PDFToFile pdfToFile = new PDFToFile();
|
try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) {
|
||||||
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
|
PDFTextStripper stripper = new PDFTextStripper();
|
||||||
|
String text = stripper.getText(document);
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
text.getBytes(),
|
||||||
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "")
|
||||||
|
+ ".txt",
|
||||||
|
MediaType.TEXT_PLAIN);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PDFToFile pdfToFile = new PDFToFile();
|
||||||
|
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/word")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/word")
|
||||||
|
|
|
@ -29,8 +29,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
public class ConvertWebsiteToPDF {
|
public class ConvertWebsiteToPDF {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("htmlFormatsInstalled")
|
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||||
private boolean htmlFormatsInstalled;
|
private boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -53,7 +53,7 @@ public class ConvertWebsiteToPDF {
|
||||||
|
|
||||||
// Prepare the OCRmyPDF command
|
// Prepare the OCRmyPDF command
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
if (!htmlFormatsInstalled) {
|
if (!bookAndHtmlFormatsInstalled) {
|
||||||
command.add("weasyprint");
|
command.add("weasyprint");
|
||||||
} else {
|
} else {
|
||||||
command.add("wkhtmltopdf");
|
command.add("wkhtmltopdf");
|
||||||
|
|
|
@ -2,23 +2,20 @@ package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
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.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.apache.pdfbox.text.PDFTextStripper;
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
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.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
@ -33,7 +30,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
|
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@ -41,6 +37,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class BlankPageController {
|
public class BlankPageController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(BlankPageController.class);
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove blank pages from a PDF file",
|
summary = "Remove blank pages from a PDF file",
|
||||||
|
@ -63,63 +61,35 @@ public class BlankPageController {
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
|
|
||||||
for (PDPage page : pages) {
|
for (PDPage page : pages) {
|
||||||
System.out.println("checking page " + pageIndex);
|
logger.info("checking page " + pageIndex);
|
||||||
textStripper.setStartPage(pageIndex + 1);
|
textStripper.setStartPage(pageIndex + 1);
|
||||||
textStripper.setEndPage(pageIndex + 1);
|
textStripper.setEndPage(pageIndex + 1);
|
||||||
String pageText = textStripper.getText(document);
|
String pageText = textStripper.getText(document);
|
||||||
boolean hasText = !pageText.trim().isEmpty();
|
boolean hasText = !pageText.trim().isEmpty();
|
||||||
|
|
||||||
|
Boolean blank = false;
|
||||||
if (hasText) {
|
if (hasText) {
|
||||||
pagesToKeepIndex.add(pageIndex);
|
logger.info("page " + pageIndex + " has text, not blank");
|
||||||
System.out.println("page " + pageIndex + " has text");
|
blank = false;
|
||||||
} else {
|
} else {
|
||||||
boolean hasImages = PdfUtils.hasImagesOnPage(page);
|
boolean hasImages = PdfUtils.hasImagesOnPage(page);
|
||||||
if (hasImages) {
|
if (hasImages) {
|
||||||
System.out.println("page " + pageIndex + " has image");
|
logger.info("page " + pageIndex + " has image, running blank detection");
|
||||||
|
|
||||||
Path tempFile = Files.createTempFile("image_", ".png");
|
|
||||||
|
|
||||||
// Render image and save as temp file
|
// Render image and save as temp file
|
||||||
BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 300);
|
BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 30);
|
||||||
ImageIO.write(image, "png", tempFile.toFile());
|
blank = isBlankImage(image, threshold, whitePercent, threshold);
|
||||||
|
|
||||||
List<String> command =
|
|
||||||
new ArrayList<>(
|
|
||||||
Arrays.asList(
|
|
||||||
"python",
|
|
||||||
System.getProperty("user.dir")
|
|
||||||
+ "/scripts/detect-blank-pages.py",
|
|
||||||
tempFile.toString(),
|
|
||||||
"--threshold",
|
|
||||||
String.valueOf(threshold),
|
|
||||||
"--white_percent",
|
|
||||||
String.valueOf(whitePercent)));
|
|
||||||
|
|
||||||
Boolean blank = false;
|
|
||||||
// Run CLI command
|
|
||||||
try {
|
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
|
|
||||||
.runCommandWithOutputHandling(command);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// From detect-blank-pages.py
|
|
||||||
// Return code 1: The image is considered blank.
|
|
||||||
// Return code 0: The image is not considered blank.
|
|
||||||
// Since the process returned with a failure code, it should be blank.
|
|
||||||
blank = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blank) {
|
|
||||||
System.out.println("Skipping, Image was blank for page #" + pageIndex);
|
|
||||||
} else {
|
|
||||||
System.out.println(
|
|
||||||
"page " + pageIndex + " has image which is not blank");
|
|
||||||
pagesToKeepIndex.add(pageIndex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (blank) {
|
||||||
|
logger.info("Skipping, Image was blank for page #" + pageIndex);
|
||||||
|
} else {
|
||||||
|
logger.info("page " + pageIndex + " has image which is not blank");
|
||||||
|
pagesToKeepIndex.add(pageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
pageIndex++;
|
pageIndex++;
|
||||||
}
|
}
|
||||||
System.out.print("pagesToKeep=" + pagesToKeepIndex.size());
|
|
||||||
|
|
||||||
// Remove pages not present in pagesToKeepIndex
|
// Remove pages not present in pagesToKeepIndex
|
||||||
List<Integer> pageIndices =
|
List<Integer> pageIndices =
|
||||||
IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList());
|
IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList());
|
||||||
|
@ -142,4 +112,30 @@ public class BlankPageController {
|
||||||
if (document != null) document.close();
|
if (document != null) document.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isBlankImage(
|
||||||
|
BufferedImage image, int threshold, double whitePercent, int blurSize) {
|
||||||
|
if (image == null) {
|
||||||
|
logger.info("Error: Image is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to binary image based on the threshold
|
||||||
|
int whitePixels = 0;
|
||||||
|
int totalPixels = image.getWidth() * image.getHeight();
|
||||||
|
|
||||||
|
for (int i = 0; i < image.getHeight(); i++) {
|
||||||
|
for (int j = 0; j < image.getWidth(); j++) {
|
||||||
|
int color = image.getRGB(j, i) & 0xFF;
|
||||||
|
if (color >= 255 - threshold) {
|
||||||
|
whitePixels++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double whitePixelPercentage = (whitePixels / (double) totalPixels) * 100;
|
||||||
|
logger.info(String.format("Page has white pixel percent of %.2f%%", whitePixelPercentage));
|
||||||
|
|
||||||
|
return whitePixelPercentage >= whitePercent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,12 +68,14 @@ public class ShowJavascript {
|
||||||
|
|
||||||
if (script.isEmpty()) {
|
if (script.isEmpty()) {
|
||||||
script =
|
script =
|
||||||
"PDF '" + Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + "' does not contain Javascript";
|
"PDF '"
|
||||||
|
+ Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
|
+ "' does not contain Javascript";
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
script.getBytes(StandardCharsets.UTF_8),
|
script.getBytes(StandardCharsets.UTF_8),
|
||||||
inputFile.getOriginalFilename() + ".js");
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import java.io.FileOutputStream;
|
||||||
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.util.List;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
@ -87,49 +88,54 @@ public class StampController {
|
||||||
// Load the input PDF
|
// Load the input PDF
|
||||||
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||||
|
|
||||||
for (PDPage page : document.getPages()) {
|
List<Integer> pageNumbers = request.getPageNumbersList();
|
||||||
PDRectangle pageSize = page.getMediaBox();
|
|
||||||
float margin = marginFactor * (pageSize.getWidth() + pageSize.getHeight()) / 2;
|
|
||||||
|
|
||||||
PDPageContentStream contentStream =
|
for (int pageIndex : pageNumbers) {
|
||||||
new PDPageContentStream(
|
int zeroBasedIndex = pageIndex - 1;
|
||||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
if (zeroBasedIndex >= 0 && zeroBasedIndex < document.getNumberOfPages()) {
|
||||||
|
PDPage page = document.getPage(zeroBasedIndex);
|
||||||
|
PDRectangle pageSize = page.getMediaBox();
|
||||||
|
float margin = marginFactor * (pageSize.getWidth() + pageSize.getHeight()) / 2;
|
||||||
|
|
||||||
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
PDPageContentStream contentStream =
|
||||||
graphicsState.setNonStrokingAlphaConstant(opacity);
|
new PDPageContentStream(
|
||||||
contentStream.setGraphicsStateParameters(graphicsState);
|
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
|
|
||||||
if ("text".equalsIgnoreCase(watermarkType)) {
|
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
||||||
addTextStamp(
|
graphicsState.setNonStrokingAlphaConstant(opacity);
|
||||||
contentStream,
|
contentStream.setGraphicsStateParameters(graphicsState);
|
||||||
watermarkText,
|
|
||||||
document,
|
if ("text".equalsIgnoreCase(watermarkType)) {
|
||||||
page,
|
addTextStamp(
|
||||||
rotation,
|
contentStream,
|
||||||
position,
|
watermarkText,
|
||||||
fontSize,
|
document,
|
||||||
alphabet,
|
page,
|
||||||
overrideX,
|
rotation,
|
||||||
overrideY,
|
position,
|
||||||
margin,
|
fontSize,
|
||||||
customColor);
|
alphabet,
|
||||||
} else if ("image".equalsIgnoreCase(watermarkType)) {
|
overrideX,
|
||||||
addImageStamp(
|
overrideY,
|
||||||
contentStream,
|
margin,
|
||||||
watermarkImage,
|
customColor);
|
||||||
document,
|
} else if ("image".equalsIgnoreCase(watermarkType)) {
|
||||||
page,
|
addImageStamp(
|
||||||
rotation,
|
contentStream,
|
||||||
position,
|
watermarkImage,
|
||||||
fontSize,
|
document,
|
||||||
overrideX,
|
page,
|
||||||
overrideY,
|
rotation,
|
||||||
margin);
|
position,
|
||||||
|
fontSize,
|
||||||
|
overrideX,
|
||||||
|
overrideY,
|
||||||
|
margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
contentStream.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
||||||
|
|
|
@ -13,7 +13,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConverterWebController {
|
public class ConverterWebController {
|
||||||
|
|
||||||
@ConditionalOnExpression("#{bookFormatsInstalled}")
|
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
|
||||||
@GetMapping("/book-to-pdf")
|
@GetMapping("/book-to-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String convertBookToPdfForm(Model model) {
|
public String convertBookToPdfForm(Model model) {
|
||||||
|
@ -21,7 +21,7 @@ public class ConverterWebController {
|
||||||
return "convert/book-to-pdf";
|
return "convert/book-to-pdf";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConditionalOnExpression("#{bookFormatsInstalled}")
|
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
|
||||||
@GetMapping("/pdf-to-book")
|
@GetMapping("/pdf-to-book")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String convertPdfToBookForm(Model model) {
|
public String convertPdfToBookForm(Model model) {
|
||||||
|
|
|
@ -290,31 +290,20 @@ public class ApplicationProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CustomApplications {
|
public static class CustomApplications {
|
||||||
private boolean installBookFormats;
|
private boolean installBookAndHtmlFormats;
|
||||||
private boolean installAdvancedHtmlToPDF;
|
|
||||||
|
|
||||||
public boolean isInstallBookFormats() {
|
public boolean isInstallBookAndHtmlFormats() {
|
||||||
return installBookFormats;
|
return installBookAndHtmlFormats;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setInstallBookFormats(boolean installBookFormats) {
|
public void setInstallBookAndHtmlFormats(boolean installBookAndHtmlFormats) {
|
||||||
this.installBookFormats = installBookFormats;
|
this.installBookAndHtmlFormats = installBookAndHtmlFormats;
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInstallAdvancedHtmlToPDF() {
|
|
||||||
return installAdvancedHtmlToPDF;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInstallAdvancedHtmlToPDF(boolean installAdvancedHtmlToPDF) {
|
|
||||||
this.installAdvancedHtmlToPDF = installAdvancedHtmlToPDF;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "CustomApplications [installBookFormats="
|
return "CustomApplications [installBookAndHtmlFormats="
|
||||||
+ installBookFormats
|
+ installBookAndHtmlFormats
|
||||||
+ ", installAdvancedHtmlToPDF="
|
|
||||||
+ installAdvancedHtmlToPDF
|
|
||||||
+ "]";
|
+ "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,39 +14,4 @@ public class HTMLToPdfRequest extends PDFFile {
|
||||||
description = "Zoom level for displaying the website. Default is '1'.",
|
description = "Zoom level for displaying the website. Default is '1'.",
|
||||||
defaultValue = "1")
|
defaultValue = "1")
|
||||||
private float zoom;
|
private float zoom;
|
||||||
|
|
||||||
@Schema(description = "Width of the page in centimeters.")
|
|
||||||
private Float pageWidth;
|
|
||||||
|
|
||||||
@Schema(description = "Height of the page in centimeters.")
|
|
||||||
private Float pageHeight;
|
|
||||||
|
|
||||||
@Schema(description = "Top margin of the page in millimeters.")
|
|
||||||
private Float marginTop;
|
|
||||||
|
|
||||||
@Schema(description = "Bottom margin of the page in millimeters.")
|
|
||||||
private Float marginBottom;
|
|
||||||
|
|
||||||
@Schema(description = "Left margin of the page in millimeters.")
|
|
||||||
private Float marginLeft;
|
|
||||||
|
|
||||||
@Schema(description = "Right margin of the page in millimeters.")
|
|
||||||
private Float marginRight;
|
|
||||||
|
|
||||||
@Schema(
|
|
||||||
description = "Enable or disable rendering of website background.",
|
|
||||||
allowableValues = {"Yes", "No"})
|
|
||||||
private String printBackground;
|
|
||||||
|
|
||||||
@Schema(
|
|
||||||
description =
|
|
||||||
"Enable or disable the default header. The default header includes the name of the page on the left and the page number on the right.",
|
|
||||||
allowableValues = {"Yes", "No"})
|
|
||||||
private String defaultHeader;
|
|
||||||
|
|
||||||
@Schema(
|
|
||||||
description = "Change the CSS media type of the page. Defaults to 'print'.",
|
|
||||||
allowableValues = {"none", "print", "screen"},
|
|
||||||
defaultValue = "print")
|
|
||||||
private String cssMediaType;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,11 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class AddStampRequest extends PDFFile {
|
public class AddStampRequest extends PDFWithPageNums {
|
||||||
|
|
||||||
@Schema(
|
@Schema(
|
||||||
description = "The stamp type (text or image)",
|
description = "The stamp type (text or image)",
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
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;
|
||||||
|
@ -34,95 +35,40 @@ public class FileToPdf {
|
||||||
tempInputFile = Files.createTempFile("input_", ".html");
|
tempInputFile = Files.createTempFile("input_", ".html");
|
||||||
Files.write(tempInputFile, fileBytes);
|
Files.write(tempInputFile, fileBytes);
|
||||||
} else {
|
} else {
|
||||||
tempInputFile = unzipAndGetMainHtml(fileBytes);
|
tempInputFile = Files.createTempFile("input_", ".zip");
|
||||||
|
Files.write(tempInputFile, fileBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
if (!htmlFormatsInstalled) {
|
if (!htmlFormatsInstalled) {
|
||||||
command.add("weasyprint");
|
command.add("weasyprint");
|
||||||
} else {
|
command.add(tempInputFile.toString());
|
||||||
command.add("wkhtmltopdf");
|
command.add(tempOutputFile.toString());
|
||||||
command.add("--enable-local-file-access");
|
|
||||||
command.add("--load-error-handling");
|
} else {
|
||||||
command.add("ignore");
|
command.add("ebook-convert");
|
||||||
command.add("--load-media-error-handling");
|
command.add(tempInputFile.toString());
|
||||||
command.add("ignore");
|
command.add(tempOutputFile.toString());
|
||||||
command.add("--zoom");
|
command.add("--paper-size");
|
||||||
command.add(String.valueOf(request.getZoom()));
|
command.add("a4");
|
||||||
|
|
||||||
// if custom zoom add zoom style direct to html
|
|
||||||
// https://github.com/wkhtmltopdf/wkhtmltopdf/issues/4900
|
|
||||||
if (request.getZoom() != 1.0) {
|
if (request.getZoom() != 1.0) {
|
||||||
String htmlContent = new String(Files.readAllBytes(tempInputFile));
|
// Create a temporary CSS file
|
||||||
|
File tempCssFile = Files.createTempFile("customStyle", ".css").toFile();
|
||||||
String zoomStyle = "<style>body { zoom: " + request.getZoom() + "; }</style>";
|
try (FileWriter writer = new FileWriter(tempCssFile)) {
|
||||||
// Check for <head> tag, add style tag to associated tag
|
// Write the CSS rule to the file
|
||||||
if (htmlContent.contains("<head>")) {
|
writer.write("body { zoom: " + request.getZoom() + "; }");
|
||||||
htmlContent = htmlContent.replace("<head>", "<head>" + zoomStyle);
|
|
||||||
} else if (htmlContent.contains("<html>")) {
|
|
||||||
// If no <head> tag, but <html> tag exists
|
|
||||||
htmlContent = htmlContent.replace("<html>", "<html>" + zoomStyle);
|
|
||||||
} else {
|
|
||||||
// If neither <head> nor <html> tags exist
|
|
||||||
htmlContent = zoomStyle + htmlContent;
|
|
||||||
}
|
}
|
||||||
// rewrite new html to file
|
command.add("--extra-css");
|
||||||
Files.write(tempInputFile, htmlContent.getBytes(StandardCharsets.UTF_8));
|
command.add(tempCssFile.getAbsolutePath());
|
||||||
}
|
|
||||||
|
|
||||||
if (request.getPageWidth() != null) {
|
|
||||||
command.add("--page-width");
|
|
||||||
command.add(request.getPageWidth() + "cm");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.getPageHeight() != null) {
|
|
||||||
command.add("--page-height");
|
|
||||||
command.add(request.getPageHeight() + "cm");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.getMarginTop() != null) {
|
|
||||||
command.add("--margin-top");
|
|
||||||
command.add(request.getMarginTop() + "mm");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repeat similar pattern for marginBottom, marginLeft, marginRight
|
|
||||||
|
|
||||||
if ("Yes".equalsIgnoreCase(request.getPrintBackground())) {
|
|
||||||
command.add("--background");
|
|
||||||
} else {
|
|
||||||
command.add("--no-background");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("Yes".equalsIgnoreCase(request.getDefaultHeader())) {
|
|
||||||
command.add("--default-header");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("print".equalsIgnoreCase(request.getCssMediaType())) {
|
|
||||||
command.add("--print-media-type");
|
|
||||||
} else if ("screen".equalsIgnoreCase(request.getCssMediaType())) {
|
|
||||||
command.add("--no-print-media-type");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
command.add(tempInputFile.toString());
|
|
||||||
command.add(tempOutputFile.toString());
|
|
||||||
ProcessExecutorResult returnCode;
|
ProcessExecutorResult returnCode;
|
||||||
if (fileName.endsWith(".zip")) {
|
|
||||||
|
|
||||||
if (htmlFormatsInstalled) {
|
returnCode =
|
||||||
// command.add(1, "--allow");
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
||||||
// command.add(2, tempInputFile.getParent().toString());
|
.runCommandWithOutputHandling(command);
|
||||||
}
|
|
||||||
returnCode =
|
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
|
||||||
.runCommandWithOutputHandling(
|
|
||||||
command, tempInputFile.getParent().toFile());
|
|
||||||
} else {
|
|
||||||
|
|
||||||
returnCode =
|
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
|
||||||
.runCommandWithOutputHandling(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -135,10 +81,6 @@ public class FileToPdf {
|
||||||
// Clean up temporary files
|
// Clean up temporary files
|
||||||
Files.delete(tempOutputFile);
|
Files.delete(tempOutputFile);
|
||||||
Files.delete(tempInputFile);
|
Files.delete(tempInputFile);
|
||||||
|
|
||||||
if (fileName.endsWith(".zip")) {
|
|
||||||
GeneralUtils.deleteDirectory(tempInputFile.getParent());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pdfBytes;
|
return pdfBytes;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.nio.file.Paths;
|
||||||
import java.nio.file.SimpleFileVisitor;
|
import java.nio.file.SimpleFileVisitor;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
@ -115,6 +116,13 @@ public class GeneralUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Integer> parsePageString(String pageOrder, int totalPages) {
|
public static List<Integer> parsePageString(String pageOrder, int totalPages) {
|
||||||
|
if (pageOrder == null || pageOrder.isEmpty()) {
|
||||||
|
return Collections.singletonList(1);
|
||||||
|
}
|
||||||
|
if (pageOrder.matches("\\d+")) {
|
||||||
|
// Convert the single number string to an integer and return it in a list
|
||||||
|
return Collections.singletonList(Integer.parseInt(pageOrder));
|
||||||
|
}
|
||||||
return parsePageList(pageOrder.split(","), totalPages);
|
return parsePageList(pageOrder.split(","), totalPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,7 @@ system:
|
||||||
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
||||||
enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
|
enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
|
||||||
customApplications:
|
customApplications:
|
||||||
installBookFormats: false # Installs Calibre for book format conversion (For non docker it must be manually downloaded but will need to be true to show in UI)
|
bookAndHtmlFormatsInstalled: false # Installs Calibre for book format conversion (For non docker it must be manually downloaded but will need to be true to show in UI)
|
||||||
installAdvancedHtmlToPDF: false # DO NOT USE EXTERNALLY, NOT SAFE! Install wkHtmlToPDF (For non docker it must be manually downloaded but will need to be true to show in UI)
|
|
||||||
|
|
||||||
#ui:
|
#ui:
|
||||||
# appName: exampleAppName # Application's visible name
|
# appName: exampleAppName # Application's visible name
|
||||||
|
|
|
@ -14,13 +14,16 @@ function initializeGame() {
|
||||||
const highScoreElement = document.getElementById('high-score');
|
const highScoreElement = document.getElementById('high-score');
|
||||||
|
|
||||||
let pdfSize = gameContainer.clientWidth * 0.0625; // 5% of container width
|
let pdfSize = gameContainer.clientWidth * 0.0625; // 5% of container width
|
||||||
let projectileWidth = gameContainer.clientWidth * 0.00625; // 0.5% of container width
|
let projectileWidth = gameContainer.clientWidth * 0.00625;// 0.00625; // 0.5% of container width
|
||||||
let projectileHeight = gameContainer.clientHeight * 0.01667; // 1% of container height
|
let projectileHeight = gameContainer.clientHeight * 0.01667; // 1% of container height
|
||||||
|
|
||||||
let paused = false;
|
let paused = false;
|
||||||
|
|
||||||
const fireRate = 200; // Time between shots in milliseconds
|
const fireRate = 200; // Time between shots in milliseconds
|
||||||
let lastProjectileTime = 0;
|
let lastProjectileTime = 0;
|
||||||
let lives = 3;
|
let lives = 3;
|
||||||
|
|
||||||
|
|
||||||
let highScore = localStorage.getItem('highScore') ? parseInt(localStorage.getItem('highScore')) : 0;
|
let highScore = localStorage.getItem('highScore') ? parseInt(localStorage.getItem('highScore')) : 0;
|
||||||
updateHighScore();
|
updateHighScore();
|
||||||
|
|
||||||
|
@ -31,7 +34,7 @@ function initializeGame() {
|
||||||
const projectiles = [];
|
const projectiles = [];
|
||||||
let score = 0;
|
let score = 0;
|
||||||
let level = 1;
|
let level = 1;
|
||||||
let pdfSpeed = 1;
|
let pdfSpeed = 0.5;
|
||||||
let gameOver = false;
|
let gameOver = false;
|
||||||
|
|
||||||
function handleKeys() {
|
function handleKeys() {
|
||||||
|
@ -119,7 +122,7 @@ function initializeGame() {
|
||||||
|
|
||||||
for (let pdfIndex = 0; pdfIndex < pdfs.length; pdfIndex++) {
|
for (let pdfIndex = 0; pdfIndex < pdfs.length; pdfIndex++) {
|
||||||
const pdf = pdfs[pdfIndex];
|
const pdf = pdfs[pdfIndex];
|
||||||
const pdfY = parseInt(pdf.style.top) + pdfSpeed;
|
const pdfY = parseFloat(pdf.style.top) + pdfSpeed;
|
||||||
if (pdfY + 50 > gameContainer.clientHeight) {
|
if (pdfY + 50 > gameContainer.clientHeight) {
|
||||||
gameContainer.removeChild(pdf);
|
gameContainer.removeChild(pdf);
|
||||||
pdfs.splice(pdfIndex, 1);
|
pdfs.splice(pdfIndex, 1);
|
||||||
|
@ -218,7 +221,7 @@ function initializeGame() {
|
||||||
if (newLevel > level) {
|
if (newLevel > level) {
|
||||||
level = newLevel;
|
level = newLevel;
|
||||||
levelElement.textContent = 'Level: ' + level;
|
levelElement.textContent = 'Level: ' + level;
|
||||||
pdfSpeed += 1;
|
pdfSpeed += 0.2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,6 +252,10 @@ function initializeGame() {
|
||||||
|
|
||||||
let spawnPdfTimeout;
|
let spawnPdfTimeout;
|
||||||
|
|
||||||
|
const BASE_SPAWN_INTERVAL_MS = 1250; // milliseconds before a new enemy spawns
|
||||||
|
const LEVEL_INCREASE_FACTOR_MS = 0; // milliseconds to decrease the spawn interval per level
|
||||||
|
const MAX_SPAWN_RATE_REDUCTION_MS = 800; // Max milliseconds from the base spawn interval
|
||||||
|
|
||||||
function spawnPdfInterval() {
|
function spawnPdfInterval() {
|
||||||
console.log("spawnPdfInterval");
|
console.log("spawnPdfInterval");
|
||||||
if (gameOver || paused) {
|
if (gameOver || paused) {
|
||||||
|
@ -258,7 +265,9 @@ function initializeGame() {
|
||||||
}
|
}
|
||||||
console.log("spawnPdfInterval 3");
|
console.log("spawnPdfInterval 3");
|
||||||
spawnPdf();
|
spawnPdf();
|
||||||
spawnPdfTimeout = setTimeout(spawnPdfInterval, 1000 - level * 50);
|
let spawnRateReduction = Math.min(level * LEVEL_INCREASE_FACTOR_MS, MAX_SPAWN_RATE_REDUCTION_MS);
|
||||||
|
let spawnRate = BASE_SPAWN_INTERVAL_MS - spawnRateReduction;
|
||||||
|
spawnPdfTimeout = setTimeout(spawnPdfInterval, spawnRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlayerPosition();
|
updatePlayerPosition();
|
||||||
|
|
|
@ -19,64 +19,7 @@
|
||||||
<label for="zoom" th:text="#{HTMLToPDF.zoom}" class="form-label"></label>
|
<label for="zoom" th:text="#{HTMLToPDF.zoom}" class="form-label"></label>
|
||||||
<input type="number" step="0.1" class="form-control" id="zoom" name="zoom" value="1" />
|
<input type="number" step="0.1" class="form-control" id="zoom" name="zoom" value="1" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="pageWidth" th:text="#{HTMLToPDF.pageWidth}" class="form-label"></label>
|
|
||||||
<input type="number" class="form-control" id="pageWidth" name="pageWidth" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="pageHeight" th:text="#{HTMLToPDF.pageHeight}" class="form-label"></label>
|
|
||||||
<input type="number" class="form-control" id="pageHeight" name="pageHeight" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="marginTop" th:text="#{HTMLToPDF.marginTop}" class="form-label"></label>
|
|
||||||
<input type="number" class="form-control" id="marginTop" name="marginTop" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="marginBottom" th:text="#{HTMLToPDF.marginBottom}" class="form-label"></label>
|
|
||||||
<input type="number" class="form-control" id="marginBottom" name="marginBottom" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="marginLeft" th:text="#{HTMLToPDF.marginLeft}" class="form-label"></label>
|
|
||||||
<input type="number" class="form-control" id="marginLeft" name="marginLeft" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="marginRight" th:text="#{HTMLToPDF.marginRight}" class="form-label"></label>
|
|
||||||
<input type="number" class="form-control" id="marginRight" name="marginRight" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label th:text="#{HTMLToPDF.printBackground}" class="form-label"></label>
|
|
||||||
<select class="form-select" name="printBackground">
|
|
||||||
<option value="Yes" th:text="#{yes}">Yes</option>
|
|
||||||
<option value="No" th:text="#{no}">No</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label th:text="#{HTMLToPDF.defaultHeader}" class="form-label"></label>
|
|
||||||
<select class="form-select" name="defaultHeader">
|
|
||||||
<option value="No" th:text="#{no}">No</option>
|
|
||||||
<option value="Yes" th:text="#{yes}">Yes</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label th:text="#{HTMLToPDF.cssMediaType}" class="form-label"></label>
|
|
||||||
<select class="form-select" name="cssMediaType">
|
|
||||||
<option value="screen" th:text="#{HTMLToPDF.screen}">Screen</option>
|
|
||||||
<option value="none" th:text="#{HTMLToPDF.none}">None</option>
|
|
||||||
<option value="print" th:text="#{HTMLToPDF.print}">Print</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{HTMLToPDF.submit}"></button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{HTMLToPDF.submit}"></button>
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
<label th:text="#{PDFToText.selectText.1}"></label>
|
<label th:text="#{PDFToText.selectText.1}"></label>
|
||||||
<select class="form-control" name="outputFormat">
|
<select class="form-control" name="outputFormat">
|
||||||
<option value="rtf">RTF</option>
|
<option value="rtf">RTF</option>
|
||||||
|
<option value="txt">TXT</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
|
|
Loading…
Reference in a new issue