sign crop init

This commit is contained in:
Anthony Stirling 2023-03-23 15:39:51 +00:00
parent b5a59ddb6a
commit 0890073bd4
5 changed files with 468 additions and 1 deletions

View File

@ -0,0 +1,66 @@
package stirling.software.SPDF.controller;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
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.graphics.image.PDImageXObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Controller
public class CropController {
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
@GetMapping("/crop-pdf")
public String compressPdfForm(Model model) {
model.addAttribute("currentPage", "crop-pdf");
return "crop-pdf";
}
}

View File

@ -0,0 +1,66 @@
package stirling.software.SPDF.controller;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
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.graphics.image.PDImageXObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Controller
public class SignController {
private static final Logger logger = LoggerFactory.getLogger(SignController.class);
@GetMapping("/sign-pdf")
public String compressPdfForm(Model model) {
model.addAttribute("currentPage", "sign-pdf");
return "sign-pdf";
}
}

View File

@ -33,7 +33,7 @@
<input type="checkbox" name="jbig2Lossy" id="jbig2Lossy">
<label for="jbig2Lossy" th:text="#{compress.selectText.7}"></label>
</div>
<button type="submit" th:text="#{compress.submit}"></button>
<button type="submit" class="btn btn-primary" th:text="#{compress.submit}"></button>
</form>
<p class="mt-3" th:text="#{compress.credit}"></p>

View File

@ -0,0 +1,162 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{extractImages.title})}"></th:block>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.css" />
<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="#{extractImages.header}"></h2>
<input type="file" id="pdfFile" accept=".pdf" />
<canvas id="pdfCanvas" style="border:1px solid black;"></canvas>
<input type="number" id="pageNumber" min="1" />
<button id="goToPage">Go to Page</button>
<button id="cropPdf">Crop PDF</button>
<button id="downloadPdf">Download PDF</button>
<Script>
const pdfCanvas = document.getElementById('pdfCanvas');
const ctx = pdfCanvas.getContext('2d');
let pdfDoc = null;
let currentPage = 1;
let scaleFactor = 1.0;
let cropper = null;
async function renderPage(pageNumber) {
const page = await pdfDoc.getPage(pageNumber);
const viewport = page.getViewport({ scale: scaleFactor });
pdfCanvas.width = viewport.width;
pdfCanvas.height = viewport.height;
const renderContext = {
canvasContext: ctx,
viewport: viewport,
};
await page.render(renderContext);
}
document.getElementById('pdfFile').addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
const fileReader = new FileReader();
fileReader.onload = async (e) => {
const typedArray = new Uint8Array(e.target.result);
pdfDoc = await pdfjsLib.getDocument(typedArray).promise;
await renderPage(currentPage);
if (cropper) cropper.destroy();
cropper = new Cropper(pdfCanvas, {
viewMode: 1,
autoCropArea: 1,
background: false, // Disable the default checkered background
modal: false, // Disable the default shading of the cropped area
dragMode: "crop",
cropBoxResizable: true,
guides: false, // Disable the default dashed crop box guides
// Set a custom background color with transparency
cropBox: {
background: "rgba(0, 0, 0, 0.2)",
},
});
};
fileReader.readAsArrayBuffer(file);
});
document.getElementById('goToPage').addEventListener('click', async () => {
currentPage = parseInt(document.getElementById('pageNumber').value);
await renderPage(currentPage);
if (cropper) cropper.destroy();
cropper = new Cropper(pdfCanvas, {
viewMode: 1,
autoCropArea: 1,
background: false, // Disable the default checkered background
modal: false, // Disable the default shading of the cropped area
dragMode: "crop",
cropBoxResizable: true,
guides: false, // Disable the default dashed crop box guides
// Set a custom background color with transparency
cropBox: {
background: "rgba(0, 0, 0, 0.2)",
},
});
});
document.getElementById('cropPdf').addEventListener('click', async () => {
if (!cropper) return;
const cropBoxData = cropper.getCropBoxData();
const [x, y, width, height] = [cropBoxData.left, cropBoxData.top, cropBoxData.width, cropBoxData.height];
const page = await pdfDoc.getPage(currentPage);
const croppedPdf = await PDFLib.PDFDocument.create();
const [croppedPage] = await croppedPdf.copyPages(pdfDoc, [currentPage - 1]);
const cropBox = {
left: x / scaleFactor,
bottom: (pdfCanvas.height - y - height) / scaleFactor,
right: (x + width) / scaleFactor,
top: (pdfCanvas.height - y) / scaleFactor,
};
croppedPage.setCropBox(cropBox);
croppedPdf.addPage(croppedPage);
const croppedPdfBytes = await croppedPdf.save();
const croppedPdfBlob = new Blob([croppedPdfBytes], { type: 'application/pdf' });
const downloadLink = document.getElementById('downloadPdf');
downloadLink.href = URL.createObjectURL(croppedPdfBlob);
downloadLink.download = 'cropped.pdf';
});
document.getElementById('downloadPdf').addEventListener('click', async () => {
if (!pdfDoc || !cropper) {
alert('No PDF available. Please load a PDF first.');
return;
}
const cropBoxData = cropper.getCropBoxData();
const [x, y, width, height] = [cropBoxData.left, cropBoxData.top, cropBoxData.width, cropBoxData.height];
const page = await pdfDoc.getPage(currentPage);
const croppedPdf = await PDFLib.PDFDocument.create();
if (!isNaN(currentPage)) {
const [croppedPage] = await croppedPdf.copyPages(pdfDoc, [currentPage - 1]);
const cropBox = {
left: x / scaleFactor,
bottom: (pdfCanvas.height - y - height) / scaleFactor,
right: (x + width) / scaleFactor,
top: (pdfCanvas.height - y) / scaleFactor,
};
croppedPage.setCropBox(cropBox);
croppedPdf.addPage(croppedPage);
}
const croppedPdfBytes = await croppedPdf.save();
const croppedPdfBlob = new Blob([croppedPdfBytes], { type: 'application/pdf' });
const downloadLink = document.getElementById('downloadPdf');
downloadLink.href = URL.createObjectURL(croppedPdfBlob);
downloadLink.download = 'cropped.pdf';
});
</Script>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>

