1
0
mirror of https://github.com/Stirling-Tools/Stirling-PDF.git synced 2024-11-16 12:20:12 +01:00

pdfjs worker changes and crop fix

This commit is contained in:
Anthony Stirling 2023-07-22 13:17:24 +01:00
parent e83a027023
commit 749461334d
15 changed files with 1093 additions and 996 deletions

View File

@ -90,7 +90,7 @@ public class CropController {
@PostMapping(value = "/crop", consumes = "multipart/form-data") @PostMapping(value = "/crop", consumes = "multipart/form-data")
@Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO") @Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> cropPdf( public ResponseEntity<byte[]> cropPdf(
@Parameter(description = "The input PDF file", required = true) @RequestParam("file") MultipartFile file, @Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
@Parameter(description = "The x-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("x") float x, @Parameter(description = "The x-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("x") float x,
@Parameter(description = "The y-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("y") float y, @Parameter(description = "The y-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("y") float y,
@Parameter(description = "The width of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("width") float width, @Parameter(description = "The width of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("width") float width,

View File

@ -0,0 +1,66 @@
package stirling.software.SPDF.controller.api.converters;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
public class ConvertHtmlToPDF {
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa")
@Operation(
summary = "Convert a PDF to a PDF/A",
description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO"
)
public ResponseEntity<byte[]> pdfToPdfA(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true)
MultipartFile inputFile) throws IOException, InterruptedException {
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf");
inputFile.transferTo(tempInputFile.toFile());
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Prepare the OCRmyPDF command
List<String> command = new ArrayList<>();
command.add("ocrmypdf");
command.add("--skip-text");
command.add("--tesseract-timeout=0");
command.add("--output-type");
command.add("pdfa");
command.add(tempInputFile.toString());
command.add(tempOutputFile.toString());
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
// Read the optimized PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Clean up the temporary files
Files.delete(tempInputFile);
Files.delete(tempOutputFile);
// Return the optimized PDF as a response
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
}

View File

@ -126,7 +126,7 @@ home.PDFToWord.desc=Convert PDF to Word formats (DOC, DOCX and ODT)
home.PDFToPresentation.title=PDF to Presentation home.PDFToPresentation.title=PDF to Presentation
home.PDFToPresentation.desc=Convert PDF to Presentation formats (PPT, PPTX and ODP) home.PDFToPresentation.desc=Convert PDF to Presentation formats (PPT, PPTX and ODP)
home.PDFToText.title=PDF to Text/RTF home.PDFToText.title=PDF to RTF (Text)
home.PDFToText.desc=Convert PDF to Text or RTF format home.PDFToText.desc=Convert PDF to Text or RTF format
home.PDFToHTML.title=PDF to HTML home.PDFToHTML.title=PDF to HTML
@ -582,8 +582,8 @@ PDFToPresentation.submit=Convert
#PDFToText #PDFToText
PDFToText.title=PDF to Text/RTF PDFToText.title=PDF to RTF (Text)
PDFToText.header=PDF to Text/RTF PDFToText.header=PDF to RTF (Text)
PDFToText.selectText.1=Output file format PDFToText.selectText.1=Output file format
PDFToText.credit=This service uses LibreOffice for file conversion. PDFToText.credit=This service uses LibreOffice for file conversion.
PDFToText.submit=Convert PDFToText.submit=Convert

View File

@ -594,7 +594,7 @@ PDFToPresentation.submit=변환
#PDFToText #PDFToText
PDFToText.title=PDF to Text/RTF PDFToText.title=PDF to RTF (Text)
PDFToText.header=PDF를 텍스트/RTF로 변환 PDFToText.header=PDF를 텍스트/RTF로 변환
PDFToText.selectText.1=출력 파일 형식 PDFToText.selectText.1=출력 파일 형식
PDFToText.credit=이 서비스는 파일 변환을 위해 LibreOffice를 사용합니다. PDFToText.credit=이 서비스는 파일 변환을 위해 LibreOffice를 사용합니다.

View File

@ -135,7 +135,7 @@ home.PDFToWord.desc=将PDF转换为Word格式DOC、DOCX和ODT
home.PDFToPresentation.title=PDF To Presentation home.PDFToPresentation.title=PDF To Presentation
home.PDFToPresentation.desc=将PDF转换成演示文稿格式PPT、PPTX和ODP home.PDFToPresentation.desc=将PDF转换成演示文稿格式PPT、PPTX和ODP
home.PDFToText.title=PDF To Text/RTF home.PDFToText.title=PDF to RTF (Text)
home.PDFToText.desc=将PDF转换为文本或RTF格式 home.PDFToText.desc=将PDF转换为文本或RTF格式
home.PDFToHTML.title=PDF To HTML home.PDFToHTML.title=PDF To HTML
@ -594,7 +594,7 @@ PDFToPresentation.submit=转换
#PDFToText #PDFToText
PDFToText.title=PDF To Text/RTF PDFToText.title=PDF to RTF (Text)
PDFToText.header=将PDF转换成文本/RTF PDFToText.header=将PDF转换成文本/RTF
PDFToText.selectText.1=输出文件格式 PDFToText.selectText.1=输出文件格式
PDFToText.credit=该服务使用LibreOffice进行文件转换。 PDFToText.credit=该服务使用LibreOffice进行文件转换。

View File

@ -69,4 +69,14 @@ html[lang-direction="rtl"] label.form-check-label {
#pdf-canvas { #pdf-canvas {
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384); box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
width: 100%; width: 100%;
}
.fixed-shadow-canvas {
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
width: 100%;
}
.shadow-canvas {
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
}
.hidden {
display: none;
} }

View File

@ -1,206 +1,207 @@
class PdfContainer { class PdfContainer {
fileName; fileName;
pagesContainer; pagesContainer;
pagesContainerWrapper; pagesContainerWrapper;
pdfAdapters; pdfAdapters;
constructor(id, wrapperId, pdfAdapters) { constructor(id, wrapperId, pdfAdapters) {
this.fileName = null; this.fileName = null;
this.pagesContainer = document.getElementById(id) this.pagesContainer = document.getElementById(id)
this.pagesContainerWrapper = document.getElementById(wrapperId); this.pagesContainerWrapper = document.getElementById(wrapperId);
this.movePageTo = this.movePageTo.bind(this); this.movePageTo = this.movePageTo.bind(this);
this.addPdfs = this.addPdfs.bind(this); this.addPdfs = this.addPdfs.bind(this);
this.rotateElement = this.rotateElement.bind(this); this.rotateElement = this.rotateElement.bind(this);
this.rotateAll = this.rotateAll.bind(this); this.rotateAll = this.rotateAll.bind(this);
this.exportPdf = this.exportPdf.bind(this); this.exportPdf = this.exportPdf.bind(this);
this.pdfAdapters = pdfAdapters; this.pdfAdapters = pdfAdapters;
this.pdfAdapters.forEach(adapter => { this.pdfAdapters.forEach(adapter => {
adapter.setActions({ adapter.setActions({
movePageTo: this.movePageTo, movePageTo: this.movePageTo,
addPdfs: this.addPdfs, addPdfs: this.addPdfs,
rotateElement: this.rotateElement, rotateElement: this.rotateElement,
}) })
}) })
window.addPdfs = this.addPdfs; window.addPdfs = this.addPdfs;
window.exportPdf = this.exportPdf; window.exportPdf = this.exportPdf;
window.rotateAll = this.rotateAll; window.rotateAll = this.rotateAll;
} }
movePageTo(startElement, endElement, scrollTo = false) { movePageTo(startElement, endElement, scrollTo = false) {
const childArray = Array.from(this.pagesContainer.childNodes); const childArray = Array.from(this.pagesContainer.childNodes);
const startIndex = childArray.indexOf(startElement); const startIndex = childArray.indexOf(startElement);
const endIndex = childArray.indexOf(endElement); const endIndex = childArray.indexOf(endElement);
this.pagesContainer.removeChild(startElement); this.pagesContainer.removeChild(startElement);
if(!endElement) { if(!endElement) {
this.pagesContainer.append(startElement); this.pagesContainer.append(startElement);
} else { } else {
this.pagesContainer.insertBefore(startElement, endElement); this.pagesContainer.insertBefore(startElement, endElement);
} }
if(scrollTo) { if(scrollTo) {
const { width } = startElement.getBoundingClientRect(); const { width } = startElement.getBoundingClientRect();
const vector = (endIndex !== -1 && startIndex > endIndex) const vector = (endIndex !== -1 && startIndex > endIndex)
? 0-width ? 0-width
: width; : width;
this.pagesContainerWrapper.scroll({ this.pagesContainerWrapper.scroll({
left: this.pagesContainerWrapper.scrollLeft + vector, left: this.pagesContainerWrapper.scrollLeft + vector,
}) })
} }
} }
addPdfs(nextSiblingElement) { addPdfs(nextSiblingElement) {
var input = document.createElement('input'); var input = document.createElement('input');
input.type = 'file'; input.type = 'file';
input.multiple = true; input.multiple = true;
input.setAttribute("accept", "application/pdf"); input.setAttribute("accept", "application/pdf");
input.onchange = async(e) => { input.onchange = async(e) => {
const files = e.target.files; const files = e.target.files;
this.fileName = files[0].name; this.fileName = files[0].name;
for (var i=0; i < files.length; i++) { for (var i=0; i < files.length; i++) {
await this.addPdfFile(files[i], nextSiblingElement); await this.addPdfFile(files[i], nextSiblingElement);
} }
document.querySelectorAll(".enable-on-file").forEach(element => { document.querySelectorAll(".enable-on-file").forEach(element => {
element.disabled = false; element.disabled = false;
}); });
} }
input.click(); input.click();
} }
rotateElement(element, deg) { rotateElement(element, deg) {
var lastTransform = element.style.rotate; var lastTransform = element.style.rotate;
if (!lastTransform) { if (!lastTransform) {
lastTransform = "0"; lastTransform = "0";
} }
const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, '')); const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ''));
const newAngle = lastAngle + deg; const newAngle = lastAngle + deg;
element.style.rotate = newAngle + "deg"; element.style.rotate = newAngle + "deg";
} }
async addPdfFile(file, nextSiblingElement) { async addPdfFile(file, nextSiblingElement) {
const { renderer, pdfDocument } = await this.loadFile(file); const { renderer, pdfDocument } = await this.loadFile(file);
for (var i=0; i < renderer.pageCount; i++) { for (var i=0; i < renderer.pageCount; i++) {
const div = document.createElement('div'); const div = document.createElement('div');
div.classList.add("page-container"); div.classList.add("page-container");
var img = document.createElement('img'); var img = document.createElement('img');
img.classList.add('page-image') img.classList.add('page-image')
const imageSrc = await renderer.renderPage(i) const imageSrc = await renderer.renderPage(i)
img.src = imageSrc; img.src = imageSrc;
img.pageIdx = i; img.pageIdx = i;
img.rend = renderer; img.rend = renderer;
img.doc = pdfDocument; img.doc = pdfDocument;
div.appendChild(img); div.appendChild(img);
this.pdfAdapters.forEach((adapter) => { this.pdfAdapters.forEach((adapter) => {
adapter.adapt?.(div) adapter.adapt?.(div)
}) })
if (nextSiblingElement) { if (nextSiblingElement) {
this.pagesContainer.insertBefore(div, nextSiblingElement); this.pagesContainer.insertBefore(div, nextSiblingElement);
} else { } else {
this.pagesContainer.appendChild(div); this.pagesContainer.appendChild(div);
} }
} }
} }
async loadFile(file) { async loadFile(file) {
var objectUrl = URL.createObjectURL(file); var objectUrl = URL.createObjectURL(file);
var pdfDocument = await this.toPdfLib(objectUrl); var pdfDocument = await this.toPdfLib(objectUrl);
var renderer = await this.toRenderer(objectUrl); var renderer = await this.toRenderer(objectUrl);
return { renderer, pdfDocument }; return { renderer, pdfDocument };
} }
async toRenderer(objectUrl) { async toRenderer(objectUrl) {
const pdf = await pdfjsLib.getDocument(objectUrl).promise; pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
return { const pdf = await pdfjsLib.getDocument(objectUrl).promise;
document: pdf, return {
pageCount: pdf.numPages, document: pdf,
renderPage: async function(pageIdx) { pageCount: pdf.numPages,
const page = await this.document.getPage(pageIdx+1); renderPage: async function(pageIdx) {
const page = await this.document.getPage(pageIdx+1);
const canvas = document.createElement("canvas");
const canvas = document.createElement("canvas");
// set the canvas size to the size of the page
if (page.rotate == 90 || page.rotate == 270) { // set the canvas size to the size of the page
canvas.width = page.view[3]; if (page.rotate == 90 || page.rotate == 270) {
canvas.height = page.view[2]; canvas.width = page.view[3];
} else { canvas.height = page.view[2];
canvas.width = page.view[2]; } else {
canvas.height = page.view[3]; canvas.width = page.view[2];
} canvas.height = page.view[3];
}
// render the page onto the canvas
var renderContext = { // render the page onto the canvas
canvasContext: canvas.getContext("2d"), var renderContext = {
viewport: page.getViewport({ scale: 1 }) canvasContext: canvas.getContext("2d"),
}; viewport: page.getViewport({ scale: 1 })
};
await page.render(renderContext).promise;
return canvas.toDataURL(); await page.render(renderContext).promise;
} return canvas.toDataURL();
}; }
} };
}
async toPdfLib(objectUrl) {
const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer()); async toPdfLib(objectUrl) {
const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, { ignoreEncryption: true }); const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer());
return pdfDoc; const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, { ignoreEncryption: true });
} return pdfDoc;
}
rotateAll(deg) {
for (var i=0; i<this.pagesContainer.childNodes.length; i++) { rotateAll(deg) {
const img = this.pagesContainer.childNodes[i].querySelector("img"); for (var i=0; i<this.pagesContainer.childNodes.length; i++) {
if (!img) continue; const img = this.pagesContainer.childNodes[i].querySelector("img");
this.rotateElement(img, deg) if (!img) continue;
} this.rotateElement(img, deg)
} }
}
async exportPdf() {
const pdfDoc = await PDFLib.PDFDocument.create(); async exportPdf() {
for (var i=0; i<this.pagesContainer.childNodes.length; i++) { const pdfDoc = await PDFLib.PDFDocument.create();
const img = this.pagesContainer.childNodes[i].querySelector("img"); for (var i=0; i<this.pagesContainer.childNodes.length; i++) {
if (!img) continue; const img = this.pagesContainer.childNodes[i].querySelector("img");
const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx]) if (!img) continue;
const page = pages[0]; const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx])
const page = pages[0];
const rotation = img.style.rotate;
if (rotation) { const rotation = img.style.rotate;
const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, '')); if (rotation) {
page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle)) const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, ''));
} page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle))
}
pdfDoc.addPage(page);
} pdfDoc.addPage(page);
const pdfBytes = await pdfDoc.save(); }
const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' }); const pdfBytes = await pdfDoc.save();
const url = URL.createObjectURL(pdfBlob); const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
const downloadOption = localStorage.getItem('downloadOption'); const url = URL.createObjectURL(pdfBlob);
const downloadOption = localStorage.getItem('downloadOption');
if (downloadOption === 'sameWindow') {
// Open the file in the same window if (downloadOption === 'sameWindow') {
window.location.href = url; // Open the file in the same window
} else if (downloadOption === 'newWindow') { window.location.href = url;
// Open the file in a new window } else if (downloadOption === 'newWindow') {
window.open(url, '_blank'); // Open the file in a new window
} else { window.open(url, '_blank');
// Download the file } else {
const downloadLink = document.createElement('a'); // Download the file
downloadLink.href = url; const downloadLink = document.createElement('a');
downloadLink.download = this.fileName ? this.fileName : 'managed.pdf'; downloadLink.href = url;
downloadLink.click(); downloadLink.download = this.fileName ? this.fileName : 'managed.pdf';
} downloadLink.click();
} }
} }
}
export default PdfContainer;
export default PdfContainer;

