1
0
mirror of https://github.com/Stirling-Tools/Stirling-PDF.git synced 2024-11-17 12:40:11 +01:00

language cleanups and sanitize

This commit is contained in:
Anthony Stirling 2023-07-16 18:57:21 +01:00
parent d07e3e6522
commit 92b9142902
13 changed files with 1120 additions and 871 deletions

View File

@ -68,6 +68,9 @@ public class EndpointConfiguration {
addEndpointToGroup("PageOps", "rotate-pdf");
addEndpointToGroup("PageOps", "multi-page-layout");
addEndpointToGroup("PageOps", "scale-pages");
addEndpointToGroup("PageOps", "adjust-contrast");
addEndpointToGroup("PageOps", "crop");
addEndpointToGroup("PageOps", "auto-split-pdf");
// Adding endpoints to "Convert" group
addEndpointToGroup("Convert", "pdf-to-img");
@ -87,7 +90,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Security", "change-permissions");
addEndpointToGroup("Security", "add-watermark");
addEndpointToGroup("Security", "cert-sign");
addEndpointToGroup("Security", "sanitize-pdf");
// Adding endpoints to "Other" group
@ -102,9 +105,8 @@ public class EndpointConfiguration {
addEndpointToGroup("Other", "repair");
addEndpointToGroup("Other", "remove-blanks");
addEndpointToGroup("Other", "compare");
addEndpointToGroup("Other", "add-page-numbers");
addEndpointToGroup("Other", "auto-rename");
@ -168,12 +170,18 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "cert-sign");
addEndpointToGroup("Java", "multi-page-layout");
addEndpointToGroup("Java", "scale-pages");
addEndpointToGroup("Java", "add-page-numbers");
addEndpointToGroup("Java", "auto-rename");
addEndpointToGroup("Java", "auto-split-pdf");
addEndpointToGroup("Java", "sanitize-pdf");
addEndpointToGroup("Java", "crop");
//Javascript
addEndpointToGroup("Javascript", "pdf-organizer");
addEndpointToGroup("Javascript", "sign");
addEndpointToGroup("Javascript", "compare");
addEndpointToGroup("Javascript", "adjust-contrast");
}

View File

