mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
Exception cleanup, resource change and OCR Defaults (#3876)
# Description of Changes Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --- ## 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. --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: a <a>
This commit is contained in:
co-authored by
Copilot
a <a>
parent
b4a7b5d520
commit
14a4bdfb1b
+1
-1
@@ -72,7 +72,7 @@ public class SplitPDFController {
|
||||
pageNumbers.add(totalPages - 1);
|
||||
}
|
||||
|
||||
log.info(
|
||||
log.debug(
|
||||
"Splitting PDF into pages: {}",
|
||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
|
||||
|
||||
+4
-4
@@ -70,17 +70,17 @@ public class ToSinglePageController {
|
||||
float yOffset = totalHeight;
|
||||
|
||||
// For each page, copy its content to the new page at the correct offset
|
||||
int pageIndex = 0;
|
||||
for (PDPage page : sourceDocument.getPages()) {
|
||||
PDFormXObject form =
|
||||
layerUtility.importPageAsForm(
|
||||
sourceDocument, sourceDocument.getPages().indexOf(page));
|
||||
PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, pageIndex);
|
||||
AffineTransform af =
|
||||
AffineTransform.getTranslateInstance(
|
||||
0, yOffset - page.getMediaBox().getHeight());
|
||||
layerUtility.wrapInSaveRestore(newPage);
|
||||
String defaultLayerName = "Layer" + sourceDocument.getPages().indexOf(page);
|
||||
String defaultLayerName = "Layer" + pageIndex;
|
||||
layerUtility.appendFormAsLayer(newPage, form, af, defaultLayerName);
|
||||
yOffset -= page.getMediaBox().getHeight();
|
||||
pageIndex++;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
+7
-4
@@ -95,9 +95,9 @@ public class ExtractImagesController {
|
||||
try {
|
||||
int pageCount = document.getPages().getCount();
|
||||
log.debug("Document reports {} pages", pageCount);
|
||||
|
||||
|
||||
int consecutiveFailures = 0;
|
||||
|
||||
|
||||
for (int pgNum = 0; pgNum < pageCount; pgNum++) {
|
||||
try {
|
||||
PDPage page = document.getPage(pgNum);
|
||||
@@ -118,7 +118,10 @@ public class ExtractImagesController {
|
||||
allowDuplicates);
|
||||
} catch (Exception e) {
|
||||
// Log the error and continue processing other pages
|
||||
ExceptionUtils.logException("image extraction from page " + currentPageNum, e);
|
||||
ExceptionUtils.logException(
|
||||
"image extraction from page "
|
||||
+ currentPageNum,
|
||||
e);
|
||||
}
|
||||
|
||||
return null; // Callable requires a return type
|
||||
@@ -129,7 +132,7 @@ public class ExtractImagesController {
|
||||
} catch (Exception e) {
|
||||
consecutiveFailures++;
|
||||
ExceptionUtils.logException("page access for page " + (pgNum + 1), e);
|
||||
|
||||
|
||||
if (consecutiveFailures >= 3) {
|
||||
log.warn("Stopping page iteration after 3 consecutive failures");
|
||||
break;
|
||||
|
||||
+45
-2
@@ -47,6 +47,11 @@ public class FakeScanController {
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
// Size limits to prevent OutOfMemoryError
|
||||
private static final int MAX_IMAGE_WIDTH = 8192;
|
||||
private static final int MAX_IMAGE_HEIGHT = 8192;
|
||||
private static final long MAX_IMAGE_PIXELS = 16_777_216; // 4096x4096
|
||||
|
||||
@PostMapping(value = "/fake-scan", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Convert PDF to look like a scanned document",
|
||||
@@ -82,8 +87,46 @@ public class FakeScanController {
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
|
||||
for (int i = 0; i < document.getNumberOfPages(); i++) {
|
||||
// Render page to image with specified resolution
|
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(i, resolution);
|
||||
// Get page dimensions to calculate safe resolution
|
||||
PDRectangle pageSize = document.getPage(i).getMediaBox();
|
||||
float pageWidthPts = pageSize.getWidth();
|
||||
float pageHeightPts = pageSize.getHeight();
|
||||
|
||||
// Calculate what the image dimensions would be at the requested resolution
|
||||
int projectedWidth = (int) Math.ceil(pageWidthPts * resolution / 72.0);
|
||||
int projectedHeight = (int) Math.ceil(pageHeightPts * resolution / 72.0);
|
||||
long projectedPixels = (long) projectedWidth * projectedHeight;
|
||||
|
||||
// Calculate safe resolution that stays within limits
|
||||
int safeResolution = resolution;
|
||||
if (projectedWidth > MAX_IMAGE_WIDTH
|
||||
|| projectedHeight > MAX_IMAGE_HEIGHT
|
||||
|| projectedPixels > MAX_IMAGE_PIXELS) {
|
||||
double widthScale = (double) MAX_IMAGE_WIDTH / projectedWidth;
|
||||
double heightScale = (double) MAX_IMAGE_HEIGHT / projectedHeight;
|
||||
double pixelScale = Math.sqrt((double) MAX_IMAGE_PIXELS / projectedPixels);
|
||||
double minScale = Math.min(Math.min(widthScale, heightScale), pixelScale);
|
||||
safeResolution = (int) Math.max(72, resolution * minScale);
|
||||
|
||||
log.warn(
|
||||
"Page {} would be too large at {}dpi ({}x{} pixels). Reducing to {}dpi",
|
||||
i + 1,
|
||||
resolution,
|
||||
projectedWidth,
|
||||
projectedHeight,
|
||||
safeResolution);
|
||||
}
|
||||
|
||||
// Render page to image with safe resolution
|
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(i, safeResolution);
|
||||
|
||||
log.debug(
|
||||
"Processing page {} with dimensions {}x{} ({} pixels) at {}dpi",
|
||||
i + 1,
|
||||
image.getWidth(),
|
||||
image.getHeight(),
|
||||
(long) image.getWidth() * image.getHeight(),
|
||||
safeResolution);
|
||||
|
||||
// 1. Convert to grayscale or keep color
|
||||
BufferedImage processed;
|
||||
|
||||
+25
-15
@@ -153,15 +153,19 @@ public class PipelineProcessor {
|
||||
String filename = file.getFilename();
|
||||
String providedExtension = "no extension";
|
||||
if (filename != null && filename.contains(".")) {
|
||||
providedExtension = filename.substring(filename.lastIndexOf(".")).toLowerCase();
|
||||
providedExtension =
|
||||
filename.substring(filename.lastIndexOf(".")).toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
logPrintStream.println(
|
||||
"No files with extension "
|
||||
+ String.join(", ", inputFileTypes)
|
||||
+ " found for operation "
|
||||
+ operation
|
||||
+ ". Provided file '" + filename + "' has extension: " + providedExtension);
|
||||
+ ". Provided file '"
|
||||
+ filename
|
||||
+ "' has extension: "
|
||||
+ providedExtension);
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
@@ -211,17 +215,21 @@ public class PipelineProcessor {
|
||||
}
|
||||
} else {
|
||||
// Get details about what files were actually provided
|
||||
List<String> providedExtensions = outputFiles.stream()
|
||||
.map(file -> {
|
||||
String filename = file.getFilename();
|
||||
if (filename != null && filename.contains(".")) {
|
||||
return filename.substring(filename.lastIndexOf(".")).toLowerCase();
|
||||
}
|
||||
return "no extension";
|
||||
})
|
||||
.distinct()
|
||||
.toList();
|
||||
|
||||
List<String> providedExtensions =
|
||||
outputFiles.stream()
|
||||
.map(
|
||||
file -> {
|
||||
String filename = file.getFilename();
|
||||
if (filename != null && filename.contains(".")) {
|
||||
return filename.substring(
|
||||
filename.lastIndexOf("."))
|
||||
.toLowerCase();
|
||||
}
|
||||
return "no extension";
|
||||
})
|
||||
.distinct()
|
||||
.toList();
|
||||
|
||||
logPrintStream.println(
|
||||
"No files with extension "
|
||||
+ String.join(", ", inputFileTypes)
|
||||
@@ -229,7 +237,9 @@ public class PipelineProcessor {
|
||||
+ operation
|
||||
+ ". Provided files have extensions: "
|
||||
+ String.join(", ", providedExtensions)
|
||||
+ " (total files: " + outputFiles.size() + ")");
|
||||
+ " (total files: "
|
||||
+ outputFiles.size()
|
||||
+ ")");
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -65,6 +65,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.micrometer.common.util.StringUtils;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@@ -166,7 +167,7 @@ public class CertSignController {
|
||||
Integer pageNumber = request.getPageNumber() != null ? (request.getPageNumber() - 1) : null;
|
||||
Boolean showLogo = request.getShowLogo();
|
||||
|
||||
if (certType == null) {
|
||||
if (StringUtils.isBlank(certType)) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.optionsNotSpecified",
|
||||
"{0} options are not specified",
|
||||
|
||||
@@ -226,7 +226,8 @@ function setupFileInput(chooser) {
|
||||
|
||||
try {
|
||||
const { isEncrypted, requiresPassword } = await decryptFile.checkFileEncrypted(file);
|
||||
if (file.type === 'application/pdf' && isEncrypted) {
|
||||
if (file.type === 'application/pdf' && isEncrypted &&
|
||||
!window.location.pathname.includes('remove-password')) {
|
||||
decryptedFile = await decryptFile.decryptFile(file, requiresPassword);
|
||||
if (!decryptedFile) throw new Error('File decryption failed.');
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<label th:text="#{ocr.selectText.10}"></label>
|
||||
<select class="form-control" name="ocrType">
|
||||
<option value="skip-text" th:text="#{ocr.selectText.6}"></option>
|
||||
<option value="force-ocr" th:text="#{ocr.selectText.7}"></option>
|
||||
<option selected value="force-ocr" th:text="#{ocr.selectText.7}"></option>
|
||||
<option value="Normal" th:text="#{ocr.selectText.8}"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user