compare init, robots, sign lang and local js

This commit is contained in:
Anthony Stirling 2023-05-08 00:17:20 +01:00
parent 1d55ee7f93
commit acf4662d2f
9 changed files with 198 additions and 25 deletions

@ -1,8 +1,10 @@
package stirling.software.SPDF.controller.web; package stirling.software.SPDF.controller.web;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Hidden;
@ -67,9 +69,6 @@ public class GeneralWebController {
return "split-pdfs"; return "split-pdfs";
} }
@GetMapping("/sign") @GetMapping("/sign")
@Hidden @Hidden
public String signForm(Model model) { public String signForm(Model model) {
@ -77,4 +76,19 @@ public class GeneralWebController {
return "sign"; return "sign";
} }
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody
public String getRobotsTxt() {
String allowGoogleVisibility = System.getProperty("ALLOW_GOOGLE_VISABILITY");
if (allowGoogleVisibility == null)
allowGoogleVisibility = System.getenv("ALLOW_GOOGLE_VISABILITY");
if (allowGoogleVisibility == null)
allowGoogleVisibility = "true";
if (Boolean.parseBoolean(allowGoogleVisibility)) {
return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nDisallow: /";
} else {
return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /";
}
}
} }

@ -53,6 +53,12 @@ public class OtherWebController {
return "other/change-metadata"; return "other/change-metadata";
} }
@GetMapping("/compare")
@Hidden
public String compareForm(Model model) {
model.addAttribute("currentPage", "compare");
return "other/compare";
}
public List<String> getAvailableTesseractLanguages() { public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tesseract-ocr/4.00/tessdata"; String tessdataDir = "/usr/share/tesseract-ocr/4.00/tessdata";

@ -117,8 +117,17 @@ home.flatten.desc=Remove all interactive elements and forms from a PDF
home.repair.title=Repair home.repair.title=Repair
home.repair.desc=Tries to repair a corrupt/broken PDF home.repair.desc=Tries to repair a corrupt/broken PDF
downloadPdf=Download PDF
text=Text
font=Font
sign.title=Sign sign.title=Sign
sign.header=Sign PDFs sign.header=Sign PDFs
sign.upload=Upload Image
sign.draw=Draw Signature
sign.text=Text Input
sign.clear=Clear
sign.add=Add
ScannerImageSplit.selectText.1=Angle Threshold: ScannerImageSplit.selectText.1=Angle Threshold:
ScannerImageSplit.selectText.2=Sets the minimum absolute angle required for the image to be rotated (default: 10). ScannerImageSplit.selectText.2=Sets the minimum absolute angle required for the image to be rotated (default: 10).

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,135 @@
<!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=#{repair.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="#{repair.header}"></h2>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput2', multiple=false, accept='application/pdf')}"></div>
<button onclick="comparePDFs()">Compare</button>
<div id="result"></div>
<script>
async function comparePDFs() {
const file1 = document.getElementById("fileInput-input").files[0];
const file2 = document.getElementById("fileInput2-input").files[0];
if (!file1 || !file2) {
console.error("Please select two PDF files to compare");
return;
}
const [pdf1, pdf2] = await Promise.all([
pdfjsLib.getDocument(URL.createObjectURL(file1)).promise,
pdfjsLib.getDocument(URL.createObjectURL(file2)).promise
]);
const extractText = async (pdf) => {
const pages = [];
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const content = await page.getTextContent();
const strings = content.items.map(item => item.str);
pages.push(strings.join(""));
}
return pages.join("\n");
};
const [text1, text2] = await Promise.all([
extractText(pdf1),
extractText(pdf2)
]);
const diff = (text1, text2) => {
const lines1 = text1.split("\n");
const lines2 = text2.split("\n");
const result = [];
let i = 0, j = 0;
while (i < lines1.length || j < lines2.length) {
console.log(`lines1[${i}]='${lines1[i]}', lines2[${j}]='${lines2[j]}'`);
console.log(`i=${i}, j=${j}`);
if (lines1[i] === lines2[j]) {
result.push([i, j, lines1[i]]);
i++;
j++;
console.log(`i=${i}, j=${j}`);
} else {
let k = i, l = j;
while (k < lines1.length && l < lines2.length && lines1[k] !== lines2[l]) {
k++;
l++;
}
for (let x = i; x < k; x++) {
result.push([x, -1, lines1[x]]);
}
for (let y = j; y < l; y++) {
result.push([-1, y, lines2[y]]);
}
i = k;
j = l;
}
}
return result;
};
const differences = diff(text1, text2);
const highlightDifferences = async (pdf, differences) => {
for (const difference of differences) {
const [pageIndex, lineIndex, lineText] = difference;
if (lineIndex === -1) {
continue;
}
console.log(pageIndex);
const page = await pdf.getPage(pageIndex);
const viewport = page.getViewport({ scale: 1 });
const [left,top] = viewport.convertToViewportPoint(0, lineIndex * 20);
const [right, bottom] = viewport.convertToViewportPoint(500, (lineIndex + 1) * 20);
const annotation = {
type: "Highlight",
rect: [left, top, right - left, bottom - top],
color: [255, 255, 0],
opacity: 0.5,
quadPoints:
[
left, top, right, top, right, bottom, left, bottom
]
};
await page.addAnnotation(annotation);
const message = `Difference found in page ${pageIndex }, line ${lineIndex + 1}: ${lineText}`;
const p = document.createElement("p");
p.textContent = message;
document.getElementById("result").appendChild(p);
}
};
await highlightDifferences(pdf1, differences);
}
</script>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>