@ -85,10 +85,10 @@ public class PageNumbersController {
float marginFactor;
switch (customMargin.toLowerCase()) {
case "small":
marginFactor = 0.01f;
marginFactor = 0.02f;
break;
case "medium":
marginFactor = 0.025f;
marginFactor = 0.035f;
break;
case "large":
marginFactor = 0.05f;
@ -97,7 +97,7 @@ public class PageNumbersController {
marginFactor = 0.1f;
break;
default:
marginFactor = 0.01f;
marginFactor = 0.035f;
break;
}

View File

@ -0,0 +1,140 @@
package stirling.software.SPDF.controller.api.security;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.interactive.action.*;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDNonTerminalField;
import org.apache.pdfbox.pdmodel.interactive.form.PDTerminalField;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import stirling.software.SPDF.utils.WebResponseUtils;
import java.io.IOException;
import java.io.InputStream;
@RestController
public class SanitizeController {
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
@Operation(summary = "Sanitize a PDF file",
description = "This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> sanitizePDF(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be sanitized")
MultipartFile inputFile,
@RequestParam(name = "removeJavaScript", required = false, defaultValue = "true")
@Parameter(description = "Remove JavaScript actions from the PDF if set to true")
Boolean removeJavaScript,
@RequestParam(name = "removeEmbeddedFiles", required = false, defaultValue = "true")
@Parameter(description = "Remove embedded files from the PDF if set to true")
Boolean removeEmbeddedFiles,
@RequestParam(name = "removeMetadata", required = false, defaultValue = "true")
@Parameter(description = "Remove metadata from the PDF if set to true")
Boolean removeMetadata,
@RequestParam(name = "removeLinks", required = false, defaultValue = "true")
@Parameter(description = "Remove links from the PDF if set to true")
Boolean removeLinks,
@RequestParam(name = "removeFonts", required = false, defaultValue = "true")
@Parameter(description = "Remove fonts from the PDF if set to true")
Boolean removeFonts) throws IOException {
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
if (removeJavaScript) {
sanitizeJavaScript(document);
}
if (removeEmbeddedFiles) {
sanitizeEmbeddedFiles(document);
}
if (removeMetadata) {
sanitizeMetadata(document);
}
if (removeLinks) {
sanitizeLinks(document);
}
if (removeFonts) {
sanitizeFonts(document);
}
return WebResponseUtils.pdfDocToWebResponse(document, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_sanitized.pdf");
}
}
private void sanitizeJavaScript(PDDocument document) throws IOException {
for (PDPage page : document.getPages()) {
for (PDAnnotation annotation : page.getAnnotations()) {
if (annotation instanceof PDAnnotationWidget) {
PDAnnotationWidget widget = (PDAnnotationWidget) annotation;
PDAction action = widget.getAction();
if (action instanceof PDActionJavaScript) {
widget.setAction(null);
}
}
}
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
if (acroForm != null) {
for (PDField field : acroForm.getFields()) {
if (field.getActions().getF() instanceof PDActionJavaScript) {
field.getActions().setF(null);
}
}
}
}
}
private void sanitizeEmbeddedFiles(PDDocument document) {
PDPageTree allPages = document.getPages();
for (PDPage page : allPages) {
PDResources res = page.getResources();
// Remove embedded files from the PDF
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles"));
}
}
private void sanitizeMetadata(PDDocument document) {
PDMetadata metadata = document.getDocumentCatalog().getMetadata();
if (metadata != null) {
document.getDocumentCatalog().setMetadata(null);
}
}
private void sanitizeLinks(PDDocument document) throws IOException {
for (PDPage page : document.getPages()) {
for (PDAnnotation annotation : page.getAnnotations()) {
if (annotation instanceof PDAnnotationLink) {
PDAction action = ((PDAnnotationLink) annotation).getAction();
if (action instanceof PDActionLaunch || action instanceof PDActionURI) {
((PDAnnotationLink) annotation).setAction(null);
}
}
}
}
}
private void sanitizeFonts(PDDocument document) {
for (PDPage page : document.getPages()) {
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font"));
}
}
}

View File

@ -43,4 +43,11 @@ public class SecurityWebController {
model.addAttribute("currentPage", "cert-sign");
return "security/cert-sign";
}
@GetMapping("/sanitize-pdf")
@Hidden
public String sanitizeForm(Model model) {
model.addAttribute("currentPage", "");
return "security/sanitize-pdf";
}
}

View File

@ -152,6 +152,10 @@ home.crop.desc=Crop a PDF to reduce its size (maintains text!)
home.autoSplitPDF.title=Auto Split Pages
home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code
home.sanitizePdf.title=Sanitize
home.sanitizePdf.desc=Remove scripts and other elements from PDF files
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=Download PDF
@ -159,6 +163,49 @@ text=Text
font=Font
selectFillter=-- Select --
pageNum=Page Number
sizes.small=Small
sizes.medium=Medium
sizes.large=Large
sizes.x-large=X-Large
sanitizePDF.title=Sanitize PDF
sanitizePDF.header=Sanitize a PDF file
sanitizePDF.selectText.1=Remove JavaScript actions
sanitizePDF.selectText.2=Remove embedded files
sanitizePDF.selectText.3=Remove metadata
sanitizePDF.selectText.4=Remove links
sanitizePDF.selectText.5=Remove fonts
sanitizePDF.submit=Sanitize PDF
addPageNumbers.title=Add Page Numbers
addPageNumbers.header=Add Page Numbers
addPageNumbers.selectText.1=Select PDF file:
addPageNumbers.selectText.2=Margin Size
addPageNumbers.selectText.3=Position
addPageNumbers.selectText.4=Starting Number
addPageNumbers.selectText.5=Pages to Number
addPageNumbers.selectText.6=Custom Text
addPageNumbers.submit=Add Page Numbers
auto-rename.title=Auto Rename
auto-rename.header=Auto Rename PDF
auto-rename.submit=Auto Rename
adjustContrast.title=Adjust Contrast
adjustContrast.header=Adjust Contrast
adjustContrast.contrast=Contrast:
adjustContrast.brightness=Brightness:
adjustContrast.saturation=Saturation:
adjustContrast.download=Download
crop.title=Crop
crop.header=Crop Image
crop.submit=Submit
autoSplitPDF.title=Auto Split PDF
autoSplitPDF.header=Auto Split PDF
autoSplitPDF.submit=Submit
pipeline.title=Pipeline