View File

@ -1,35 +1,34 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org"> <html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{PDFToText.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{PDFToText.title})}"></th:block>
<body> <body>
<th:block th:insert="~{fragments/common :: game}"></th:block> <th:block th:insert="~{fragments/common :: 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>
<br> <br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{PDFToText.header}"></h2> <h2 th:text="#{PDFToText.header}"></h2>
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-text}"> <form method="post" enctype="multipart/form-data" th:action="@{pdf-to-text}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="form-group"> <div class="form-group">
<label th:text="#{PDFToText.selectText.1}"></label> <label th:text="#{PDFToText.selectText.1}"></label>
<select class="form-control" name="outputFormat"> <select class="form-control" name="outputFormat">
<option value="rtf">RTF</option> <option value="rtf">RTF</option>
<option value="txt:Text">TXT</option> </select>
</select> </div>
</div> <br>
<br> <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToText.submit}"></button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToText.submit}"></button>
</form>
</form> <p class="mt-3" th:text="#{PDFToText.credit}"></p>
<p class="mt-3" th:text="#{PDFToText.credit}"></p> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div th:insert="~{fragments/footer.html :: footer}"></div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>

View File

@ -22,13 +22,13 @@
<button type="submit" class="btn btn-primary" th:text="#{crop.submit}"></button> <button type="submit" class="btn btn-primary" th:text="#{crop.submit}"></button>
</form> </form>
<div style="position: relative; display: inline-block;"> <div style="position: relative; display: inline-block;">
<canvas id="pdf-canvas" style="position: absolute; top: 0; left: 0; z-index: 1;"></canvas> <canvas id="crop-pdf-canvas" style="position: absolute; top: 0; left: 0; z-index: 1;"></canvas>
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2;"></canvas> <canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2;"></canvas>
</div> </div>
<script> <script>
let pdfCanvas = document.getElementById('pdf-canvas'); let pdfCanvas = document.getElementById('crop-pdf-canvas');
let overlayCanvas = document.getElementById('overlayCanvas'); let overlayCanvas = document.getElementById('overlayCanvas');
let context = pdfCanvas.getContext('2d'); let context = pdfCanvas.getContext('2d');
@ -61,6 +61,7 @@
let reader = new FileReader(); let reader = new FileReader();
reader.onload = function(ev) { reader.onload = function(ev) {
let typedArray = new Uint8Array(reader.result); let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) { pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
pdfDoc = pdf; pdfDoc = pdf;
totalPages = pdf.numPages; totalPages = pdf.numPages;
@ -126,6 +127,7 @@
let renderContext = { canvasContext: context, viewport: viewport }; let renderContext = { canvasContext: context, viewport: viewport };
page.render(renderContext); page.render(renderContext);
pdfCanvas.classList.add("shadow-canvas");
}); });
} }

