mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 03:20:46 +02:00
Memory enhancements and PDF decompress API (#3129)
# Description of Changes - PDF split by size to check size of PDF as it splits, avoids issue were a PDFs size is different viewed vs saved due to compression caused by repeated data etc. - Additionally memory enhancements for PDF load to dynamically load in memory vs scratch - PDF Decompress API for PDF testing ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
@@ -5,10 +5,10 @@ import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.text.PDFTextStripper;
|
||||
import org.apache.pdfbox.text.TextPosition;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -23,6 +23,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -34,6 +35,13 @@ public class AutoRenameController {
|
||||
private static final float TITLE_FONT_SIZE_THRESHOLD = 20.0f;
|
||||
private static final int LINE_LIMIT = 200;
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public AutoRenameController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/auto-rename")
|
||||
@Operation(
|
||||
summary = "Extract header from PDF file",
|
||||
@@ -44,7 +52,7 @@ public class AutoRenameController {
|
||||
MultipartFile file = request.getFileInput();
|
||||
Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback();
|
||||
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
PDDocument document = pdfDocumentFactory.load(file.getBytes());
|
||||
PDFTextStripper reader =
|
||||
new PDFTextStripper() {
|
||||
List<LineInfo> lineInfos = new ArrayList<>();
|
||||
|
||||
+3
-3
@@ -111,9 +111,9 @@ public class AutoSplitPdfController {
|
||||
summary = "Auto split PDF pages into separate documents",
|
||||
description =
|
||||
"This endpoint accepts a PDF file, scans each page for a specific QR code, and"
|
||||
+ " splits the document at the QR code boundaries. The output is a zip file"
|
||||
+ " containing each separate PDF document. Input:PDF Output:ZIP-PDF"
|
||||
+ " Type:SISO")
|
||||
+ " splits the document at the QR code boundaries. The output is a zip file"
|
||||
+ " containing each separate PDF document. Input:PDF Output:ZIP-PDF"
|
||||
+ " Type:SISO")
|
||||
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request)
|
||||
throws IOException {
|
||||
MultipartFile file = request.getFileInput();
|
||||
|
||||
@@ -8,7 +8,6 @@ import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||
@@ -85,7 +84,7 @@ public class BlankPageController {
|
||||
int threshold = request.getThreshold();
|
||||
float whitePercent = request.getWhitePercent();
|
||||
|
||||
try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) {
|
||||
try (PDDocument document = pdfDocumentFactory.load(inputFile.getBytes())) {
|
||||
PDPageTree pages = document.getDocumentCatalog().getPages();
|
||||
PDFTextStripper textStripper = new PDFTextStripper();
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import javax.imageio.ImageWriter;
|
||||
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
@@ -59,7 +58,8 @@ public class CompressController {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
private void compressImagesInPDF(Path pdfFile, double scaleFactor, float jpegQuality) throws Exception {
|
||||
private void compressImagesInPDF(Path pdfFile, double scaleFactor, float jpegQuality)
|
||||
throws Exception {
|
||||
byte[] fileBytes = Files.readAllBytes(pdfFile);
|
||||
long originalFileSize = fileBytes.length;
|
||||
log.info(
|
||||
@@ -71,7 +71,7 @@ public class CompressController {
|
||||
// Track processed images to avoid recompression
|
||||
Set<String> processedImages = new HashSet<>();
|
||||
|
||||
try (PDDocument doc = Loader.loadPDF(fileBytes)) {
|
||||
try (PDDocument doc = pdfDocumentFactory.load(fileBytes)) {
|
||||
int totalImages = 0;
|
||||
int compressedImages = 0;
|
||||
int skippedImages = 0;
|
||||
@@ -204,10 +204,12 @@ public class CompressController {
|
||||
// Choose appropriate format and compression
|
||||
String format = bufferedImage.getColorModel().hasAlpha() ? "png" : "jpeg";
|
||||
|
||||
// First get the actual size of the original image by encoding it to the chosen format
|
||||
// First get the actual size of the original image by encoding it to the chosen
|
||||
// format
|
||||
ByteArrayOutputStream originalImageStream = new ByteArrayOutputStream();
|
||||
if (format.equals("jpeg")) {
|
||||
// Get the best available JPEG writer (prioritizes TwelveMonkeys if available)
|
||||
// Get the best available JPEG writer (prioritizes TwelveMonkeys if
|
||||
// available)
|
||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpeg");
|
||||
ImageWriter writer = null;
|
||||
|
||||
@@ -430,8 +432,8 @@ public class CompressController {
|
||||
|
||||
// All levels (1-9): Apply QPDF compression
|
||||
if (!qpdfCompressionApplied) {
|
||||
long preQpdfSize = Files.size(tempInputFile);
|
||||
log.info("Pre-QPDF file size: {}", GeneralUtils.formatBytes(preQpdfSize));
|
||||
long preQpdfSize = Files.size(tempInputFile);
|
||||
log.info("Pre-QPDF file size: {}", GeneralUtils.formatBytes(preQpdfSize));
|
||||
|
||||
// For levels 1-3, map to qpdf compression levels 1-9
|
||||
int qpdfCompressionLevel = optimizeLevel;
|
||||
@@ -472,8 +474,7 @@ public class CompressController {
|
||||
double qpdfReduction = 100.0 - ((postQpdfSize * 100.0) / preQpdfSize);
|
||||
log.info(
|
||||
"Post-QPDF file size: {} (reduced by {:.1f}%)",
|
||||
GeneralUtils.formatBytes(postQpdfSize),
|
||||
qpdfReduction);
|
||||
GeneralUtils.formatBytes(postQpdfSize), qpdfReduction);
|
||||
|
||||
} else {
|
||||
tempOutputFile = tempInputFile;
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.pdfbox.cos.*;
|
||||
import org.apache.pdfbox.io.IOUtils;
|
||||
import org.apache.pdfbox.pdfwriter.compress.CompressParameters;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/misc")
|
||||
@Slf4j
|
||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||
public class DecompressPdfController {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public DecompressPdfController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/decompress-pdf", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Decompress PDF streams",
|
||||
description = "Fully decompresses all PDF streams including text content")
|
||||
public ResponseEntity<byte[]> decompressPdf(@ModelAttribute PDFFile request)
|
||||
throws IOException {
|
||||
|
||||
MultipartFile file = request.getFileInput();
|
||||
|
||||
try (PDDocument document = pdfDocumentFactory.load(file.getBytes())) {
|
||||
// Process all objects in document
|
||||
processAllObjects(document);
|
||||
|
||||
// Save with explicit no compression
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos, CompressParameters.NO_COMPRESSION);
|
||||
|
||||
String outputFilename =
|
||||
file.getOriginalFilename().replaceFirst("\\.(?=[^.]+$)", "_decompressed.");
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
baos.toByteArray(), outputFilename, MediaType.APPLICATION_PDF);
|
||||
}
|
||||
}
|
||||
|
||||
private void processAllObjects(PDDocument document) {
|
||||
Set<COSBase> processed = new HashSet<>();
|
||||
COSDocument cosDoc = document.getDocument();
|
||||
|
||||
// Process all objects in the document
|
||||
for (COSObjectKey key : cosDoc.getXrefTable().keySet()) {
|
||||
COSObject obj = cosDoc.getObjectFromPool(key);
|
||||
processObject(obj, processed);
|
||||
}
|
||||
}
|
||||
|
||||
private void processObject(COSBase obj, Set<COSBase> processed) {
|
||||
// Skip null objects or already processed objects to avoid infinite recursion
|
||||
if (obj == null || processed.contains(obj)) return;
|
||||
processed.add(obj);
|
||||
|
||||
if (obj instanceof COSObject cosObj) {
|
||||
processObject(cosObj.getObject(), processed);
|
||||
} else if (obj instanceof COSDictionary dict) {
|
||||
processDictionary(dict, processed);
|
||||
} else if (obj instanceof COSArray array) {
|
||||
processArray(array, processed);
|
||||
}
|
||||
}
|
||||
|
||||
private void processDictionary(COSDictionary dict, Set<COSBase> processed) {
|
||||
// Process all dictionary entries
|
||||
for (COSName key : dict.keySet()) {
|
||||
processObject(dict.getDictionaryObject(key), processed);
|
||||
}
|
||||
|
||||
// If this is a stream, decompress it
|
||||
if (dict instanceof COSStream stream) {
|
||||
decompressStream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
private void processArray(COSArray array, Set<COSBase> processed) {
|
||||
// Process all array elements
|
||||
for (int i = 0; i < array.size(); i++) {
|
||||
processObject(array.get(i), processed);
|
||||
}
|
||||
}
|
||||
|
||||
private void decompressStream(COSStream stream) {
|
||||
try {
|
||||
log.debug("Processing stream: {}", stream);
|
||||
|
||||
// Only remove filter information if it exists
|
||||
if (stream.containsKey(COSName.FILTER)
|
||||
|| stream.containsKey(COSName.DECODE_PARMS)
|
||||
|| stream.containsKey(COSName.D)) {
|
||||
|
||||
// Read the decompressed content first
|
||||
byte[] decompressedBytes;
|
||||
try (COSInputStream is = stream.createInputStream()) {
|
||||
decompressedBytes = IOUtils.toByteArray(is);
|
||||
}
|
||||
|
||||
// Now remove filter information
|
||||
stream.removeItem(COSName.FILTER);
|
||||
stream.removeItem(COSName.DECODE_PARMS);
|
||||
stream.removeItem(COSName.D);
|
||||
|
||||
// Write the raw content back
|
||||
try (OutputStream out = stream.createRawOutputStream()) {
|
||||
out.write(decompressedBytes);
|
||||
}
|
||||
|
||||
// Set the Length to reflect the new stream size
|
||||
stream.setInt(COSName.LENGTH, decompressedBytes.length);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error decompressing stream", e);
|
||||
// Continue processing other streams even if this one fails
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-2
@@ -14,9 +14,9 @@ import java.util.zip.ZipOutputStream;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -32,6 +32,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.CheckProgramInstall;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
@@ -45,6 +46,13 @@ public class ExtractImageScansController {
|
||||
|
||||
private static final String REPLACEFIRST = "[.][^.]+$";
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public ExtractImageScansController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
|
||||
@Operation(
|
||||
summary = "Extract image scans from an input file",
|
||||
@@ -87,7 +95,8 @@ public class ExtractImageScansController {
|
||||
// Check if input file is a PDF
|
||||
if ("pdf".equalsIgnoreCase(extension)) {
|
||||
// Load PDF document
|
||||
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
|
||||
try (PDDocument document =
|
||||
pdfDocumentFactory.load(form.getFileInput().getBytes())) {
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
pdfRenderer.setSubsamplingAllowed(true);
|
||||
int pageCount = document.getNumberOfPages();
|
||||
|
||||
+10
-2
@@ -20,11 +20,11 @@ import java.util.zip.ZipOutputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -40,6 +40,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFExtractImagesRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.ImageProcessingUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -49,6 +50,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||
public class ExtractImagesController {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public ExtractImagesController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
||||
@Operation(
|
||||
summary = "Extract images from a PDF file",
|
||||
@@ -59,7 +67,7 @@ public class ExtractImagesController {
|
||||
MultipartFile file = request.getFileInput();
|
||||
String format = request.getFormat();
|
||||
boolean allowDuplicates = request.isAllowDuplicates();
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
PDDocument document = pdfDocumentFactory.load(file.getBytes());
|
||||
|
||||
// Determine if multithreading should be used based on PDF size or number of pages
|
||||
boolean useMultithreading = shouldUseMultithreading(file, document);
|
||||
|
||||
@@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api.misc;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
@@ -51,7 +50,7 @@ public class FlattenController {
|
||||
public ResponseEntity<byte[]> flatten(@ModelAttribute FlattenRequest request) throws Exception {
|
||||
MultipartFile file = request.getFileInput();
|
||||
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
PDDocument document = pdfDocumentFactory.load(file.getBytes());
|
||||
Boolean flattenOnlyForms = request.getFlattenOnlyForms();
|
||||
|
||||
if (Boolean.TRUE.equals(flattenOnlyForms)) {
|
||||
|
||||
@@ -7,10 +7,10 @@ import java.util.Calendar;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -23,6 +23,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.MetadataRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
import stirling.software.SPDF.utils.propertyeditor.StringToMapPropertyEditor;
|
||||
|
||||
@@ -32,6 +33,13 @@ import stirling.software.SPDF.utils.propertyeditor.StringToMapPropertyEditor;
|
||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||
public class MetadataController {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public MetadataController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
private String checkUndefined(String entry) {
|
||||
// Check if the string is "undefined"
|
||||
if ("undefined".equals(entry)) {
|
||||
@@ -76,7 +84,7 @@ public class MetadataController {
|
||||
allRequestParams = new java.util.HashMap<String, String>();
|
||||
}
|
||||
// Load the PDF file into a PDDocument
|
||||
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||
PDDocument document = pdfDocumentFactory.load(pdfFile.getBytes());
|
||||
|
||||
// Get the document information from the PDF
|
||||
PDDocumentInformation info = document.getDocumentInformation();
|
||||
|
||||
+54
-49
@@ -73,17 +73,16 @@ public class PageNumbersController {
|
||||
case "x-large":
|
||||
marginFactor = 0.075f;
|
||||
break;
|
||||
|
||||
default:
|
||||
marginFactor = 0.035f;
|
||||
break;
|
||||
}
|
||||
|
||||
float fontSize = font_size;
|
||||
if (pagesToNumber == null || pagesToNumber.length() == 0) {
|
||||
if (pagesToNumber == null || pagesToNumber.isEmpty()) {
|
||||
pagesToNumber = "all";
|
||||
}
|
||||
if (customText == null || customText.length() == 0) {
|
||||
if (customText == null || customText.isEmpty()) {
|
||||
customText = "{n}";
|
||||
}
|
||||
List<Integer> pagesToNumberList =
|
||||
@@ -94,63 +93,69 @@ public class PageNumbersController {
|
||||
PDRectangle pageSize = page.getMediaBox();
|
||||
|
||||
String text =
|
||||
customText != null
|
||||
? customText
|
||||
.replace("{n}", String.valueOf(pageNumber))
|
||||
.replace("{total}", String.valueOf(document.getNumberOfPages()))
|
||||
.replace(
|
||||
"{filename}",
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", ""))
|
||||
: String.valueOf(pageNumber);
|
||||
customText
|
||||
.replace("{n}", String.valueOf(pageNumber))
|
||||
.replace("{total}", String.valueOf(document.getNumberOfPages()))
|
||||
.replace(
|
||||
"{filename}",
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", ""));
|
||||
|
||||
PDType1Font currentFont =
|
||||
switch (font_type.toLowerCase()) {
|
||||
case "courier" -> new PDType1Font(Standard14Fonts.FontName.COURIER);
|
||||
case "times" -> new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN);
|
||||
default -> new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||
};
|
||||
|
||||
float x, y;
|
||||
|
||||
int xGroup = (position - 1) % 3;
|
||||
int yGroup = 2 - (position - 1) / 3;
|
||||
if (position == 5) {
|
||||
// Calculate text width and font metrics
|
||||
float textWidth = currentFont.getStringWidth(text) / 1000 * fontSize;
|
||||
|
||||
switch (xGroup) {
|
||||
case 0: // left
|
||||
x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth();
|
||||
break;
|
||||
case 1: // center
|
||||
x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2);
|
||||
break;
|
||||
default: // right
|
||||
x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth();
|
||||
break;
|
||||
}
|
||||
float ascent = currentFont.getFontDescriptor().getAscent() / 1000 * fontSize;
|
||||
float descent = currentFont.getFontDescriptor().getDescent() / 1000 * fontSize;
|
||||
|
||||
switch (yGroup) {
|
||||
case 0: // bottom
|
||||
y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight();
|
||||
break;
|
||||
case 1: // middle
|
||||
y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2);
|
||||
break;
|
||||
default: // top
|
||||
y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight();
|
||||
break;
|
||||
float centerX = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2);
|
||||
float centerY = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2);
|
||||
|
||||
x = centerX - (textWidth / 2);
|
||||
y = centerY - (ascent + descent) / 2;
|
||||
} else {
|
||||
int xGroup = (position - 1) % 3;
|
||||
int yGroup = 2 - (position - 1) / 3;
|
||||
|
||||
x =
|
||||
switch (xGroup) {
|
||||
case 0 ->
|
||||
pageSize.getLowerLeftX()
|
||||
+ marginFactor * pageSize.getWidth(); // left
|
||||
case 1 ->
|
||||
pageSize.getLowerLeftX() + (pageSize.getWidth() / 2); // center
|
||||
default ->
|
||||
pageSize.getUpperRightX()
|
||||
- marginFactor * pageSize.getWidth(); // right
|
||||
};
|
||||
|
||||
y =
|
||||
switch (yGroup) {
|
||||
case 0 ->
|
||||
pageSize.getLowerLeftY()
|
||||
+ marginFactor * pageSize.getHeight(); // bottom
|
||||
case 1 ->
|
||||
pageSize.getLowerLeftY() + (pageSize.getHeight() / 2); // middle
|
||||
default ->
|
||||
pageSize.getUpperRightY()
|
||||
- marginFactor * pageSize.getHeight(); // top
|
||||
};
|
||||
}
|
||||
|
||||
PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||
contentStream.beginText();
|
||||
switch (font_type.toLowerCase()) {
|
||||
case "helvetica":
|
||||
contentStream.setFont(
|
||||
new PDType1Font(Standard14Fonts.FontName.HELVETICA), fontSize);
|
||||
break;
|
||||
case "courier":
|
||||
contentStream.setFont(
|
||||
new PDType1Font(Standard14Fonts.FontName.COURIER), fontSize);
|
||||
break;
|
||||
case "times":
|
||||
contentStream.setFont(
|
||||
new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN), fontSize);
|
||||
break;
|
||||
}
|
||||
contentStream.setFont(currentFont, fontSize);
|
||||
contentStream.newLineAtOffset(x, y);
|
||||
contentStream.showText(text);
|
||||
contentStream.endText();
|
||||
|
||||
@@ -3,10 +3,10 @@ package stirling.software.SPDF.controller.api.misc;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -27,6 +28,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||
public class ShowJavascript {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public ShowJavascript(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
|
||||
@Operation(
|
||||
summary = "Grabs all JS from a PDF and returns a single JS file with all code",
|
||||
@@ -35,7 +43,7 @@ public class ShowJavascript {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
String script = "";
|
||||
|
||||
try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) {
|
||||
try (PDDocument document = pdfDocumentFactory.load(inputFile.getBytes())) {
|
||||
|
||||
if (document.getDocumentCatalog() != null
|
||||
&& document.getDocumentCatalog().getNames() != null) {
|
||||
|
||||
Reference in New Issue
Block a user