diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java index 865a95c5c..1fecc1aef 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java @@ -791,6 +791,16 @@ public class CompressController { currentFile = originalFile; } + // Apply linearization as final step if requested + if (Boolean.TRUE.equals(request.getLinearize()) && isQpdfEnabled()) { + try { + applyLinearization(currentFile, tempFiles); + log.info("PDF linearization applied successfully"); + } catch (IOException e) { + log.warn("PDF linearization failed, continuing without linearization", e); + } + } + String outputFilename = GeneralUtils.generateFilename( inputFile.getOriginalFilename(), "_Optimized.pdf"); @@ -810,6 +820,36 @@ public class CompressController { } } + // Apply PDF linearization using QPDF + private void applyLinearization(Path currentFile, List tempFiles) throws IOException { + log.info("Applying PDF linearization to: {}", currentFile); + + // Create output file for linearization + Path linearizedOutputFile = Files.createTempFile("linearized_output_", ".pdf"); + tempFiles.add(linearizedOutputFile); + + // Build QPDF command for linearization only + List command = new ArrayList<>(); + command.add("qpdf"); + command.add("--linearize"); + command.add(currentFile.toString()); + command.add(linearizedOutputFile.toString()); + + try { + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.QPDF) + .runCommandWithOutputHandling(command); + + // Update current file to the linearized output + Files.copy(linearizedOutputFile, currentFile, StandardCopyOption.REPLACE_EXISTING); + + log.info("PDF linearization completed successfully"); + } catch (Exception e) { + log.warn("PDF linearization failed", e); + throw new IOException("PDF linearization failed", e); + } + } + // Run Ghostscript compression private void applyGhostscriptCompression( OptimizePdfRequest request, int optimizeLevel, Path currentFile, List tempFiles) diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index a0f7a2666..a6a350200 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -3324,6 +3324,9 @@ "grayscale": { "label": "Apply Grayscale for Compression" }, + "linearize": { + "label": "Optimize PDF for faster web viewing" + }, "tooltip": { "header": { "title": "Compress Settings Overview" @@ -3341,6 +3344,10 @@ "grayscale": { "title": "Grayscale", "text": "Select this option to convert all images to black and white, which can significantly reduce file size especially for scanned PDFs or image-heavy documents." + }, + "linearize": { + "title": "Linearize", + "text": "Optimize PDF for faster web viewing by rearranging the PDF structure so the first page can be displayed quickly in web browsers." } }, "error": { diff --git a/frontend/src/core/components/tools/compress/CompressSettings.tsx b/frontend/src/core/components/tools/compress/CompressSettings.tsx index 398e0b7b4..fb5ed7bdd 100644 --- a/frontend/src/core/components/tools/compress/CompressSettings.tsx +++ b/frontend/src/core/components/tools/compress/CompressSettings.tsx @@ -129,6 +129,12 @@ const CompressSettings = ({ parameters, onParameterChange, disabled = false }: C disabled={disabled} label={t("compress.grayscale.label", "Apply Grayscale for compression")} /> + onParameterChange('linearize', event.currentTarget.checked)} + disabled={disabled} + label={t("compress.linearize.label", "Linearize PDF for faster web viewing")} + /> ); diff --git a/frontend/src/core/hooks/tools/compress/useCompressOperation.ts b/frontend/src/core/hooks/tools/compress/useCompressOperation.ts index 5b1417b21..c15e0981b 100644 --- a/frontend/src/core/hooks/tools/compress/useCompressOperation.ts +++ b/frontend/src/core/hooks/tools/compress/useCompressOperation.ts @@ -19,6 +19,7 @@ export const buildCompressFormData = (parameters: CompressParameters, file: File } formData.append("grayscale", parameters.grayscale.toString()); + formData.append("linearize", parameters.linearize.toString()); return formData; }; diff --git a/frontend/src/core/hooks/tools/compress/useCompressParameters.ts b/frontend/src/core/hooks/tools/compress/useCompressParameters.ts index 1ae9298f5..74540494d 100644 --- a/frontend/src/core/hooks/tools/compress/useCompressParameters.ts +++ b/frontend/src/core/hooks/tools/compress/useCompressParameters.ts @@ -8,6 +8,7 @@ export interface CompressParameters extends BaseParameters { compressionMethod: 'quality' | 'filesize'; fileSizeValue: string; fileSizeUnit: 'KB' | 'MB'; + linearize: boolean; } export const defaultParameters: CompressParameters = { @@ -17,6 +18,7 @@ export const defaultParameters: CompressParameters = { compressionMethod: 'quality', fileSizeValue: '', fileSizeUnit: 'MB', + linearize: false, }; export type CompressParametersHook = BaseParametersHook;