View File

@ -1,140 +1,141 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org"> <html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<head> <head>
<th:block th:insert="~{fragments/common :: head(title=#{addImage.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{addImage.title})}"></th:block>
<script src="js/thirdParty/interact.min.js"></script> <script src="js/thirdParty/interact.min.js"></script>
</head> </head>
<body> <body>
<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>
<br> <br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{addImage.header}"></h2> <h2 th:text="#{addImage.header}"></h2>
<!-- pdf selector --> <!-- pdf selector -->
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multiple=false, accept='application/pdf')}"></div> <div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multiple=false, accept='application/pdf')}"></div>
<script> <script>
let originalFileName = ''; let originalFileName = '';
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => { document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
const file = event.target.files[0]; const file = event.target.files[0];
if (file) { if (file) {
originalFileName = file.name.replace(/\.[^/.]+$/, ""); originalFileName = file.name.replace(/\.[^/.]+$/, "");
const pdfData = await file.arrayBuffer(); const pdfData = await file.arrayBuffer();
const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise; pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
await DraggableUtils.renderPage(pdfDoc, 0); 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.querySelectorAll(".show-on-file-selected").forEach(el => {
}) el.style.cssText = '';
} })
}); }
document.addEventListener("DOMContentLoaded", () => { });
document.querySelectorAll(".show-on-file-selected").forEach(el => { document.addEventListener("DOMContentLoaded", () => {
el.style.cssText = "display:none !important"; document.querySelectorAll(".show-on-file-selected").forEach(el => {
}) el.style.cssText = "display:none !important";
}); })
</script> });
</script>
<div class="tab-group show-on-file-selected">
<div class="tab-container" th:title="#{addImage.upload}"> <div class="tab-group show-on-file-selected">
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', multiple=true, accept='image/*', inputText=#{imgPrompt})}"></div> <div class="tab-container" th:title="#{addImage.upload}">
<script> <div th:replace="~{fragments/common :: fileSelector(name='image-upload', multiple=true, accept='image/*', inputText=#{imgPrompt})}"></div>
const imageUpload = document.querySelector('input[name=image-upload]'); <script>
imageUpload.addEventListener('change', e => { const imageUpload = document.querySelector('input[name=image-upload]');
if(!e.target.files) { imageUpload.addEventListener('change', e => {
return; if(!e.target.files) {
} return;
for (const imageFile of e.target.files) { }
var reader = new FileReader(); for (const imageFile of e.target.files) {
reader.readAsDataURL(imageFile); var reader = new FileReader();
reader.onloadend = function (e) { reader.readAsDataURL(imageFile);
DraggableUtils.createDraggableCanvasFromUrl(e.target.result); reader.onloadend = function (e) {
}; DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
} };
}); }
</script> });
</div> </script>
</div> </div>
</div>
<!-- draggables box -->
<div id="box-drag-container" class="show-on-file-selected"> <!-- draggables box -->
<canvas id="pdf-canvas"></canvas> <div id="box-drag-container" class="show-on-file-selected">
<script src="js/draggable-utils.js"></script> <canvas id="pdf-canvas"></canvas>
<div class="draggable-buttons-box ignore-rtl"> <script src="js/draggable-utils.js"></script>
<button class="btn btn-outline-secondary" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())"> <div class="draggable-buttons-box ignore-rtl">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"> <button class="btn btn-outline-secondary" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())">
<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"/> <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="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"/> <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"/>
</svg> <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"/>
</button> </svg>
<button class="btn btn-outline-secondary" onclick="document.documentElement.getAttribute('lang-direction')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()" style="margin-left:auto"> </button>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-left" viewBox="0 0 16 16"> <button class="btn btn-outline-secondary" onclick="document.documentElement.getAttribute('lang-direction')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()" style="margin-left:auto">
<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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-left" viewBox="0 0 16 16">
</svg> <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"/>
</button> </svg>
<button class="btn btn-outline-secondary" onclick="document.documentElement.getAttribute('lang-direction')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()"> </button>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-right" viewBox="0 0 16 16"> <button class="btn btn-outline-secondary" onclick="document.documentElement.getAttribute('lang-direction')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()">
<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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-right" viewBox="0 0 16 16">
</svg> <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"/>
</button> </svg>
</div> </button>
<style> </div>
#box-drag-container { <style>
position: relative; #box-drag-container {
margin: 20px 0; position: relative;
} margin: 20px 0;
#pdf-canvas { }
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384); #pdf-canvas {
width: 100%; box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
} width: 100%;
.draggable-buttons-box { }
position: absolute; .draggable-buttons-box {
top: 0; position: absolute;
padding: 10px; top: 0;
width: 100%; padding: 10px;
display: flex; width: 100%;
gap: 5px; display: flex;
} gap: 5px;
.draggable-buttons-box > button { }
z-index: 10; .draggable-buttons-box > button {
background-color: rgba(13, 110, 253, 0.1); z-index: 10;
} background-color: rgba(13, 110, 253, 0.1);
.draggable-canvas { }
border: 1px solid red; .draggable-canvas {
position: absolute; border: 1px solid red;
touch-action: none; position: absolute;
user-select: none; touch-action: none;
top: 0px; user-select: none;
left: 0; top: 0px;
} left: 0;
</style> }
</div> </style>
</div>
<!-- download button -->
<div class="margin-auto-parent"> <!-- download button -->
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center">Download PDF</button> <div class="margin-auto-parent">
</div> <button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center">Download PDF</button>
<script> </div>
document.getElementById("download-pdf").addEventListener('click', async() => { <script>
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument(); document.getElementById("download-pdf").addEventListener('click', async() => {
const modifiedPdfBytes = await modifiedPdf.save(); const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' }); const modifiedPdfBytes = await modifiedPdf.save();
const link = document.createElement('a'); const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
link.href = URL.createObjectURL(blob); const link = document.createElement('a');
link.download = originalFileName + '_addedImage.pdf'; link.href = URL.createObjectURL(blob);
link.click(); link.download = originalFileName + '_addedImage.pdf';
}); link.click();
</script> });
</div> </script>
</div> </div>
</div> </div>
</div> </div>
<div th:insert="~{fragments/footer.html :: footer}"></div> </div>
</div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</body> </div>
</body>
</html> </html>

View File

@ -14,37 +14,50 @@
<br> <br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-12">
<h2 th:text="#{adjustContrast.header}"></h2> <div class="row justify-content-center">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf', remoteCall='false')}"></div> <div class="col-md-3">
<h4> <div id="sliders-container" style="display:none;">
<span th:text="#{adjustContrast.contrast}"></span> <span id="contrast-val">100</span>% <h4>
</h4> <span th:text="#{adjustContrast.contrast}"></span> <span id="contrast-val">100</span>%
<input type="range" min="0" max="200" value="100" </h4>
id="contrast-slider" /> <input type="range" min="0" max="200" value="100" id="contrast-slider" />
<h4> <h4>
<span th:text="#{adjustContrast.brightness}"></span> <span id="brightness-val">100</span>% <span th:text="#{adjustContrast.brightness}"></span> <span id="brightness-val">100</span>%
</h4> </h4>
<input type="range" min="0" max="200" value="100" <input type="range" min="0" max="200" value="100" id="brightness-slider" />
id="brightness-slider" />
<h4> <h4>
<span th:text="#{adjustContrast.saturation}"></span> <span id="saturation-val">100</span>% <span th:text="#{adjustContrast.saturation}"></span> <span id="saturation-val">100</span>%
</h4> </h4>
<input type="range" min="0" max="200" value="100" <input type="range" min="0" max="200" value="100" id="saturation-slider" />
id="saturation-slider" /> </div>
</div>
</br> <div class="col-md-7">
<canvas id="pdf-canvas"></canvas> <h2 th:text="#{adjustContrast.header}"></h2>
<div class="col-md-8">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf', remoteCall='false')}"></div>
</div>
<br>
<canvas id="contrast-pdf-canvas"></canvas>
<button id="download-button" class="btn btn-primary" th:text="#{adjustContrast.download}"></button>
</div>
</div>
<style>
#flex-container {
display: flex;
align-items: center;
}
#sliders-container {
padding: 0 20px; /* Add some padding to separate sliders from canvas */
}
</style>
<button id="download-button" class="btn btn-primary" th:text="#{adjustContrast.download}"></button>
<script src="pdfjs/pdf.js"></script> <script src="pdfjs/pdf.js"></script>
<script> <script>
var canvas = document.getElementById('pdf-canvas'); var canvas = document.getElementById('contrast-pdf-canvas');
var context = canvas.getContext('2d'); var context = canvas.getContext('2d');
var originalImageData = null; var originalImageData = null;
var allPages = []; var allPages = [];
@ -55,6 +68,7 @@
var fileReader = new FileReader(); var fileReader = new FileReader();
fileReader.onload = async function() { fileReader.onload = async function() {
var data = new Uint8Array(this.result); var data = new Uint8Array(this.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
pdf = await pdfjsLib.getDocument({data: data}).promise; pdf = await pdfjsLib.getDocument({data: data}).promise;
// Get the number of pages in the PDF // Get the number of pages in the PDF
@ -65,6 +79,8 @@
pdfDoc = await PDFLib.PDFDocument.create(); pdfDoc = await PDFLib.PDFDocument.create();
// Render the first page in the viewer // Render the first page in the viewer
await renderPageAndAdjustImageProperties(1); await renderPageAndAdjustImageProperties(1);
document.getElementById("sliders-container").style.display = "block";
}; };
fileReader.readAsArrayBuffer(file); fileReader.readAsArrayBuffer(file);
} }
@ -90,6 +106,7 @@
adjustImageProperties(); adjustImageProperties();
resolve(); resolve();
}); });
canvas.classList.add("fixed-shadow-canvas");
}); });
} }