View File

@ -0,0 +1 @@
<svg height="48" viewBox="0 0 48 48" width="48" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h48v48h-48z" fill="none"/><path d="m8.091 21c0 1.089-.576 1.695-1.304 2.464-.755.797-1.696 1.788-1.696 3.394v11.142c0 2.757 2.243 5 5 5h12c2.757 0 5-2.243 5-5v-11.143c0-2.316-2.045-3.302-4.022-4.254-2.447-1.179-4.978-2.397-4.978-6.104v-.215l-.088-.195c-.081-.179-.287-.608-.6-1.09h1.969l2.032-1.242 5.949 5.949 1.414-1.414-5.608-5.608 1.841-1.123v-6.561h-14.186l-5.713 3.428-.12 6.572h3.46c-.219.456-.351.961-.351 1.5v4.5zm-1.01-11.428 4.287-2.572h11.632v3.439l-4.19 2.561h-4.219-3-4.572zm3.01 11.428v-4.5c0-.827.673-1.5 1.5-1.5h3c.341 0 1.054.832 1.502 1.731.108 4.784 3.569 6.451 6.107 7.674 1.846.89 2.89 1.441 2.89 2.452v11.143c0 1.654-1.346 3-3 3h-12c-1.654 0-3-1.346-3-3v-11.143c0-.771.415-1.244 1.147-2.017.826-.87 1.854-1.953 1.854-3.84z"/><path d="m15.091 38h2v-5h5v-2h-5v-5h-2v5h-5v2h5z"/><circle cx="30.091" cy="8" r="2"/><circle cx="36.091" cy="8" r="2"/><circle cx="42.091" cy="8" r="2"/><circle cx="33.091" cy="13" r="2"/><circle cx="37.091" cy="17" r="2"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -14,12 +14,12 @@
<div class="col-md-6">
<h2 th:text="#{crop.header}"></h2>
<form id="cropForm" action="/crop" method="post" enctype="multipart/form-data">
<input id="fileInput" type="file" name="file" required>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<input id="x" type="hidden" name="x">
<input id="y" type="hidden" name="y">
<input id="width" type="hidden" name="width">
<input id="height" type="hidden" name="height">
<button type="submit">Submit</button>
<button type="submit" class="btn btn-primary" th:text="#{crop.submit}"></button>
</form>
<canvas id="pdf-canvas"></canvas>
<script>
@ -28,7 +28,7 @@
let context = canvas.getContext('2d');
let cropForm = document.getElementById('cropForm');
let fileInput = document.getElementById('fileInput');
let fileInput = document.getElementById('fileInput-input');
let xInput = document.getElementById('x');
let yInput = document.getElementById('y');
let widthInput = document.getElementById('width');
@ -91,8 +91,6 @@
});
}
</script>
</div>

View File