@ -3,20 +3,21 @@
<head> <head>
<th:block th:insert="~{fragments/common :: head(title=#{sign.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{sign.title})}"></th:block>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="js/signature_pad.umd.min.js"></script>
<title>PDF Signature App</title> <script src="js/interact.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet">
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/interact.js/1.10.11/interact.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Estonia&family=Tangerine&family=Windsong&display=swap" rel="stylesheet">
</head>
</head>
<style>
@font-face {
font-family: 'Estonia';
src: url(fonts/Estonia.woff2) format('woff2');
}
@font-face {
font-family: 'Tangerine';
src: url(fonts/Tangerine.woff2) format('woff2');
}
</style>
<body> <body>
<th:block th:insert="~{fragments/common.html :: game}"></th:block>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
@ -49,7 +50,7 @@
</script> </script>
<div class="tab-group show-on-file-selected"> <div class="tab-group show-on-file-selected">
<div class="tab-container" title="Upload Image"> <div class="tab-container" th:title="#{sign.upload}">
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', multiple=true, accept='image/*', inputText=#{imgPrompt})}"></div> <div th:replace="~{fragments/common :: fileSelector(name='image-upload', multiple=true, accept='image/*', inputText=#{imgPrompt})}"></div>
<script> <script>
const imageUpload = document.querySelector('input[name=image-upload]'); const imageUpload = document.querySelector('input[name=image-upload]');
@ -67,11 +68,11 @@
}); });
</script> </script>
</div> </div>
<div class="tab-container drawing-pad-container" title="Draw Signature"> <div class="tab-container drawing-pad-container" th:title="#{sign.draw}">
<canvas id="drawing-pad-canvas"></canvas> <canvas id="drawing-pad-canvas"></canvas>
<br> <br>
<button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()">Clear</button> <button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()" th:text="#{sign.clear}"></button>
<button id="save-signature" class="btn btn-outline-success mt-2" onclick="addDraggableFromPad()">Add</button> <button id="save-signature" class="btn btn-outline-success mt-2" onclick="addDraggableFromPad()" th:text="#{sign.add}"></button>
<script> <script>
const signaturePadCanvas = document.getElementById('drawing-pad-canvas'); const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
const signaturePad = new SignaturePad(signaturePadCanvas, { const signaturePad = new SignaturePad(signaturePadCanvas, {
@ -95,17 +96,16 @@
} }
</style> </style>
</div> </div>
<div class="tab-container" title="Text Input"> <div class="tab-container" th:title="#{sign.text}">
<label class="form-check-label" for="sigText">Text</label> <label class="form-check-label" for="sigText" th:text="#{text}"></label>
<input type="text" class="form-control" id="sigText" name="sigText"> <input type="text" class="form-control" id="sigText" name="sigText">
<label>Font:</label> <label th:text="#{font}"></label>
<select class="form-control" name="font" id="font-select"> <select class="form-control" name="font" id="font-select">
<option value="Estonia" class="estonia-font">Estonia</option> <option value="Estonia" class="estonia-font">Estonia</option>
<option value="Tangerine" class="tangerine-font">Tangerine</option> <option value="Tangerine" class="tangerine-font">Tangerine</option>
<option value="Windsong" class="windsong-font">Windsong</option>
</select> </select>
<div class="margin-auto-parent"> <div class="margin-auto-parent">
<button id="save-text-signature" class="btn btn-outline-success mt-2 margin-center" onclick="addDraggableFromText()">Add</button> <button id="save-text-signature" class="btn btn-outline-success mt-2 margin-center" onclick="addDraggableFromText()" th:text="#{sign.add}"></button>
</div> </div>
<script> <script>
function addDraggableFromText() { function addDraggableFromText() {
@ -148,7 +148,7 @@
<!-- draggables box --> <!-- draggables box -->
<div id="box-drag-container" class="show-on-file-selected"> <div id="box-drag-container" class="show-on-file-selected">
<canvas id="pdf-canvas"></canvas> <canvas id="pdf-canvas"></canvas>
<script src="/js/draggable-utils.js"></script> <script src="js/draggable-utils.js"></script>
<div class="draggable-buttons-box ignore-rtl"> <div class="draggable-buttons-box ignore-rtl">
<button class="btn btn-outline-secondary" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())"> <button class="btn btn-outline-secondary" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">