View File

@ -1,263 +1,263 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org"> <html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{changeMetadata.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{changeMetadata.title})}"></th:block>
<body> <body>
<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>
<br> <br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{changeMetadata.header}"></h2> <h2 th:text="#{changeMetadata.header}"></h2>
<form method="post" id="form1" enctype="multipart/form-data" th:action="@{/update-metadata}"> <form method="post" id="form1" enctype="multipart/form-data" th:action="@{/update-metadata}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p> <p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
<div class="form-group-inline form-check"> <div class="form-group-inline form-check">
<input type="checkbox" class="form-check-input" id="deleteAll" name="deleteAll"> <input type="checkbox" class="form-check-input" id="deleteAll" name="deleteAll">
<label class="ml-3" for="deleteAll" th:text="#{changeMetadata.selectText.2}" ></label> <label class="ml-3" for="deleteAll" th:text="#{changeMetadata.selectText.2}" ></label>
</div> </div>
<div class="form-group-inline form-check"> <div class="form-group-inline form-check">
<input type="checkbox" class="form-check-input" id="customModeCheckbox"> <input type="checkbox" class="form-check-input" id="customModeCheckbox">
<label class="ml-3" for="customModeCheckbox" th:text="#{changeMetadata.selectText.3}"></label> <label class="ml-3" for="customModeCheckbox" th:text="#{changeMetadata.selectText.3}"></label>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-check-label" for="author" th:text="#{changeMetadata.author}"></label> <label class="form-check-label" for="author" th:text="#{changeMetadata.author}"></label>
<input type="text" class="form-control" id="author" name="author"> <input type="text" class="form-control" id="author" name="author">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-check-label" for="creationDate" th:text="#{changeMetadata.creationDate}"></label> <label class="form-check-label" for="creationDate" th:text="#{changeMetadata.creationDate}"></label>
<input type="text" class="form-control" id="creationDate" name="creationDate" placeholder="2020/12/25 18:30:59"> <input type="text" class="form-control" id="creationDate" name="creationDate" placeholder="2020/12/25 18:30:59">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-check-label" for="creator" th:text="#{changeMetadata.creator}"></label> <label class="form-check-label" for="creator" th:text="#{changeMetadata.creator}"></label>
<input type="text" class="form-control" id="creator" name="creator"> <input type="text" class="form-control" id="creator" name="creator">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-check-label" for="keywords" th:text="#{changeMetadata.keywords}"></label> <label class="form-check-label" for="keywords" th:text="#{changeMetadata.keywords}"></label>
<input type="text" class="form-control" id="keywords" name="keywords"> <input type="text" class="form-control" id="keywords" name="keywords">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-check-label" for="modificationDate" th:text="#{changeMetadata.modDate}"></label> <label class="form-check-label" for="modificationDate" th:text="#{changeMetadata.modDate}"></label>
<input type="text" class="form-control" id="modificationDate" name="modificationDate" placeholder="2020/12/25 18:30:59"> <input type="text" class="form-control" id="modificationDate" name="modificationDate" placeholder="2020/12/25 18:30:59">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-check-label" for="producer" th:text="#{changeMetadata.producer}"></label> <label class="form-check-label" for="producer" th:text="#{changeMetadata.producer}"></label>
<input type="text" class="form-control" id="producer" name="producer"> <input type="text" class="form-control" id="producer" name="producer">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-check-label" for="subject" th:text="#{changeMetadata.subject}"></label> <label class="form-check-label" for="subject" th:text="#{changeMetadata.subject}"></label>
<input type="text" class="form-control" id="subject" name="subject"> <input type="text" class="form-control" id="subject" name="subject">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-check-label" for="title" th:text="#{changeMetadata.title}"></label> <label class="form-check-label" for="title" th:text="#{changeMetadata.title}"></label>
<input type="text" class="form-control" id="title" name="title"> <input type="text" class="form-control" id="title" name="title">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-check-label" for="trapped" th:text="#{changeMetadata.trapped}"></label> <label class="form-check-label" for="trapped" th:text="#{changeMetadata.trapped}"></label>
<select class="form-control" id="trapped" name="trapped"> <select class="form-control" id="trapped" name="trapped">
<option value="True" th:text="#{true}"></option> <option value="True" th:text="#{true}"></option>
<option value="False" th:text="#{false}" selected></option> <option value="False" th:text="#{false}" selected></option>
<option value="Unknown" th:text="#{unknown}"></option> <option value="Unknown" th:text="#{unknown}"></option>
</select> </select>
</div> </div>
<div id="customMetadata" style="display: none;"> <div id="customMetadata" style="display: none;">
<h3 th:text="#{changeMetadata.selectText.4}"></h3> <h3 th:text="#{changeMetadata.selectText.4}"></h3>
<div class="form-group" id="otherMetadataEntries"></div> <div class="form-group" id="otherMetadataEntries"></div>
</div> </div>
<div id="customMetadataEntries"></div> <div id="customMetadataEntries"></div>
<button type="button" class="btn btn-secondary" id="addMetadataBtn" th:text="#{changeMetadata.selectText.5}"></button> <button type="button" class="btn btn-secondary" id="addMetadataBtn" th:text="#{changeMetadata.selectText.5}"></button>
<br> <br>
<br> <br>
<button class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button> <button class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button>
<script> <script>
const deleteAllCheckbox = document.querySelector("#deleteAll"); const deleteAllCheckbox = document.querySelector("#deleteAll");
const inputs = document.querySelectorAll(".form-control"); const inputs = document.querySelectorAll(".form-control");
const customMetadataDiv = document.getElementById('customMetadata'); const customMetadataDiv = document.getElementById('customMetadata');
const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries'); const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries');
deleteAllCheckbox.addEventListener("change", function(event) { deleteAllCheckbox.addEventListener("change", function(event) {
if (event.target !== deleteAllCheckbox) { if (event.target !== deleteAllCheckbox) {
return; return;
} }
inputs.forEach(input => { inputs.forEach(input => {
if (input === deleteAllCheckbox) { if (input === deleteAllCheckbox) {
return; return;
} }
input.disabled = deleteAllCheckbox.checked; input.disabled = deleteAllCheckbox.checked;
}); });
}); });
const customModeCheckbox = document.getElementById('customModeCheckbox'); const customModeCheckbox = document.getElementById('customModeCheckbox');
const addMetadataBtn = document.getElementById("addMetadataBtn"); const addMetadataBtn = document.getElementById("addMetadataBtn");
const customMetadataFormContainer = document.getElementById("customMetadataEntries"); const customMetadataFormContainer = document.getElementById("customMetadataEntries");
var count = 1; var count = 1;
const fileInput = document.querySelector("#fileInput-input"); const fileInput = document.querySelector("#fileInput-input");
const authorInput = document.querySelector("#author"); const authorInput = document.querySelector("#author");
const creationDateInput = document.querySelector("#creationDate"); const creationDateInput = document.querySelector("#creationDate");
const creatorInput = document.querySelector("#creator"); const creatorInput = document.querySelector("#creator");
const keywordsInput = document.querySelector("#keywords"); const keywordsInput = document.querySelector("#keywords");
const modificationDateInput = document.querySelector("#modificationDate"); const modificationDateInput = document.querySelector("#modificationDate");
const producerInput = document.querySelector("#producer"); const producerInput = document.querySelector("#producer");
const subjectInput = document.querySelector("#subject"); const subjectInput = document.querySelector("#subject");
const titleInput = document.querySelector("#title"); const titleInput = document.querySelector("#title");
const trappedInput = document.querySelector("#trapped"); const trappedInput = document.querySelector("#trapped");
var lastPDFFileMeta = null; var lastPDFFileMeta = null;
fileInput.addEventListener("change", async function() { fileInput.addEventListener("change", async function() {
while (otherMetadataEntriesDiv.firstChild) { while (otherMetadataEntriesDiv.firstChild) {
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild); otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
} }
while (customMetadataFormContainer.firstChild) { while (customMetadataFormContainer.firstChild) {
customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild); customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
} }
const file = this.files[0]; const file = this.files[0];
var url = URL.createObjectURL(file) var url = URL.createObjectURL(file)
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
const pdf = await pdfjsLib.getDocument(url).promise; const pdf = await pdfjsLib.getDocument(url).promise;
const pdfMetadata = await pdf.getMetadata(); const pdfMetadata = await pdf.getMetadata();
lastPDFFile = pdfMetadata?.info lastPDFFile = pdfMetadata?.info
console.log(pdfMetadata); console.log(pdfMetadata);
if(!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) { if(!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) {
customModeCheckbox.disabled = true; customModeCheckbox.disabled = true;
customModeCheckbox.checked = false; customModeCheckbox.checked = false;
} else { } else {
customModeCheckbox.disabled = false; customModeCheckbox.disabled = false;
} }
authorInput.value = pdfMetadata?.info?.Author; authorInput.value = pdfMetadata?.info?.Author;
creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate); creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate);
creatorInput.value = pdfMetadata?.info?.Creator; creatorInput.value = pdfMetadata?.info?.Creator;
keywordsInput.value = pdfMetadata?.info?.Keywords; keywordsInput.value = pdfMetadata?.info?.Keywords;
modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate); modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate);
producerInput.value = pdfMetadata?.info?.Producer; producerInput.value = pdfMetadata?.info?.Producer;
subjectInput.value = pdfMetadata?.info?.Subject; subjectInput.value = pdfMetadata?.info?.Subject;
titleInput.value = pdfMetadata?.info?.Title; titleInput.value = pdfMetadata?.info?.Title;
console.log(pdfMetadata?.info); console.log(pdfMetadata?.info);
const trappedValue = pdfMetadata?.info?.Trapped; const trappedValue = pdfMetadata?.info?.Trapped;
// Get all options in the select element // Get all options in the select element
const options = trappedInput.options; const options = trappedInput.options;
// Loop through all options to find the one with a matching value // Loop through all options to find the one with a matching value
for (let i = 0; i < options.length; i++) { for (let i = 0; i < options.length; i++) {
if (options[i].value === trappedValue) { if (options[i].value === trappedValue) {
options[i].selected = true; options[i].selected = true;
break; break;
} }
} }
addExtra(); addExtra();
}); });
addMetadataBtn.addEventListener("click", () => { addMetadataBtn.addEventListener("click", () => {
const keyInput = document.createElement("input"); const keyInput = document.createElement("input");
keyInput.type = "text"; keyInput.type = "text";
keyInput.placeholder = 'Key'; keyInput.placeholder = 'Key';
keyInput.className = "form-control"; keyInput.className = "form-control";
keyInput.name = "customKey" + count; keyInput.name = "customKey" + count;
const valueInput = document.createElement("input"); const valueInput = document.createElement("input");
valueInput.type = "text"; valueInput.type = "text";
valueInput.placeholder = 'Value'; valueInput.placeholder = 'Value';
valueInput.className = "form-control"; valueInput.className = "form-control";
valueInput.name = "customValue" + count; valueInput.name = "customValue" + count;
count = count + 1; count = count + 1;
const formGroup = document.createElement("div"); const formGroup = document.createElement("div");
formGroup.className = "form-group"; formGroup.className = "form-group";
formGroup.appendChild(keyInput); formGroup.appendChild(keyInput);
formGroup.appendChild(valueInput); formGroup.appendChild(valueInput);
customMetadataFormContainer.appendChild(formGroup); customMetadataFormContainer.appendChild(formGroup);
}); });
function convertDateFormat(dateTimeString) { function convertDateFormat(dateTimeString) {
if (!dateTimeString || dateTimeString.length < 17) { if (!dateTimeString || dateTimeString.length < 17) {
return dateTimeString; return dateTimeString;
} }
const year = dateTimeString.substring(2, 6); const year = dateTimeString.substring(2, 6);
const month = dateTimeString.substring(6, 8); const month = dateTimeString.substring(6, 8);
const day = dateTimeString.substring(8, 10); const day = dateTimeString.substring(8, 10);
const hour = dateTimeString.substring(10, 12); const hour = dateTimeString.substring(10, 12);
const minute = dateTimeString.substring(12, 14); const minute = dateTimeString.substring(12, 14);
const second = dateTimeString.substring(14, 16); const second = dateTimeString.substring(14, 16);
return year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second; return year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second;
} }
function addExtra() { function addExtra() {
const event = document.getElementById("customModeCheckbox"); const event = document.getElementById("customModeCheckbox");
if (event.checked && lastPDFFile.Custom != null) { if (event.checked && lastPDFFile.Custom != null) {
customMetadataDiv.style.display = 'block'; customMetadataDiv.style.display = 'block';
for (const [key, value] of Object.entries(lastPDFFile.Custom)) { for (const [key, value] of Object.entries(lastPDFFile.Custom)) {
if (key === 'Author' || key === 'CreationDate' || key === 'Creator' || key === 'Keywords' || key === 'ModDate' || key === 'Producer' || key === 'Subject' || key === 'Title' || key === 'Trapped') { if (key === 'Author' || key === 'CreationDate' || key === 'Creator' || key === 'Keywords' || key === 'ModDate' || key === 'Producer' || key === 'Subject' || key === 'Title' || key === 'Trapped') {
continue; continue;
} }
const entryDiv = document.createElement('div'); const entryDiv = document.createElement('div');
entryDiv.className = 'form-group'; entryDiv.className = 'form-group';
entryDiv.innerHTML = `<div class="form-group"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`; entryDiv.innerHTML = `<div class="form-group"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
otherMetadataEntriesDiv.appendChild(entryDiv); otherMetadataEntriesDiv.appendChild(entryDiv);
} }
} else { } else {
customMetadataDiv.style.display = 'none'; customMetadataDiv.style.display = 'none';
while (otherMetadataEntriesDiv.firstChild) { while (otherMetadataEntriesDiv.firstChild) {
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild); otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
} }
} }
} }
customModeCheckbox.addEventListener('change', (event) => { customModeCheckbox.addEventListener('change', (event) => {
addExtra(); addExtra();
}); });
</script> </script>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div th:insert="~{fragments/footer.html :: footer}"></div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,190 +1,190 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org"> <html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{compare.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{compare.title})}"></th:block>
<body> <body>
<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>
<br> <br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-9"> <div class="col-md-9">
<h2 th:text="#{compare.header}"></h2> <h2 th:text="#{compare.header}"></h2>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div> <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> <div th:replace="~{fragments/common :: fileSelector(name='fileInput2', multiple=false, accept='application/pdf')}"></div>
<button class="btn btn-primary" onclick="comparePDFs()" th:text="#{compare.submit}"></button> <button class="btn btn-primary" onclick="comparePDFs()" th:text="#{compare.submit}"></button>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<h3 th:text="#{compare.document.1}"></h3> <h3 th:text="#{compare.document.1}"></h3>
<div id="result1" class="result-column"></div> <div id="result1" class="result-column"></div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<h3 th:text="#{compare.document.2}"></h3> <h3 th:text="#{compare.document.2}"></h3>
<div id="result2" class="result-column"></div> <div id="result2" class="result-column"></div>
</div> </div>
</div> </div>
<style> <style>
.result-column { .result-column {
border: 1px solid #ccc; border: 1px solid #ccc;
padding: 15px; padding: 15px;
overflow-y: auto; overflow-y: auto;
height: calc(100vh - 400px); height: calc(100vh - 400px);
white-space: pre-wrap; white-space: pre-wrap;
} }
</style> </style>
<script> <script>
// get the elements // get the elements
var result1 = document.getElementById('result1'); var result1 = document.getElementById('result1');
var result2 = document.getElementById('result2'); var result2 = document.getElementById('result2');
// add event listeners // add event listeners
result1.addEventListener('scroll', function() { result1.addEventListener('scroll', function() {
result2.scrollTop = result1.scrollTop; result2.scrollTop = result1.scrollTop;
}); });
result2.addEventListener('scroll', function() { result2.addEventListener('scroll', function() {
result1.scrollTop = result2.scrollTop; result1.scrollTop = result2.scrollTop;
}); });
async function comparePDFs() { async function comparePDFs() {
const file1 = document.getElementById("fileInput-input").files[0]; const file1 = document.getElementById("fileInput-input").files[0];
const file2 = document.getElementById("fileInput2-input").files[0]; const file2 = document.getElementById("fileInput2-input").files[0];
if (!file1 || !file2) { if (!file1 || !file2) {
console.error("Please select two PDF files to compare"); console.error("Please select two PDF files to compare");
return; return;
} }
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
const [pdf1, pdf2] = await Promise.all([ const [pdf1, pdf2] = await Promise.all([
pdfjsLib.getDocument(URL.createObjectURL(file1)).promise, pdfjsLib.getDocument(URL.createObjectURL(file1)).promise,
pdfjsLib.getDocument(URL.createObjectURL(file2)).promise pdfjsLib.getDocument(URL.createObjectURL(file2)).promise
]); ]);
const extractText = async (pdf) => { const extractText = async (pdf) => {
const pages = []; const pages = [];
for (let i = 1; i <= pdf.numPages; i++) { for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i); const page = await pdf.getPage(i);
const content = await page.getTextContent(); const content = await page.getTextContent();
const strings = content.items.map(item => item.str); const strings = content.items.map(item => item.str);
pages.push(strings.join(" ")); pages.push(strings.join(" "));
} }
return pages.join(" "); return pages.join(" ");
}; };
const [text1, text2] = await Promise.all([ const [text1, text2] = await Promise.all([
extractText(pdf1), extractText(pdf1),
extractText(pdf2) extractText(pdf2)
]); ]);
if (text1.trim() === "" || text2.trim() === "") { if (text1.trim() === "" || text2.trim() === "") {
alert("One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison."); alert("One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.");
return; return;
} }
const diff = (text1, text2) => { const diff = (text1, text2) => {
const words1 = text1.split(' '); const words1 = text1.split(' ');
const words2 = text2.split(' '); const words2 = text2.split(' ');
// Create a 2D array to hold our "matrix" // Create a 2D array to hold our "matrix"
const matrix = Array(words1.length + 1).fill(null).map(() => Array(words2.length + 1).fill(0)); const matrix = Array(words1.length + 1).fill(null).map(() => Array(words2.length + 1).fill(0));
// Perform standard LCS algorithm // Perform standard LCS algorithm
for (let i = 1; i <= words1.length; i++) { for (let i = 1; i <= words1.length; i++) {
for (let j = 1; j <= words2.length; j++) { for (let j = 1; j <= words2.length; j++) {
if (words1[i - 1] === words2[j - 1]) { if (words1[i - 1] === words2[j - 1]) {
matrix[i][j] = matrix[i - 1][j - 1] + 1; matrix[i][j] = matrix[i - 1][j - 1] + 1;
} else { } else {
matrix[i][j] = Math.max(matrix[i][j - 1], matrix[i - 1][j]); matrix[i][j] = Math.max(matrix[i][j - 1], matrix[i - 1][j]);
} }
} }
} }
let i = words1.length; let i = words1.length;
let j = words2.length; let j = words2.length;
const differences = []; const differences = [];
// Backtrack through the matrix to create the diff // Backtrack through the matrix to create the diff
while (i > 0 || j > 0) { while (i > 0 || j > 0) {
if (i > 0 && j > 0 && words1[i - 1] === words2[j - 1]) { if (i > 0 && j > 0 && words1[i - 1] === words2[j - 1]) {
differences.unshift(['black', words1[i - 1]]); differences.unshift(['black', words1[i - 1]]);
i--; i--;
j--; j--;
} else if (j > 0 && (i === 0 || matrix[i][j - 1] >= matrix[i - 1][j])) { } else if (j > 0 && (i === 0 || matrix[i][j - 1] >= matrix[i - 1][j])) {
differences.unshift(['green', words2[j - 1]]); differences.unshift(['green', words2[j - 1]]);
j--; j--;
} else if (i > 0 && (j === 0 || matrix[i][j - 1] < matrix[i - 1][j])) { } else if (i > 0 && (j === 0 || matrix[i][j - 1] < matrix[i - 1][j])) {
differences.unshift(['red', words1[i - 1]]); differences.unshift(['red', words1[i - 1]]);
i--; i--;
} }
} }
return differences; return differences;
}; };
const differences = diff(text1, text2); const differences = diff(text1, text2);
const displayDifferences = (differences) => { const displayDifferences = (differences) => {
const resultDiv1 = document.getElementById("result1"); const resultDiv1 = document.getElementById("result1");
const resultDiv2 = document.getElementById("result2"); const resultDiv2 = document.getElementById("result2");
resultDiv1.innerHTML = ""; resultDiv1.innerHTML = "";
resultDiv2.innerHTML = ""; resultDiv2.innerHTML = "";
differences.forEach(([color, word]) => { differences.forEach(([color, word]) => {
const span1 = document.createElement("span"); const span1 = document.createElement("span");
const span2 = document.createElement("span"); const span2 = document.createElement("span");
// If it's an addition, show it in green in the second document and transparent in the first // If it's an addition, show it in green in the second document and transparent in the first
if (color === "green") { if (color === "green") {
span1.style.color = "transparent"; span1.style.color = "transparent";
span1.style.userSelect = "none"; span1.style.userSelect = "none";
span2.style.color = color; span2.style.color = color;
} }
// If it's a deletion, show it in red in the first document and transparent in the second // If it's a deletion, show it in red in the first document and transparent in the second
else if (color === "red") { else if (color === "red") {
span1.style.color = color; span1.style.color = color;
span2.style.color = "transparent"; span2.style.color = "transparent";
span2.style.userSelect = "none"; span2.style.userSelect = "none";
} }
// If it's unchanged, show it in black in both // If it's unchanged, show it in black in both
else { else {
span1.style.color = color; span1.style.color = color;
span2.style.color = color; span2.style.color = color;
} }
span1.textContent = word; span1.textContent = word;
span2.textContent = word; span2.textContent = word;
resultDiv1.appendChild(span1); resultDiv1.appendChild(span1);
resultDiv2.appendChild(span2); resultDiv2.appendChild(span2);
// Add space after each word, or a new line if the word ends with a full stop // Add space after each word, or a new line if the word ends with a full stop
const spaceOrNewline1 = document.createElement("span"); const spaceOrNewline1 = document.createElement("span");
const spaceOrNewline2 = document.createElement("span"); const spaceOrNewline2 = document.createElement("span");
if (word.endsWith(".")) { if (word.endsWith(".")) {
spaceOrNewline1.innerHTML = "<br>"; spaceOrNewline1.innerHTML = "<br>";
spaceOrNewline2.innerHTML = "<br>"; spaceOrNewline2.innerHTML = "<br>";
} else { } else {
spaceOrNewline1.textContent = " "; spaceOrNewline1.textContent = " ";
spaceOrNewline2.textContent = " "; spaceOrNewline2.textContent = " ";
} }
resultDiv1.appendChild(spaceOrNewline1); resultDiv1.appendChild(spaceOrNewline1);
resultDiv2.appendChild(spaceOrNewline2); resultDiv2.appendChild(spaceOrNewline2);
}); });
}; };
console.log('Differences:', differences); console.log('Differences:', differences);
displayDifferences(differences); displayDifferences(differences);
} }
</script> </script>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div th:insert="~{fragments/footer.html :: footer}"></div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>

View File

@ -1,132 +1,132 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org"> <html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{rotate.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{rotate.title})}"></th:block>
<body> <body>
<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>
<br> <br> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{rotate.header}"></h2> <h2 th:text="#{rotate.header}"></h2>
<form action="#" th:action="@{rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data"> <form action="#" th:action="@{rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<input type="hidden" id="angleInput" name="angle" value="0"> <input type="hidden" id="angleInput" name="angle" value="0">
<div id="editSection" style="display: none"> <div id="editSection" style="display: none">
<div class="previewContainer"> <div class="previewContainer">
<img id="pdf-preview" /> <img id="pdf-preview" />
</div> </div>
<div class="buttonContainer"> <div class="buttonContainer">
<button type="button" class="btn btn-secondary" onclick="rotate(-90)"> <button type="button" class="btn btn-secondary" onclick="rotate(-90)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" /> <path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" />
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" /> <path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
</svg> </svg>
</button> </button>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{rotate.submit}"></button> <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{rotate.submit}"></button>
<button type="button" class="btn btn-secondary" onclick="rotate(90)"> <button type="button" class="btn btn-secondary" onclick="rotate(90)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" /> <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" /> <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
</svg> </svg>
</button> </button>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div th:insert="~{fragments/footer.html :: footer}"></div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
<script> <script>
const angleInput = document.getElementById("angleInput"); const angleInput = document.getElementById("angleInput");
const fileInput = document.getElementById("fileInput-input"); const fileInput = document.getElementById("fileInput-input");
const preview = document.getElementById("pdf-preview"); const preview = document.getElementById("pdf-preview");
fileInput.addEventListener("change", async function() { fileInput.addEventListener("change", async function() {
console.log("loading pdf"); console.log("loading pdf");
document.querySelector("#editSection").style.display = ""; document.querySelector("#editSection").style.display = "";
var url = URL.createObjectURL(fileInput.files[0]) var url = URL.createObjectURL(fileInput.files[0])
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
const pdf = await pdfjsLib.getDocument(url).promise; const pdf = await pdfjsLib.getDocument(url).promise;
const page = await pdf.getPage(1); const page = await pdf.getPage(1);
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
// set the canvas size to the size of the page // set the canvas size to the size of the page
if (page.rotate == 90 || page.rotate == 270) { if (page.rotate == 90 || page.rotate == 270) {
canvas.width = page.view[3]; canvas.width = page.view[3];
canvas.height = page.view[2]; canvas.height = page.view[2];
} else { } else {
canvas.width = page.view[2]; canvas.width = page.view[2];
canvas.height = page.view[3]; canvas.height = page.view[3];
} }
// render the page onto the canvas // render the page onto the canvas
var renderContext = { var renderContext = {
canvasContext: canvas.getContext("2d"), canvasContext: canvas.getContext("2d"),
viewport: page.getViewport({ scale: 1 }) viewport: page.getViewport({ scale: 1 })
}; };
await page.render(renderContext).promise; await page.render(renderContext).promise;
preview.src = canvas.toDataURL(); preview.src = canvas.toDataURL();
}); });
function rotate(deg) { function rotate(deg) {
var lastTransform = preview.style.rotate; var lastTransform = preview.style.rotate;
if (!lastTransform) { if (!lastTransform) {
lastTransform = "0"; lastTransform = "0";
} }
const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, '')); const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ''));
const newAngle = lastAngle + deg; const newAngle = lastAngle + deg;
preview.style.rotate = newAngle + "deg"; preview.style.rotate = newAngle + "deg";
angleInput.value = newAngle; angleInput.value = newAngle;
} }
</script> </script>
<style> <style>
#pdf-preview { #pdf-preview {
margin: 0 auto; margin: 0 auto;
display: block; display: block;
max-width: calc(100% - 30px); max-width: calc(100% - 30px);
max-height: calc(100% - 30px); max-height: calc(100% - 30px);
box-shadow: 0 0 4px rgba(100, 100, 100, .25); box-shadow: 0 0 4px rgba(100, 100, 100, .25);
transition: rotate .3s; transition: rotate .3s;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
translate: -50% -50%; translate: -50% -50%;
} }
.previewContainer { .previewContainer {
aspect-ratio: 1; aspect-ratio: 1;
width: 100%; width: 100%;
border: 1px solid rgba(0, 0, 0, .125); border: 1px solid rgba(0, 0, 0, .125);
border-radius: 0.25rem; border-radius: 0.25rem;
margin: 1rem 0; margin: 1rem 0;
padding: 15px; padding: 15px;
display: block; display: block;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
} }
.buttonContainer { .buttonContainer {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
} }
</style> </style>
</body> </body>
</html> </html>

View File

@ -47,6 +47,7 @@ select#font-select, select#font-select option {
if (file) { if (file) {
originalFileName = file.name.replace(/\.[^/.]+$/, ""); originalFileName = file.name.replace(/\.[^/.]+$/, "");
const pdfData = await file.arrayBuffer(); const pdfData = await file.arrayBuffer();
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise; const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
await DraggableUtils.renderPage(pdfDoc, 0); await DraggableUtils.renderPage(pdfDoc, 0);