View File

@ -0,0 +1,173 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{extractImages.title})}"></th:block>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.js"></script>
<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="#{extractImages.header}"></h2>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF Signature App</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.min.js"></script>
<script src="https://unpkg.com/pdf-lib@1.18.0/dist/umd/pdf-lib.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/signature_pad@4.1.5/dist/signature_pad.umd.min.js"></script>
<style>
canvas {
border: 1px solid black;
}
</style>
<input type="file" id="pdf-upload" accept=".pdf" />
<div>
<canvas id="pdf-canvas"></canvas>
<canvas id="signature-pad" width="400" height="200"></canvas>
</div>
<button id="save-signature">Save Signature</button>
<button id="download-pdf">Download PDF</button>
<script>
const pdfUpload = document.getElementById('pdf-upload');
// app.js
document.addEventListener('DOMContentLoaded', () => {
const pdfCanvas = document.getElementById('pdf-canvas');
const signatureCanvas = document.getElementById('signature-pad');
const saveSignatureBtn = document.getElementById('save-signature');
const downloadPdfBtn = document.getElementById('download-pdf');
const pdfCtx = pdfCanvas.getContext('2d');
const signaturePad = new SignaturePad(signatureCanvas);
let pdfDoc = null;
let signatureImage = null;
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.worker.min.js';
async function loadPdf(pdfData) {
pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
renderPage(1);
}
pdfUpload.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (file) {
const pdfData = await file.arrayBuffer();
loadPdf(pdfData);
}
});
function renderPage(pageNum) {
pdfDoc.getPage(pageNum).then((page) => {
const viewport = page.getViewport({ scale: 1 });
pdfCanvas.width = viewport.width;
pdfCanvas.height = viewport.height;
const renderCtx = {
canvasContext: pdfCtx,
viewport: viewport,
};
page.render(renderCtx);
});
}
saveSignatureBtn.addEventListener('click', () => {
if (signaturePad.isEmpty()) {
alert('Please provide a signature.');
} else {
signatureImage = signaturePad.toDataURL();
signaturePad.clear();
}
});
downloadPdfBtn.addEventListener('click', async () => {
if (!signatureImage) {
alert('Please save a signature first.');
return;
}
// Load the original PDF
const pdfUrl = 'path/to/your/pdf-file.pdf';
const pdfBytes = await fetch(pdfUrl).then((res) => res.arrayBuffer());
const pdfDoc = await PDFLib.PDFDocument.load(pdfBytes);
// Embed the signature image
const signaturePng = await fetch(signatureImage).then((res) => res.arrayBuffer());
const signatureImageObject = await pdfDoc.embedPng(signaturePng);
// Get the page to insert the signature
const [page] = pdfDoc.getPages();
const { width, height } = page.getSize();
// Set the signature dimensions and position (customize this as needed)
const signatureWidth = 200;
const signatureHeight = 100;
const signatureX = width / 2 - signatureWidth / 2;
const signatureY = height / 2 - signatureHeight / 2;
// Add the signature image to the page
page.drawImage(signatureImageObject, {
x: signatureX,
y: signatureY,
width: signatureWidth,
height: signatureHeight,
});
// Serialize the PDF and create a download link
const pdfBytesModified = await pdfDoc.save();
const blob = new Blob([pdfBytesModified], { type: 'application/pdf' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'signed-document.pdf';
link.click();
});
if (pdfUpload.files[0]) {
(async () => {
if (pdfUpload.files[0]) {
await loadPdf(await pdfUpload.files[0].arrayBuffer());
}
})();
}
});
</script>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>