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

View file

@ -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,14 +69,26 @@ 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) {
model.addAttribute("currentPage", "sign"); model.addAttribute("currentPage", "sign");
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: /";
}
}
} }

View file

@ -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";

View file

@ -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

View file

@ -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>

View file

@ -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">