@ -40,7 +40,7 @@
</li>-->
<li class="nav-item nav-item-separator"></li>
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='merge-pdfs' OR ${currentPage}=='split-pdfs' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' OR ${currentPage}=='multi-page-layout' OR ${currentPage}=='scale-pages' ? 'active' : ''">
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='merge-pdfs' OR ${currentPage}=='split-pdfs' OR ${currentPage}=='crop' OR ${currentPage}=='adjust-contrast' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' OR ${currentPage}=='multi-page-layout' OR ${currentPage}=='scale-pages' ? 'active' : ''">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="icon" src="images/file-earmark-pdf.svg" alt="icon">
<span class="icon-text" th:text="#{navbar.pageOps}"></span>
@ -55,6 +55,8 @@
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'multi-page-layout', 'images/page-layout.svg', 'home.pageLayout.title', 'home.pageLayout.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'scale-pages', 'images/scale-pages.svg', 'home.scalePages.title', 'home.scalePages.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'auto-split-pdf', 'images/layout-split.svg', 'home.autoSplitPDF.title', 'home.autoSplitPDF.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('adjust-contrast', 'images/adjust-contrast.svg', 'home.adjust-contrast.title', 'home.adjust-contrast.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('crop', 'images/crop.svg', 'home.crop.title', 'home.crop.desc')}"></div>
</div>
@ -96,11 +98,12 @@
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('change-permissions', 'images/shield-lock.svg', 'home.permissions.title', 'home.permissions.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'images/award.svg', 'home.certSign.title', 'home.certSign.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('sanitize-pdf', 'images/sanitize.svg', 'home.sanitizePdf.title', 'home.sanitizePdf.desc')}"></div>
</div>
</li>
<li class="nav-item nav-item-separator"></li>
<li class="nav-item dropdown" th:classappend="${currentPage}=='sign' OR ${currentPage}=='repair' OR ${currentPage}=='compare' OR ${currentPage}=='flatten' OR ${currentPage}=='remove-blanks' OR ${currentPage}=='extract-image-scans' OR ${currentPage}=='change-metadata' OR ${currentPage}=='add-image' OR ${currentPage}=='ocr-pdf' OR ${currentPage}=='change-permissions' OR ${currentPage}=='extract-images' OR ${currentPage}=='compress-pdf' OR ${currentPage}=='add-page-numbers' OR ${currentPage}=='auto-rename' OR ${currentPage}=='adjust-contrast' OR ${currentPage}=='crop' ? 'active' : ''">
<li class="nav-item dropdown" th:classappend="${currentPage}=='sign' OR ${currentPage}=='repair' OR ${currentPage}=='compare' OR ${currentPage}=='flatten' OR ${currentPage}=='remove-blanks' OR ${currentPage}=='extract-image-scans' OR ${currentPage}=='change-metadata' OR ${currentPage}=='add-image' OR ${currentPage}=='ocr-pdf' OR ${currentPage}=='change-permissions' OR ${currentPage}=='extract-images' OR ${currentPage}=='compress-pdf' OR ${currentPage}=='add-page-numbers' OR ${currentPage}=='auto-rename' ? 'active' : ''">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="icon" src="images/card-list.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;">
<span class="icon-text" th:text="#{navbar.other}"></span>
@ -121,8 +124,6 @@
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('compare', 'images/scales.svg', 'home.compare.title', 'home.compare.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-page-numbers', 'images/add-page-numbers.svg', 'home.add-page-numbers.title', 'home.add-page-numbers.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-rename', 'images/fonts.svg', 'home.auto-rename.title', 'home.auto-rename.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('adjust-contrast', 'images/adjust-contrast.svg', 'home.adjust-contrast.title', 'home.adjust-contrast.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('crop', 'images/crop.svg', 'home.crop.title', 'home.crop.desc')}"></div>
</div>
</li>

View File

@ -78,7 +78,7 @@
<div th:replace="~{fragments/card :: card(id='adjust-contrast', cardTitle=#{home.adjust-contrast.title}, cardText=#{home.adjust-contrast.desc}, cardLink='adjust-contrast', svgPath='images/adjust-contrast.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='crop', cardTitle=#{home.crop.title}, cardText=#{home.crop.desc}, cardLink='crop', svgPath='images/crop.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='auto-split-pdf', cardTitle=#{home.autoSplitPDF.title}, cardText=#{home.autoSplitPDF.desc}, cardLink='auto-split-pdf', svgPath='images/layout-split.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='sanitize-pdf', cardTitle=#{home.sanitizePdf.title}, cardText=#{home.sanitizePdf.desc}, cardLink='sanitize-pdf', svgPath='images/sanitize.svg')}"></div>
</div>
</div> </div>

View File

