compare init, robots, sign lang and local js
This commit is contained in:
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).
|
||||||
|
|
BIN
src/main/resources/static/fonts/Estonia.woff2
Normal file
BIN
src/main/resources/static/fonts/Estonia.woff2
Normal file
Binary file not shown.
BIN
src/main/resources/static/fonts/Tangerine.woff2
Normal file
BIN
src/main/resources/static/fonts/Tangerine.woff2
Normal file
Binary file not shown.
3
src/main/resources/static/js/interact.min.js
vendored
Normal file
3
src/main/resources/static/js/interact.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
src/main/resources/static/js/signature_pad.umd.min.js
vendored
Normal file
6
src/main/resources/static/js/signature_pad.umd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
135
src/main/resources/templates/other/compare.html
Normal file
135
src/main/resources/templates/other/compare.html
Normal 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>
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue