2023-05-02 23:59:16 +02:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
|
|
|
|
|
|
|
<head>
|
2023-05-06 15:04:55 +02:00
|
|
|
<th:block th:insert="~{fragments/common :: head(title=#{sign.title})}"></th:block>
|
2023-05-02 23:59:16 +02:00
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<title>PDF Signature App</title>
|
|
|
|
<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>
|
2023-05-06 01:48:56 +02:00
|
|
|
<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">
|
2023-05-02 23:59:16 +02:00
|
|
|
</head>
|
|
|
|
|
|
|
|
<body>
|
2023-05-03 23:07:51 +02:00
|
|
|
<th:block th:insert="~{fragments/common.html :: game}"></th:block>
|
2023-05-02 23:59:16 +02:00
|
|
|
<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">
|
2023-05-06 15:04:55 +02:00
|
|
|
<h2 th:text="#{sign.header}"></h2>
|
2023-05-03 23:07:51 +02:00
|
|
|
|
2023-05-07 22:39:34 +02:00
|
|
|
<!-- pdf selector -->
|
|
|
|
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multiple=false, accept='application/pdf')}"></div>
|
|
|
|
<script>
|
|
|
|
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
|
|
|
|
const file = event.target.files[0];
|
|
|
|
if (file) {
|
|
|
|
const pdfData = await file.arrayBuffer();
|
|
|
|
const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
|
|
|
await DraggableUtils.renderPage(pdfDoc, 0);
|
|
|
|
|
|
|
|
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
|
|
|
el.style.cssText = '';
|
|
|
|
})
|
|
|
|
}
|
|
|
|
});
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
|
|
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
|
|
|
el.style.cssText = "display:none !important";
|
|
|
|
})
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<div class="tab-group show-on-file-selected">
|
|
|
|
<div class="tab-container" title="Upload Image">
|
|
|
|
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', multiple=true, accept='image/*', inputText=#{imgPrompt})}"></div>
|
|
|
|
<script>
|
|
|
|
const imageUpload = document.querySelector('input[name=image-upload]');
|
|
|
|
imageUpload.addEventListener('change', e => {
|
|
|
|
if(!e.target.files) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (const imageFile of e.target.files) {
|
|
|
|
var reader = new FileReader();
|
|
|
|
reader.readAsDataURL(imageFile);
|
|
|
|
reader.onloadend = function (e) {
|
|
|
|
DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
</div>
|
|
|
|
<div class="tab-container drawing-pad-container" title="Draw Signature">
|
|
|
|
<canvas id="drawing-pad-canvas"></canvas>
|
|
|
|
<br>
|
|
|
|
<button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()">Clear</button>
|
|
|
|
<button id="save-signature" class="btn btn-outline-success mt-2" onclick="addDraggableFromPad()">Add</button>
|
|
|
|
<script>
|
|
|
|
const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
|
|
|
|
const signaturePad = new SignaturePad(signaturePadCanvas, {
|
|
|
|
minWidth: 1,
|
|
|
|
maxWidth: 2,
|
|
|
|
penColor: 'black',
|
|
|
|
});
|
|
|
|
function addDraggableFromPad() {
|
|
|
|
if (signaturePad.isEmpty()) return;
|
|
|
|
const dataURL = signaturePad.toDataURL();
|
|
|
|
DraggableUtils.createDraggableCanvasFromUrl(dataURL);
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
<style>
|
|
|
|
.drawing-pad-container {
|
|
|
|
text-align: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
#drawing-pad-canvas {
|
|
|
|
background: rgba(125,125,125,0.2);
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</div>
|
|
|
|
<div class="tab-container" title="Text Input">
|
|
|
|
<label class="form-check-label" for="sigText">Text</label>
|
|
|
|
<input type="text" class="form-control" id="sigText" name="sigText">
|
|
|
|
<label>Font:</label>
|
|
|
|
<select class="form-control" name="font" id="font-select">
|
|
|
|
<option value="Estonia" class="estonia-font">Estonia</option>
|
|
|
|
<option value="Tangerine" class="tangerine-font">Tangerine</option>
|
|
|
|
<option value="Windsong" class="windsong-font">Windsong</option>
|
|
|
|
</select>
|
|
|
|
<div class="margin-auto-parent">
|
|
|
|
<button id="save-text-signature" class="btn btn-outline-success mt-2 margin-center" onclick="addDraggableFromText()">Add</button>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
|
|
function addDraggableFromText() {
|
|
|
|
const sigText = document.getElementById('sigText').value;
|
|
|
|
const font = document.querySelector('select[name=font]').value;
|
|
|
|
const fontSize = 100;
|
|
|
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
ctx.font = `${fontSize}px ${font}`;
|
|
|
|
const textWidth = ctx.measureText(sigText).width;
|
|
|
|
const textHeight = fontSize;
|
|
|
|
|
|
|
|
canvas.width = textWidth;
|
|
|
|
canvas.height = textHeight*1.35; //for tails
|
|
|
|
ctx.font = `${fontSize}px ${font}`;
|
|
|
|
ctx.fillText(sigText, 0, fontSize);
|
|
|
|
|
|
|
|
const dataURL = canvas.toDataURL();
|
|
|
|
DraggableUtils.createDraggableCanvasFromUrl(dataURL);
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
<style>
|
|
|
|
#font-select option {
|
|
|
|
font-size: 30px;
|
|
|
|
}
|
|
|
|
#font-select option[value="Estonia"] {
|
|
|
|
font-family: 'Estonia', sans-serif;
|
|
|
|
}
|
|
|
|
#font-select option[value="Tangerine"] {
|
|
|
|
font-family: 'Tangerine', cursive;
|
|
|
|
}
|
|
|
|
#font-select option[value="Windsong"] {
|
|
|
|
font-family: 'Windsong', cursive;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</div>
|
2023-05-02 23:59:16 +02:00
|
|
|
</div>
|
2023-05-05 00:22:33 +02:00
|
|
|
|
2023-05-07 22:39:34 +02:00
|
|
|
<!-- draggables box -->
|
|
|
|
<div id="box-drag-container" class="show-on-file-selected">
|
2023-05-03 23:07:51 +02:00
|
|
|
<canvas id="pdf-canvas"></canvas>
|
2023-05-07 22:39:34 +02:00
|
|
|
<script src="/js/draggable-utils.js"></script>
|
|
|
|
<div class="draggable-buttons-box ignore-rtl">
|
|
|
|
<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">
|
|
|
|
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z"/>
|
|
|
|
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z"/>
|
|
|
|
</svg>
|
|
|
|
</button>
|
|
|
|
<button class="btn btn-outline-secondary" onclick="document.documentElement.getAttribute('lang-direction')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()" style="margin-left:auto">
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-left" viewBox="0 0 16 16">
|
|
|
|
<path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/>
|
|
|
|
</svg>
|
|
|
|
</button>
|
|
|
|
<button class="btn btn-outline-secondary" onclick="document.documentElement.getAttribute('lang-direction')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()">
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-right" viewBox="0 0 16 16">
|
|
|
|
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/>
|
|
|
|
</svg>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<style>
|
|
|
|
#box-drag-container {
|
|
|
|
position: relative;
|
|
|
|
}
|
|
|
|
#pdf-canvas {
|
|
|
|
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
|
|
|
|
margin: 20px 0;
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
.draggable-buttons-box {
|
|
|
|
position: absolute;
|
|
|
|
top: 20px;
|
|
|
|
padding: 10px;
|
|
|
|
width: 100%;
|
|
|
|
display: flex;
|
|
|
|
gap: 5px;
|
|
|
|
}
|
|
|
|
.draggable-buttons-box > button {
|
|
|
|
z-index: 10;
|
|
|
|
background: rgba(13, 110, 253, 0.1);
|
|
|
|
}
|
|
|
|
.draggable-canvas {
|
|
|
|
border: 1px solid red;
|
|
|
|
position: absolute;
|
|
|
|
touch-action: none;
|
|
|
|
user-select: none;
|
|
|
|
top: 0px;
|
|
|
|
left: 0;
|
|
|
|
}
|
|
|
|
</style>
|
2023-05-03 23:07:51 +02:00
|
|
|
</div>
|
|
|
|
|
2023-05-07 22:39:34 +02:00
|
|
|
<!-- download button -->
|
|
|
|
<div class="margin-auto-parent">
|
|
|
|
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center">Download PDF</button>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
|
|
document.getElementById("download-pdf").addEventListener('click', async() => {
|
|
|
|
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
|
|
|
|
const modifiedPdfBytes = await modifiedPdf.save();
|
|
|
|
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
|
|
|
|
const link = document.createElement('a');
|
|
|
|
link.href = URL.createObjectURL(blob);
|
|
|
|
link.download = 'signed-document.pdf';
|
|
|
|
link.click();
|
|
|
|
});
|
|
|
|
</script>
|
2023-05-02 23:59:16 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
|
|
|
</div>
|
2023-05-03 23:07:51 +02:00
|
|
|
</body>
|
|
|
|
</html>
|