@ -23,15 +23,13 @@
th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<br>
<div class="form-group">
<label for="customMargin">Margin Size</label> <select
<label for="customMargin" th:text="#{addPageNumbers.selectText.2}"></label> <select
class="form-control" id="customMargin" name="customMargin"
required>
<option value="" disabled selected>Select a margin
size</option>
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
<option value="x-large">X-Large</option>
<option value="small" th:text="#{sizes.small}"></option>
<option value="medium" selected th:text="#{sizes.medium}"></option>
<option value="large" th:text="#{sizes.large}"></option>
<option value="x-large" th:text="#{sizes.x-large}"></option>
</select>
</div>
<style>
@ -80,7 +78,7 @@
<div class="form-group">
<label for="position">Position</label>
<label for="position" th:text="#{addPageNumbers.selectText.3}"></label>
<div class="a4container">
<div class="pageNumber" id="1" style="top: 10%; left: 10%;">1</div>
<div class="pageNumber" id="2" style="top: 10%; left: 50%;">2</div>
@ -92,27 +90,23 @@
<div class="pageNumber" id="8" style="top: 90%; left: 50%;">8</div>
<div class="pageNumber" id="9" style="top: 90%; left: 90%;">9</div>
</div>
</div>
<input type="hidden" id="numberInput" name="position" min="1"
max="9" required>
<div class="form-group">
<label for="startingNumber">Starting Number</label> <input
<label for="startingNumber" th:text="#{addPageNumbers.selectText.4}"></label> <input
type="number" class="form-control" id="startingNumber"
name="startingNumber" min="1" required value="1" />
</div>
<div class="form-group">
<label for="pagesToNumber">Pages to Number</label> <input
<label for="pagesToNumber" th:text="#{addPageNumbers.selectText.5}"></label> <input
type="text" class="form-control" id="pagesToNumber"
name="pagesToNumber"
placeholder="Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc" />
</div>
<div class="form-group">
<label for="customText">Custom Text</label> <input type="text"
<label for="customText" th:text="#{addPageNumbers.selectText.6}"></label> <input type="text"
class="form-control" id="customText" name="customText"
placeholder="Default just number, also accepts 'Page {n} of {total}', 'Tag-{n}' etc" />
</div>

View File

@ -4,7 +4,7 @@
xmlns:th="http://www.thymeleaf.org">
<th:block
th:insert="~{fragments/common :: head(title=#{extractImages.title})}"></th:block>
th:insert="~{fragments/common :: head(title=#{adjustContrast.title})}"></th:block>
<body>
@ -15,273 +15,274 @@
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{extractImages.header}"></h2>
<input type="file" id="pdf-file" accept="application/pdf" />
<canvas id="pdf-canvas"></canvas>
<h2 th:text="#{adjustContrast.header}"></h2>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf', remoteCall='false')}"></div>
<h4>
Contrast: <span id="contrast-val">100</span>%
<span th:text="#{adjustContrast.contrast}"></span> <span id="contrast-val">100</span>%
</h4>
<input type="range" min="0" max="200" value="100"
id="contrast-slider" />
<h4>
Brightness: <span id="brightness-val">100</span>%
<span th:text="#{adjustContrast.brightness}"></span> <span id="brightness-val">100</span>%
</h4>
<input type="range" min="0" max="200" value="100"
id="brightness-slider" />
<h4>
Saturation: <span id="saturation-val">100</span>%
<span th:text="#{adjustContrast.saturation}"></span> <span id="saturation-val">100</span>%
</h4>
<input type="range" min="0" max="200" value="100"
id="saturation-slider" />
<button id="download-button">Download</button>
</br>
<canvas id="pdf-canvas"></canvas>
<button id="download-button" class="btn btn-primary" th:text="#{adjustContrast.download}"></button>
<script src="pdfjs/pdf.js"></script>
<script>
var canvas = document.getElementById('pdf-canvas');
var context = canvas.getContext('2d');
var originalImageData = null;
var allPages = [];
var pdfDoc = null;
var pdf = null; // This is the current PDF document
var canvas = document.getElementById('pdf-canvas');
var context = canvas.getContext('2d');
var originalImageData = null;
var allPages = [];
var pdfDoc = null;
var pdf = null; // This is the current PDF document
async function renderPDFAndSaveOriginalImageData(file) {
var fileReader = new FileReader();
fileReader.onload = async function() {
var data = new Uint8Array(this.result);
pdf = await pdfjsLib.getDocument({data: data}).promise;
async function renderPDFAndSaveOriginalImageData(file) {
var fileReader = new FileReader();
fileReader.onload = async function() {
var data = new Uint8Array(this.result);
pdf = await pdfjsLib.getDocument({data: data}).promise;
// Get the number of pages in the PDF
var numPages = pdf.numPages;
allPages = Array.from({length: numPages}, (_, i) => i + 1);
// Get the number of pages in the PDF
var numPages = pdf.numPages;
allPages = Array.from({length: numPages}, (_, i) => i + 1);
// Create a new PDF document
pdfDoc = await PDFLib.PDFDocument.create();
// Render the first page in the viewer
await renderPageAndAdjustImageProperties(1);
};
fileReader.readAsArrayBuffer(file);
}
// Create a new PDF document
pdfDoc = await PDFLib.PDFDocument.create();
// Render the first page in the viewer
await renderPageAndAdjustImageProperties(1);
};
fileReader.readAsArrayBuffer(file);
}
// This function is now async and returns a promise
function renderPageAndAdjustImageProperties(pageNum) {
return new Promise(async function(resolve, reject) {
var page = await pdf.getPage(pageNum);
var scale = 1.5;
var viewport = page.getViewport({ scale: scale });
// This function is now async and returns a promise
function renderPageAndAdjustImageProperties(pageNum) {
return new Promise(async function(resolve, reject) {
var page = await pdf.getPage(pageNum);
var scale = 1.5;
var viewport = page.getViewport({ scale: scale });
canvas.height = viewport.height;
canvas.width = viewport.width;
canvas.height = viewport.height;
canvas.width = viewport.width;
var renderContext = {
canvasContext: context,
viewport: viewport
};
var renderContext = {
canvasContext: context,
viewport: viewport
};
var renderTask = page.render(renderContext);
renderTask.promise.then(function () {
originalImageData = context.getImageData(0, 0, canvas.width, canvas.height);
adjustImageProperties();
resolve();
});
});
}
var renderTask = page.render(renderContext);
renderTask.promise.then(function () {
originalImageData = context.getImageData(0, 0, canvas.width, canvas.height);
adjustImageProperties();
resolve();
});
});
}
function adjustImageProperties() {
var contrast = parseFloat(document.getElementById('contrast-slider').value);
var brightness = parseFloat(document.getElementById('brightness-slider').value);
var saturation = parseFloat(document.getElementById('saturation-slider').value);
function adjustImageProperties() {
var contrast = parseFloat(document.getElementById('contrast-slider').value);
var brightness = parseFloat(document.getElementById('brightness-slider').value);
var saturation = parseFloat(document.getElementById('saturation-slider').value);
contrast /= 100; // normalize to range [0, 2]
brightness /= 100; // normalize to range [0, 2]
saturation /= 100; // normalize to range [0, 2]
contrast /= 100; // normalize to range [0, 2]
brightness /= 100; // normalize to range [0, 2]
saturation /= 100; // normalize to range [0, 2]
if (originalImageData) {
var newImageData = context.createImageData(originalImageData.width, originalImageData.height);
newImageData.data.set(originalImageData.data);
if (originalImageData) {
var newImageData = context.createImageData(originalImageData.width, originalImageData.height);
newImageData.data.set(originalImageData.data);
for(var i=0; i<newImageData.data.length; i+=4)
{
var r = newImageData.data[i];
var g = newImageData.data[i+1];
var b = newImageData.data[i+2];
// Adjust contrast
r = adjustContrastForPixel(r, contrast);
g = adjustContrastForPixel(g, contrast);
b = adjustContrastForPixel(b, contrast);
// Adjust brightness
r = adjustBrightnessForPixel(r, brightness);
g = adjustBrightnessForPixel(g, brightness);
b = adjustBrightnessForPixel(b, brightness);
// Adjust saturation
var rgb = adjustSaturationForPixel(r, g, b, saturation);
newImageData.data[i] = rgb[0];
newImageData.data[i+1] = rgb[1];
newImageData.data[i+2] = rgb[2];
}
for(var i=0; i<newImageData.data.length; i+=4)
{
var r = newImageData.data[i];
var g = newImageData.data[i+1];
var b = newImageData.data[i+2];
// Adjust contrast
r = adjustContrastForPixel(r, contrast);
g = adjustContrastForPixel(g, contrast);
b = adjustContrastForPixel(b, contrast);
// Adjust brightness
r = adjustBrightnessForPixel(r, brightness);
g = adjustBrightnessForPixel(g, brightness);
b = adjustBrightnessForPixel(b, brightness);
// Adjust saturation
var rgb = adjustSaturationForPixel(r, g, b, saturation);
newImageData.data[i] = rgb[0];
newImageData.data[i+1] = rgb[1];
newImageData.data[i+2] = rgb[2];
}
context.putImageData(newImageData, 0, 0);
}
}
context.putImageData(newImageData, 0, 0);
}
}
function rgbToHsl(r, g, b) {
r /= 255, g /= 255, b /= 255;
function rgbToHsl(r, g, b) {
r /= 255, g /= 255, b /= 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
} else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
if (max === min) {
h = s = 0; // achromatic
} else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
h /= 6;
}
return [h, s, l];
}
return [h, s, l];
}
function hslToRgb(h, s, l) {
var r, g, b;
function hslToRgb(h, s, l) {
var r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
var hue2rgb = function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
if (s === 0) {
r = g = b = l; // achromatic
} else {
var hue2rgb = function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [r * 255, g * 255, b * 255];
}
return [r * 255, g * 255, b * 255];
}
function adjustContrastForPixel(pixel, contrast) {
// Normalize to range [-0.5, 0.5]
var normalized = pixel / 255 - 0.5;
function adjustContrastForPixel(pixel, contrast) {
// Normalize to range [-0.5, 0.5]
var normalized = pixel / 255 - 0.5;
// Apply contrast
normalized *= contrast;
// Apply contrast
normalized *= contrast;
// Denormalize back to [0, 255]
return (normalized + 0.5) * 255;
}
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
// Denormalize back to [0, 255]
return (normalized + 0.5) * 255;
}
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function adjustSaturationForPixel(r, g, b, saturation) {
var hsl = rgbToHsl(r, g, b);
function adjustSaturationForPixel(r, g, b, saturation) {
var hsl = rgbToHsl(r, g, b);
// Adjust saturation
hsl[1] = clamp(hsl[1] * saturation, 0, 1);
// Adjust saturation
hsl[1] = clamp(hsl[1] * saturation, 0, 1);
// Convert back to RGB
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
// Convert back to RGB
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
// Return adjusted RGB values
return rgb;
}
// Return adjusted RGB values
return rgb;
}
function adjustBrightnessForPixel(pixel, brightness) {
return Math.max(0, Math.min(255, pixel * brightness));
}
function adjustBrightnessForPixel(pixel, brightness) {
return Math.max(0, Math.min(255, pixel * brightness));
}
async function downloadPDF() {
for (var i = 0; i < allPages.length; i++) {
await renderPageAndAdjustImageProperties(allPages[i]);
const pngImageBytes = canvas.toDataURL('image/png');
const pngImage = await pdfDoc.embedPng(pngImageBytes);
const pngDims = pngImage.scale(1);
async function downloadPDF() {
for (var i = 0; i < allPages.length; i++) {
await renderPageAndAdjustImageProperties(allPages[i]);
const pngImageBytes = canvas.toDataURL('image/png');
const pngImage = await pdfDoc.embedPng(pngImageBytes);
const pngDims = pngImage.scale(1);
// Create a blank page matching the dimensions of the image
const page = pdfDoc.addPage([pngDims.width, pngDims.height]);
// Create a blank page matching the dimensions of the image
const page = pdfDoc.addPage([pngDims.width, pngDims.height]);
// Draw the PNG image
page.drawImage(pngImage, {
x: 0,
y: 0,
width: pngDims.width,
height: pngDims.height
});
}
// Draw the PNG image
page.drawImage(pngImage, {
x: 0,
y: 0,
width: pngDims.width,
height: pngDims.height
});
}
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save();
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save();
// Create a Blob
const blob = new Blob([pdfBytes.buffer], {type: "application/pdf"});
// Create a Blob
const blob = new Blob([pdfBytes.buffer], {type: "application/pdf"});
// Create download link
const downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(blob);
downloadLink.download = "download.pdf";
downloadLink.click();
// Create download link
const downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(blob);
downloadLink.download = "download.pdf";
downloadLink.click();
// After download, reset the viewer and clear stored data
allPages = []; // Clear the pages
originalImageData = null; // Clear the image data
// After download, reset the viewer and clear stored data
allPages = []; // Clear the pages
originalImageData = null; // Clear the image data
// Go back to page 1 and render it in the viewer
if (pdf !== null) {
renderPageAndAdjustImageProperties(1);
}
// Go back to page 1 and render it in the viewer
if (pdf !== null) {
renderPageAndAdjustImageProperties(1);
}
}
}
// Event listeners
document.getElementById('pdf-file').addEventListener('change', function(e) {
if (e.target.files.length > 0) {
renderPDFAndSaveOriginalImageData(e.target.files[0]);
}
});
// Event listeners
document.getElementById('fileInput-input').addEventListener('change', function(e) {
if (e.target.files.length > 0) {
renderPDFAndSaveOriginalImageData(e.target.files[0]);
}
});
document.getElementById('contrast-slider').addEventListener('input', function() {
document.getElementById('contrast-val').textContent = this.value;
adjustImageProperties();
});
document.getElementById('contrast-slider').addEventListener('input', function() {
document.getElementById('contrast-val').textContent = this.value;
adjustImageProperties();
});
document.getElementById('brightness-slider').addEventListener('input', function() {
document.getElementById('brightness-val').textContent = this.value;
adjustImageProperties();
});
document.getElementById('saturation-slider').addEventListener('input', function() {
document.getElementById('saturation-val').textContent = this.value;
adjustImageProperties();
});
document.getElementById('download-button').addEventListener('click', function() {
downloadPDF();
});
</script>
document.getElementById('brightness-slider').addEventListener('input', function() {
document.getElementById('brightness-val').textContent = this.value;
adjustImageProperties();
});
document.getElementById('saturation-slider').addEventListener('input', function() {
document.getElementById('saturation-val').textContent = this.value;
adjustImageProperties();
});
document.getElementById('download-button').addEventListener('click', function() {
downloadPDF();
});
</script>
</div>
</div>
</div>

View File

@ -19,7 +19,6 @@
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{auto-rename.submit}"></button>
</form>
<p class="mt-3" th:text="#{auto-rename.credit}"></p>
</div>
</div>
</div>

View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{sanitizePDF.title})}"></th:block>
<body>
<div id="page-container">
<div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{sanitizePDF.header}"></h2>
<form action="sanitize-pdf" method="post" enctype="multipart/form-data">
<div class="form-group">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="removeJavaScript" name="removeJavaScript" checked>
<label class="form-check-label" for="removeJavaScript" th:text="#{sanitizePDF.selectText.1}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="removeEmbeddedFiles" name="removeEmbeddedFiles" checked>
<label class="form-check-label" for="removeEmbeddedFiles" th:text="#{sanitizePDF.selectText.2}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="removeMetadata" name="removeMetadata" checked>
<label class="form-check-label" for="removeMetadata" th:text="#{sanitizePDF.selectText.3}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="removeLinks" name="removeLinks" checked>
<label class="form-check-label" for="removeLinks" th:text="#{sanitizePDF.selectText.4}"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="removeFonts" name="removeFonts" checked>
<label class="form-check-label" for="removeFonts" th:text="#{sanitizePDF.selectText.5}"></label>
</div>
<br />
<div class="form-group text-center">
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{sanitizePDF.submit}"></button>
</div>
</form>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>