mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01:00 
			
		
		
		
	pdfjs worker changes and crop fix
This commit is contained in:
		
							parent
							
								
									e83a027023
								
							
						
					
					
						commit
						749461334d
					
				@ -90,7 +90,7 @@ public class CropController {
 | 
			
		||||
	@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")
 | 
			
		||||
	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 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,
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -126,7 +126,7 @@ home.PDFToWord.desc=Convert PDF to Word formats (DOC, DOCX and ODT)
 | 
			
		||||
home.PDFToPresentation.title=PDF to Presentation
 | 
			
		||||
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.PDFToHTML.title=PDF to HTML
 | 
			
		||||
@ -582,8 +582,8 @@ PDFToPresentation.submit=Convert
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#PDFToText
 | 
			
		||||
PDFToText.title=PDF to Text/RTF
 | 
			
		||||
PDFToText.header=PDF to Text/RTF
 | 
			
		||||
PDFToText.title=PDF to RTF (Text)
 | 
			
		||||
PDFToText.header=PDF to RTF (Text)
 | 
			
		||||
PDFToText.selectText.1=Output file format
 | 
			
		||||
PDFToText.credit=This service uses LibreOffice for file conversion.
 | 
			
		||||
PDFToText.submit=Convert
 | 
			
		||||
 | 
			
		||||
@ -594,7 +594,7 @@ PDFToPresentation.submit=변환
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#PDFToText
 | 
			
		||||
PDFToText.title=PDF to Text/RTF
 | 
			
		||||
PDFToText.title=PDF to RTF (Text)
 | 
			
		||||
PDFToText.header=PDF를 텍스트/RTF로 변환
 | 
			
		||||
PDFToText.selectText.1=출력 파일 형식
 | 
			
		||||
PDFToText.credit=이 서비스는 파일 변환을 위해 LibreOffice를 사용합니다.
 | 
			
		||||
 | 
			
		||||
@ -135,7 +135,7 @@ home.PDFToWord.desc=将PDF转换为Word格式(DOC、DOCX和ODT)。
 | 
			
		||||
home.PDFToPresentation.title=PDF To Presentation
 | 
			
		||||
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.PDFToHTML.title=PDF To HTML
 | 
			
		||||
@ -594,7 +594,7 @@ PDFToPresentation.submit=转换
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#PDFToText
 | 
			
		||||
PDFToText.title=PDF To Text/RTF
 | 
			
		||||
PDFToText.title=PDF to RTF (Text)
 | 
			
		||||
PDFToText.header=将PDF转换成文本/RTF
 | 
			
		||||
PDFToText.selectText.1=输出文件格式
 | 
			
		||||
PDFToText.credit=该服务使用LibreOffice进行文件转换。
 | 
			
		||||
 | 
			
		||||
@ -69,4 +69,14 @@ html[lang-direction="rtl"] label.form-check-label {
 | 
			
		||||
#pdf-canvas {
 | 
			
		||||
    box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
 | 
			
		||||
    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;
 | 
			
		||||
}
 | 
			
		||||
@ -1,206 +1,207 @@
 | 
			
		||||
class PdfContainer {
 | 
			
		||||
    fileName;
 | 
			
		||||
    pagesContainer;
 | 
			
		||||
    pagesContainerWrapper;
 | 
			
		||||
    pdfAdapters;
 | 
			
		||||
 | 
			
		||||
    constructor(id, wrapperId, pdfAdapters) {
 | 
			
		||||
        this.fileName = null;
 | 
			
		||||
        this.pagesContainer = document.getElementById(id)
 | 
			
		||||
        this.pagesContainerWrapper = document.getElementById(wrapperId);
 | 
			
		||||
        this.movePageTo = this.movePageTo.bind(this);
 | 
			
		||||
        this.addPdfs = this.addPdfs.bind(this);
 | 
			
		||||
        this.rotateElement = this.rotateElement.bind(this);
 | 
			
		||||
        this.rotateAll = this.rotateAll.bind(this);
 | 
			
		||||
        this.exportPdf = this.exportPdf.bind(this);
 | 
			
		||||
 | 
			
		||||
        this.pdfAdapters = pdfAdapters;
 | 
			
		||||
 | 
			
		||||
        this.pdfAdapters.forEach(adapter => {
 | 
			
		||||
            adapter.setActions({
 | 
			
		||||
                movePageTo: this.movePageTo,
 | 
			
		||||
                addPdfs: this.addPdfs,
 | 
			
		||||
                rotateElement: this.rotateElement,
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        window.addPdfs = this.addPdfs;
 | 
			
		||||
        window.exportPdf = this.exportPdf;
 | 
			
		||||
        window.rotateAll = this.rotateAll;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    movePageTo(startElement, endElement, scrollTo = false) {
 | 
			
		||||
        const childArray = Array.from(this.pagesContainer.childNodes);
 | 
			
		||||
        const startIndex = childArray.indexOf(startElement);
 | 
			
		||||
        const endIndex = childArray.indexOf(endElement);
 | 
			
		||||
        this.pagesContainer.removeChild(startElement);
 | 
			
		||||
        if(!endElement) {
 | 
			
		||||
            this.pagesContainer.append(startElement);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.pagesContainer.insertBefore(startElement, endElement);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(scrollTo) {
 | 
			
		||||
            const { width } = startElement.getBoundingClientRect();
 | 
			
		||||
            const vector = (endIndex !== -1 && startIndex > endIndex)
 | 
			
		||||
                ?  0-width
 | 
			
		||||
                : width;
 | 
			
		||||
            
 | 
			
		||||
            this.pagesContainerWrapper.scroll({
 | 
			
		||||
                left: this.pagesContainerWrapper.scrollLeft + vector,
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addPdfs(nextSiblingElement) {
 | 
			
		||||
        var input = document.createElement('input');
 | 
			
		||||
        input.type = 'file';
 | 
			
		||||
        input.multiple = true;
 | 
			
		||||
        input.setAttribute("accept", "application/pdf");
 | 
			
		||||
 | 
			
		||||
        input.onchange = async(e) => {
 | 
			
		||||
            const files = e.target.files;
 | 
			
		||||
            this.fileName = files[0].name;
 | 
			
		||||
            for (var i=0; i < files.length; i++) {
 | 
			
		||||
                await this.addPdfFile(files[i], nextSiblingElement);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            document.querySelectorAll(".enable-on-file").forEach(element => {
 | 
			
		||||
                element.disabled = false;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        input.click();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    rotateElement(element, deg) {
 | 
			
		||||
        var lastTransform = element.style.rotate;
 | 
			
		||||
        if (!lastTransform) {
 | 
			
		||||
            lastTransform = "0";
 | 
			
		||||
        }
 | 
			
		||||
        const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ''));
 | 
			
		||||
        const newAngle = lastAngle + deg;
 | 
			
		||||
 | 
			
		||||
        element.style.rotate = newAngle + "deg";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async addPdfFile(file, nextSiblingElement) {
 | 
			
		||||
        const { renderer, pdfDocument } = await this.loadFile(file);
 | 
			
		||||
 | 
			
		||||
        for (var i=0; i < renderer.pageCount; i++) {
 | 
			
		||||
            const div = document.createElement('div');
 | 
			
		||||
 | 
			
		||||
            div.classList.add("page-container");
 | 
			
		||||
 | 
			
		||||
            var img = document.createElement('img');
 | 
			
		||||
            img.classList.add('page-image')
 | 
			
		||||
            const imageSrc = await renderer.renderPage(i)
 | 
			
		||||
            img.src = imageSrc;
 | 
			
		||||
            img.pageIdx = i;
 | 
			
		||||
            img.rend = renderer;
 | 
			
		||||
            img.doc = pdfDocument;
 | 
			
		||||
            div.appendChild(img);
 | 
			
		||||
 | 
			
		||||
            this.pdfAdapters.forEach((adapter) => {
 | 
			
		||||
                adapter.adapt?.(div)
 | 
			
		||||
            })
 | 
			
		||||
            if (nextSiblingElement) {
 | 
			
		||||
                this.pagesContainer.insertBefore(div, nextSiblingElement);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.pagesContainer.appendChild(div);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async loadFile(file) {
 | 
			
		||||
        var objectUrl = URL.createObjectURL(file);
 | 
			
		||||
        var pdfDocument = await this.toPdfLib(objectUrl);
 | 
			
		||||
        var renderer = await this.toRenderer(objectUrl);
 | 
			
		||||
        return { renderer, pdfDocument };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async toRenderer(objectUrl) {
 | 
			
		||||
        const pdf = await pdfjsLib.getDocument(objectUrl).promise;
 | 
			
		||||
        return {
 | 
			
		||||
            document: pdf,
 | 
			
		||||
            pageCount: pdf.numPages,
 | 
			
		||||
            renderPage: async function(pageIdx) {
 | 
			
		||||
                const page = await this.document.getPage(pageIdx+1);
 | 
			
		||||
 | 
			
		||||
                const canvas = document.createElement("canvas");
 | 
			
		||||
 | 
			
		||||
                // set the canvas size to the size of the page
 | 
			
		||||
                if (page.rotate == 90 || page.rotate == 270) {
 | 
			
		||||
                    canvas.width = page.view[3];
 | 
			
		||||
                    canvas.height = page.view[2];
 | 
			
		||||
                } else {
 | 
			
		||||
                    canvas.width = page.view[2];
 | 
			
		||||
                    canvas.height = page.view[3];
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // render the page onto the canvas
 | 
			
		||||
                var renderContext = {
 | 
			
		||||
                    canvasContext: canvas.getContext("2d"),
 | 
			
		||||
                    viewport: page.getViewport({ scale: 1 })
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                await page.render(renderContext).promise;
 | 
			
		||||
                return canvas.toDataURL();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async toPdfLib(objectUrl) {
 | 
			
		||||
        const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer());
 | 
			
		||||
        const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, { ignoreEncryption: true });
 | 
			
		||||
        return pdfDoc;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    rotateAll(deg) {
 | 
			
		||||
        for (var i=0; i<this.pagesContainer.childNodes.length; i++) {
 | 
			
		||||
            const img = this.pagesContainer.childNodes[i].querySelector("img");
 | 
			
		||||
            if (!img) continue;
 | 
			
		||||
            this.rotateElement(img, deg)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async exportPdf() {
 | 
			
		||||
        const pdfDoc = await PDFLib.PDFDocument.create();
 | 
			
		||||
        for (var i=0; i<this.pagesContainer.childNodes.length; i++) {
 | 
			
		||||
            const img = this.pagesContainer.childNodes[i].querySelector("img");
 | 
			
		||||
            if (!img) continue;
 | 
			
		||||
            const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx])
 | 
			
		||||
            const page = pages[0];
 | 
			
		||||
 | 
			
		||||
            const rotation = img.style.rotate;
 | 
			
		||||
            if (rotation) {
 | 
			
		||||
                const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, ''));
 | 
			
		||||
                page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle))
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            pdfDoc.addPage(page);
 | 
			
		||||
        }
 | 
			
		||||
        const pdfBytes = await pdfDoc.save();
 | 
			
		||||
        const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
 | 
			
		||||
        const url = URL.createObjectURL(pdfBlob);
 | 
			
		||||
        const downloadOption = localStorage.getItem('downloadOption');
 | 
			
		||||
 | 
			
		||||
        if (downloadOption === 'sameWindow') {
 | 
			
		||||
            // Open the file in the same window
 | 
			
		||||
            window.location.href = url;
 | 
			
		||||
        } else if (downloadOption === 'newWindow') {
 | 
			
		||||
            // Open the file in a new window
 | 
			
		||||
            window.open(url, '_blank');
 | 
			
		||||
        } else {
 | 
			
		||||
            // Download the file
 | 
			
		||||
            const downloadLink = document.createElement('a');
 | 
			
		||||
            downloadLink.href = url;
 | 
			
		||||
            downloadLink.download = this.fileName ? this.fileName : 'managed.pdf';
 | 
			
		||||
            downloadLink.click();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default PdfContainer;
 | 
			
		||||
class PdfContainer {
 | 
			
		||||
    fileName;
 | 
			
		||||
    pagesContainer;
 | 
			
		||||
    pagesContainerWrapper;
 | 
			
		||||
    pdfAdapters;
 | 
			
		||||
 | 
			
		||||
    constructor(id, wrapperId, pdfAdapters) {
 | 
			
		||||
        this.fileName = null;
 | 
			
		||||
        this.pagesContainer = document.getElementById(id)
 | 
			
		||||
        this.pagesContainerWrapper = document.getElementById(wrapperId);
 | 
			
		||||
        this.movePageTo = this.movePageTo.bind(this);
 | 
			
		||||
        this.addPdfs = this.addPdfs.bind(this);
 | 
			
		||||
        this.rotateElement = this.rotateElement.bind(this);
 | 
			
		||||
        this.rotateAll = this.rotateAll.bind(this);
 | 
			
		||||
        this.exportPdf = this.exportPdf.bind(this);
 | 
			
		||||
 | 
			
		||||
        this.pdfAdapters = pdfAdapters;
 | 
			
		||||
 | 
			
		||||
        this.pdfAdapters.forEach(adapter => {
 | 
			
		||||
            adapter.setActions({
 | 
			
		||||
                movePageTo: this.movePageTo,
 | 
			
		||||
                addPdfs: this.addPdfs,
 | 
			
		||||
                rotateElement: this.rotateElement,
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        window.addPdfs = this.addPdfs;
 | 
			
		||||
        window.exportPdf = this.exportPdf;
 | 
			
		||||
        window.rotateAll = this.rotateAll;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    movePageTo(startElement, endElement, scrollTo = false) {
 | 
			
		||||
        const childArray = Array.from(this.pagesContainer.childNodes);
 | 
			
		||||
        const startIndex = childArray.indexOf(startElement);
 | 
			
		||||
        const endIndex = childArray.indexOf(endElement);
 | 
			
		||||
        this.pagesContainer.removeChild(startElement);
 | 
			
		||||
        if(!endElement) {
 | 
			
		||||
            this.pagesContainer.append(startElement);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.pagesContainer.insertBefore(startElement, endElement);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(scrollTo) {
 | 
			
		||||
            const { width } = startElement.getBoundingClientRect();
 | 
			
		||||
            const vector = (endIndex !== -1 && startIndex > endIndex)
 | 
			
		||||
                ?  0-width
 | 
			
		||||
                : width;
 | 
			
		||||
            
 | 
			
		||||
            this.pagesContainerWrapper.scroll({
 | 
			
		||||
                left: this.pagesContainerWrapper.scrollLeft + vector,
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addPdfs(nextSiblingElement) {
 | 
			
		||||
        var input = document.createElement('input');
 | 
			
		||||
        input.type = 'file';
 | 
			
		||||
        input.multiple = true;
 | 
			
		||||
        input.setAttribute("accept", "application/pdf");
 | 
			
		||||
 | 
			
		||||
        input.onchange = async(e) => {
 | 
			
		||||
            const files = e.target.files;
 | 
			
		||||
            this.fileName = files[0].name;
 | 
			
		||||
            for (var i=0; i < files.length; i++) {
 | 
			
		||||
                await this.addPdfFile(files[i], nextSiblingElement);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            document.querySelectorAll(".enable-on-file").forEach(element => {
 | 
			
		||||
                element.disabled = false;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        input.click();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    rotateElement(element, deg) {
 | 
			
		||||
        var lastTransform = element.style.rotate;
 | 
			
		||||
        if (!lastTransform) {
 | 
			
		||||
            lastTransform = "0";
 | 
			
		||||
        }
 | 
			
		||||
        const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ''));
 | 
			
		||||
        const newAngle = lastAngle + deg;
 | 
			
		||||
 | 
			
		||||
        element.style.rotate = newAngle + "deg";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async addPdfFile(file, nextSiblingElement) {
 | 
			
		||||
        const { renderer, pdfDocument } = await this.loadFile(file);
 | 
			
		||||
 | 
			
		||||
        for (var i=0; i < renderer.pageCount; i++) {
 | 
			
		||||
            const div = document.createElement('div');
 | 
			
		||||
 | 
			
		||||
            div.classList.add("page-container");
 | 
			
		||||
 | 
			
		||||
            var img = document.createElement('img');
 | 
			
		||||
            img.classList.add('page-image')
 | 
			
		||||
            const imageSrc = await renderer.renderPage(i)
 | 
			
		||||
            img.src = imageSrc;
 | 
			
		||||
            img.pageIdx = i;
 | 
			
		||||
            img.rend = renderer;
 | 
			
		||||
            img.doc = pdfDocument;
 | 
			
		||||
            div.appendChild(img);
 | 
			
		||||
 | 
			
		||||
            this.pdfAdapters.forEach((adapter) => {
 | 
			
		||||
                adapter.adapt?.(div)
 | 
			
		||||
            })
 | 
			
		||||
            if (nextSiblingElement) {
 | 
			
		||||
                this.pagesContainer.insertBefore(div, nextSiblingElement);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.pagesContainer.appendChild(div);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async loadFile(file) {
 | 
			
		||||
        var objectUrl = URL.createObjectURL(file);
 | 
			
		||||
        var pdfDocument = await this.toPdfLib(objectUrl);
 | 
			
		||||
        var renderer = await this.toRenderer(objectUrl);
 | 
			
		||||
        return { renderer, pdfDocument };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async toRenderer(objectUrl) {
 | 
			
		||||
		pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
 | 
			
		||||
        const pdf = await pdfjsLib.getDocument(objectUrl).promise;
 | 
			
		||||
        return {
 | 
			
		||||
            document: pdf,
 | 
			
		||||
            pageCount: pdf.numPages,
 | 
			
		||||
            renderPage: async function(pageIdx) {
 | 
			
		||||
                const page = await this.document.getPage(pageIdx+1);
 | 
			
		||||
 | 
			
		||||
                const canvas = document.createElement("canvas");
 | 
			
		||||
 | 
			
		||||
                // set the canvas size to the size of the page
 | 
			
		||||
                if (page.rotate == 90 || page.rotate == 270) {
 | 
			
		||||
                    canvas.width = page.view[3];
 | 
			
		||||
                    canvas.height = page.view[2];
 | 
			
		||||
                } else {
 | 
			
		||||
                    canvas.width = page.view[2];
 | 
			
		||||
                    canvas.height = page.view[3];
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // render the page onto the canvas
 | 
			
		||||
                var renderContext = {
 | 
			
		||||
                    canvasContext: canvas.getContext("2d"),
 | 
			
		||||
                    viewport: page.getViewport({ scale: 1 })
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                await page.render(renderContext).promise;
 | 
			
		||||
                return canvas.toDataURL();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async toPdfLib(objectUrl) {
 | 
			
		||||
        const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer());
 | 
			
		||||
        const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, { ignoreEncryption: true });
 | 
			
		||||
        return pdfDoc;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    rotateAll(deg) {
 | 
			
		||||
        for (var i=0; i<this.pagesContainer.childNodes.length; i++) {
 | 
			
		||||
            const img = this.pagesContainer.childNodes[i].querySelector("img");
 | 
			
		||||
            if (!img) continue;
 | 
			
		||||
            this.rotateElement(img, deg)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async exportPdf() {
 | 
			
		||||
        const pdfDoc = await PDFLib.PDFDocument.create();
 | 
			
		||||
        for (var i=0; i<this.pagesContainer.childNodes.length; i++) {
 | 
			
		||||
            const img = this.pagesContainer.childNodes[i].querySelector("img");
 | 
			
		||||
            if (!img) continue;
 | 
			
		||||
            const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx])
 | 
			
		||||
            const page = pages[0];
 | 
			
		||||
 | 
			
		||||
            const rotation = img.style.rotate;
 | 
			
		||||
            if (rotation) {
 | 
			
		||||
                const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, ''));
 | 
			
		||||
                page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle))
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            pdfDoc.addPage(page);
 | 
			
		||||
        }
 | 
			
		||||
        const pdfBytes = await pdfDoc.save();
 | 
			
		||||
        const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
 | 
			
		||||
        const url = URL.createObjectURL(pdfBlob);
 | 
			
		||||
        const downloadOption = localStorage.getItem('downloadOption');
 | 
			
		||||
 | 
			
		||||
        if (downloadOption === 'sameWindow') {
 | 
			
		||||
            // Open the file in the same window
 | 
			
		||||
            window.location.href = url;
 | 
			
		||||
        } else if (downloadOption === 'newWindow') {
 | 
			
		||||
            // Open the file in a new window
 | 
			
		||||
            window.open(url, '_blank');
 | 
			
		||||
        } else {
 | 
			
		||||
            // Download the file
 | 
			
		||||
            const downloadLink = document.createElement('a');
 | 
			
		||||
            downloadLink.href = url;
 | 
			
		||||
            downloadLink.download = this.fileName ? this.fileName : 'managed.pdf';
 | 
			
		||||
            downloadLink.click();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default PdfContainer;
 | 
			
		||||
 | 
			
		||||
@ -1,35 +1,34 @@
 | 
			
		||||
<!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=#{PDFToText.title})}"></th:block>
 | 
			
		||||
<body>
 | 
			
		||||
    <th:block th:insert="~{fragments/common :: game}"></th:block>
 | 
			
		||||
    <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="#{PDFToText.header}"></h2>
 | 
			
		||||
                        <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 class="form-group">
 | 
			
		||||
                                <label th:text="#{PDFToText.selectText.1}"></label> 
 | 
			
		||||
                                <select class="form-control" name="outputFormat">
 | 
			
		||||
                                    <option value="rtf">RTF</option>
 | 
			
		||||
                                    <option value="txt:Text">TXT</option>
 | 
			
		||||
                                </select>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <br>
 | 
			
		||||
                            <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToText.submit}"></button>
 | 
			
		||||
 | 
			
		||||
                        </form>
 | 
			
		||||
                        <p class="mt-3" th:text="#{PDFToText.credit}"></p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
<!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=#{PDFToText.title})}"></th:block>
 | 
			
		||||
<body>
 | 
			
		||||
    <th:block th:insert="~{fragments/common :: game}"></th:block>
 | 
			
		||||
    <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="#{PDFToText.header}"></h2>
 | 
			
		||||
                        <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 class="form-group">
 | 
			
		||||
                                <label th:text="#{PDFToText.selectText.1}"></label> 
 | 
			
		||||
                                <select class="form-control" name="outputFormat">
 | 
			
		||||
                                    <option value="rtf">RTF</option>
 | 
			
		||||
                                </select>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <br>
 | 
			
		||||
                            <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToText.submit}"></button>
 | 
			
		||||
 | 
			
		||||
                        </form>
 | 
			
		||||
                        <p class="mt-3" th:text="#{PDFToText.credit}"></p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
</div>
 | 
			
		||||
@ -22,13 +22,13 @@
 | 
			
		||||
              <button type="submit" class="btn btn-primary" th:text="#{crop.submit}"></button>
 | 
			
		||||
            </form>
 | 
			
		||||
            <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>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
            <script>
 | 
			
		||||
            
 | 
			
		||||
            let pdfCanvas  = document.getElementById('pdf-canvas');
 | 
			
		||||
            let pdfCanvas  = document.getElementById('crop-pdf-canvas');
 | 
			
		||||
            let overlayCanvas = document.getElementById('overlayCanvas');
 | 
			
		||||
 | 
			
		||||
            let context = pdfCanvas.getContext('2d');
 | 
			
		||||
@ -61,6 +61,7 @@
 | 
			
		||||
                let reader = new FileReader();
 | 
			
		||||
                reader.onload = function(ev) {
 | 
			
		||||
                  let typedArray = new Uint8Array(reader.result);
 | 
			
		||||
                  pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
 | 
			
		||||
                  pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
 | 
			
		||||
                    pdfDoc = pdf;
 | 
			
		||||
                    totalPages = pdf.numPages;
 | 
			
		||||
@ -126,6 +127,7 @@
 | 
			
		||||
                    
 | 
			
		||||
                    let renderContext = { canvasContext: context, viewport: viewport };
 | 
			
		||||
                    page.render(renderContext);
 | 
			
		||||
                    pdfCanvas.classList.add("shadow-canvas");
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
           
 | 
			
		||||
 | 
			
		||||
@ -1,140 +1,141 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <th:block th:insert="~{fragments/common :: head(title=#{addImage.title})}"></th:block>
 | 
			
		||||
    <script src="js/thirdParty/interact.min.js"></script>
 | 
			
		||||
 | 
			
		||||
</head>
 | 
			
		||||
<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="#{addImage.header}"></h2>
 | 
			
		||||
 | 
			
		||||
                        <!-- pdf selector -->
 | 
			
		||||
                        <div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multiple=false, accept='application/pdf')}"></div>
 | 
			
		||||
                        <script>
 | 
			
		||||
                        	let originalFileName = '';
 | 
			
		||||
                            document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
 | 
			
		||||
                                const file = event.target.files[0];
 | 
			
		||||
                                if (file) {
 | 
			
		||||
                                	originalFileName = file.name.replace(/\.[^/.]+$/, "");
 | 
			
		||||
                                    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" th:title="#{addImage.upload}">
 | 
			
		||||
                                <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>
 | 
			
		||||
 | 
			
		||||
                        <!-- draggables box -->
 | 
			
		||||
                        <div id="box-drag-container" class="show-on-file-selected">
 | 
			
		||||
                            <canvas id="pdf-canvas"></canvas>
 | 
			
		||||
                            <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;
 | 
			
		||||
                                    margin: 20px 0;
 | 
			
		||||
                                }
 | 
			
		||||
                                #pdf-canvas {
 | 
			
		||||
                                    box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
 | 
			
		||||
                                    width: 100%;
 | 
			
		||||
                                }
 | 
			
		||||
                                .draggable-buttons-box {
 | 
			
		||||
                                    position: absolute;
 | 
			
		||||
                                    top: 0;
 | 
			
		||||
                                    padding: 10px;
 | 
			
		||||
                                    width: 100%;
 | 
			
		||||
                                    display: flex;
 | 
			
		||||
                                    gap: 5px;
 | 
			
		||||
                                }
 | 
			
		||||
                                .draggable-buttons-box > button {
 | 
			
		||||
                                    z-index: 10;
 | 
			
		||||
                                    background-color: 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>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- 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 = originalFileName + '_addedImage.pdf';
 | 
			
		||||
                                link.click();
 | 
			
		||||
                            });
 | 
			
		||||
                        </script>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
</body>
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <th:block th:insert="~{fragments/common :: head(title=#{addImage.title})}"></th:block>
 | 
			
		||||
    <script src="js/thirdParty/interact.min.js"></script>
 | 
			
		||||
 | 
			
		||||
</head>
 | 
			
		||||
<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="#{addImage.header}"></h2>
 | 
			
		||||
 | 
			
		||||
                        <!-- pdf selector -->
 | 
			
		||||
                        <div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multiple=false, accept='application/pdf')}"></div>
 | 
			
		||||
                        <script>
 | 
			
		||||
                        	let originalFileName = '';
 | 
			
		||||
                            document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
 | 
			
		||||
                                const file = event.target.files[0];
 | 
			
		||||
                                if (file) {
 | 
			
		||||
                                	originalFileName = file.name.replace(/\.[^/.]+$/, "");
 | 
			
		||||
                                    const pdfData = await file.arrayBuffer();
 | 
			
		||||
                                    pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
 | 
			
		||||
                                    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" th:title="#{addImage.upload}">
 | 
			
		||||
                                <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>
 | 
			
		||||
 | 
			
		||||
                        <!-- draggables box -->
 | 
			
		||||
                        <div id="box-drag-container" class="show-on-file-selected">
 | 
			
		||||
                            <canvas id="pdf-canvas"></canvas>
 | 
			
		||||
                            <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;
 | 
			
		||||
                                    margin: 20px 0;
 | 
			
		||||
                                }
 | 
			
		||||
                                #pdf-canvas {
 | 
			
		||||
                                    box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
 | 
			
		||||
                                    width: 100%;
 | 
			
		||||
                                }
 | 
			
		||||
                                .draggable-buttons-box {
 | 
			
		||||
                                    position: absolute;
 | 
			
		||||
                                    top: 0;
 | 
			
		||||
                                    padding: 10px;
 | 
			
		||||
                                    width: 100%;
 | 
			
		||||
                                    display: flex;
 | 
			
		||||
                                    gap: 5px;
 | 
			
		||||
                                }
 | 
			
		||||
                                .draggable-buttons-box > button {
 | 
			
		||||
                                    z-index: 10;
 | 
			
		||||
                                    background-color: 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>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- 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 = originalFileName + '_addedImage.pdf';
 | 
			
		||||
                                link.click();
 | 
			
		||||
                            });
 | 
			
		||||
                        </script>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
@ -14,37 +14,50 @@
 | 
			
		||||
			<br> <br>
 | 
			
		||||
			<div class="container">
 | 
			
		||||
				<div class="row justify-content-center">
 | 
			
		||||
					<div class="col-md-6">
 | 
			
		||||
						<h2 th:text="#{adjustContrast.header}"></h2>
 | 
			
		||||
						<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf', remoteCall='false')}"></div>
 | 
			
		||||
						<h4>
 | 
			
		||||
							<span th:text="#{adjustContrast.contrast}"></span> <span id="contrast-val">100</span>%
 | 
			
		||||
						</h4>
 | 
			
		||||
						<input type="range" min="0" max="200" value="100"
 | 
			
		||||
							id="contrast-slider" />
 | 
			
		||||
					<div class="col-md-12">
 | 
			
		||||
                        <div class="row justify-content-center">
 | 
			
		||||
                            <div class="col-md-3">
 | 
			
		||||
                                <div id="sliders-container" style="display:none;">
 | 
			
		||||
                                    <h4>
 | 
			
		||||
                                        <span th:text="#{adjustContrast.contrast}"></span> <span id="contrast-val">100</span>%
 | 
			
		||||
                                    </h4>
 | 
			
		||||
                                    <input type="range" min="0" max="200" value="100" id="contrast-slider" />
 | 
			
		||||
 | 
			
		||||
						<h4>
 | 
			
		||||
							<span th:text="#{adjustContrast.brightness}"></span> <span id="brightness-val">100</span>%
 | 
			
		||||
						</h4>
 | 
			
		||||
						<input type="range" min="0" max="200" value="100"
 | 
			
		||||
							id="brightness-slider" />
 | 
			
		||||
                                    <h4>
 | 
			
		||||
                                        <span th:text="#{adjustContrast.brightness}"></span> <span id="brightness-val">100</span>%
 | 
			
		||||
                                    </h4>
 | 
			
		||||
                                    <input type="range" min="0" max="200" value="100" id="brightness-slider" />
 | 
			
		||||
 | 
			
		||||
						<h4>
 | 
			
		||||
							<span th:text="#{adjustContrast.saturation}"></span> <span id="saturation-val">100</span>%
 | 
			
		||||
						</h4>
 | 
			
		||||
						<input type="range" min="0" max="200" value="100"
 | 
			
		||||
							id="saturation-slider" />
 | 
			
		||||
							
 | 
			
		||||
							</br>
 | 
			
		||||
						<canvas id="pdf-canvas"></canvas>
 | 
			
		||||
                                    <h4>
 | 
			
		||||
                                        <span th:text="#{adjustContrast.saturation}"></span> <span id="saturation-val">100</span>%
 | 
			
		||||
                                    </h4>
 | 
			
		||||
                                    <input type="range" min="0" max="200" value="100" id="saturation-slider" />
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-7">
 | 
			
		||||
                                <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>
 | 
			
		||||
						  var canvas = document.getElementById('pdf-canvas');
 | 
			
		||||
						  var canvas = document.getElementById('contrast-pdf-canvas');
 | 
			
		||||
						  var context = canvas.getContext('2d');
 | 
			
		||||
						  var originalImageData = null;
 | 
			
		||||
						  var allPages = [];
 | 
			
		||||
@ -55,6 +68,7 @@
 | 
			
		||||
						    var fileReader = new FileReader();
 | 
			
		||||
						    fileReader.onload = async function() {
 | 
			
		||||
						      var data = new Uint8Array(this.result);
 | 
			
		||||
						      pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
 | 
			
		||||
						      pdf = await pdfjsLib.getDocument({data: data}).promise;
 | 
			
		||||
						
 | 
			
		||||
						      // Get the number of pages in the PDF
 | 
			
		||||
@ -65,6 +79,8 @@
 | 
			
		||||
						      pdfDoc = await PDFLib.PDFDocument.create();
 | 
			
		||||
						      // Render the first page in the viewer
 | 
			
		||||
						      await renderPageAndAdjustImageProperties(1);
 | 
			
		||||
						      document.getElementById("sliders-container").style.display = "block";
 | 
			
		||||
						      
 | 
			
		||||
						    };
 | 
			
		||||
						    fileReader.readAsArrayBuffer(file);
 | 
			
		||||
						  }
 | 
			
		||||
@ -90,6 +106,7 @@
 | 
			
		||||
						            adjustImageProperties();
 | 
			
		||||
						            resolve();
 | 
			
		||||
						          });
 | 
			
		||||
						          canvas.classList.add("fixed-shadow-canvas");
 | 
			
		||||
						      });
 | 
			
		||||
						  }
 | 
			
		||||
						  
 | 
			
		||||
 | 
			
		||||
@ -1,263 +1,263 @@
 | 
			
		||||
<!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=#{changeMetadata.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="#{changeMetadata.header}"></h2>
 | 
			
		||||
 | 
			
		||||
                        <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>
 | 
			
		||||
                            <p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group-inline form-check">
 | 
			
		||||
                                <input type="checkbox" class="form-check-input" id="deleteAll" name="deleteAll"> 
 | 
			
		||||
                                <label class="ml-3" for="deleteAll"  th:text="#{changeMetadata.selectText.2}" ></label>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group-inline form-check">
 | 
			
		||||
                                <input type="checkbox" class="form-check-input" id="customModeCheckbox"> 
 | 
			
		||||
                                <label class="ml-3" for="customModeCheckbox" th:text="#{changeMetadata.selectText.3}"></label>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="author" th:text="#{changeMetadata.author}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="author" name="author">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <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">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="creator" th:text="#{changeMetadata.creator}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="creator" name="creator">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="keywords" th:text="#{changeMetadata.keywords}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="keywords" name="keywords">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <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">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="producer" th:text="#{changeMetadata.producer}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="producer" name="producer">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="subject" th:text="#{changeMetadata.subject}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="subject" name="subject">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="title" th:text="#{changeMetadata.title}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="title" name="title">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="trapped" th:text="#{changeMetadata.trapped}"></label> 
 | 
			
		||||
                                <select class="form-control" id="trapped" name="trapped">
 | 
			
		||||
                                    <option value="True" th:text="#{true}"></option>
 | 
			
		||||
                                    <option value="False"  th:text="#{false}" selected></option>
 | 
			
		||||
                                    <option value="Unknown" th:text="#{unknown}"></option>
 | 
			
		||||
                                  </select>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div id="customMetadata" style="display: none;">
 | 
			
		||||
                                <h3 th:text="#{changeMetadata.selectText.4}"></h3>
 | 
			
		||||
                                <div class="form-group" id="otherMetadataEntries"></div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div id="customMetadataEntries"></div>
 | 
			
		||||
                            <button type="button" class="btn btn-secondary" id="addMetadataBtn" th:text="#{changeMetadata.selectText.5}"></button>
 | 
			
		||||
                            <br>
 | 
			
		||||
                            <br>
 | 
			
		||||
                            <button class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button>
 | 
			
		||||
                            <script>
 | 
			
		||||
  
 | 
			
		||||
    const deleteAllCheckbox = document.querySelector("#deleteAll");
 | 
			
		||||
    const inputs = document.querySelectorAll(".form-control");
 | 
			
		||||
    const customMetadataDiv = document.getElementById('customMetadata');
 | 
			
		||||
    const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries');
 | 
			
		||||
    
 | 
			
		||||
    deleteAllCheckbox.addEventListener("change", function(event) {
 | 
			
		||||
      if (event.target !== deleteAllCheckbox) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      inputs.forEach(input => {
 | 
			
		||||
        if (input === deleteAllCheckbox) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        input.disabled = deleteAllCheckbox.checked;
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
                            
 | 
			
		||||
                            
 | 
			
		||||
  const customModeCheckbox = document.getElementById('customModeCheckbox');
 | 
			
		||||
  
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
  
 | 
			
		||||
  const addMetadataBtn = document.getElementById("addMetadataBtn");
 | 
			
		||||
  const customMetadataFormContainer = document.getElementById("customMetadataEntries");
 | 
			
		||||
  var count = 1;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  const fileInput = document.querySelector("#fileInput-input");
 | 
			
		||||
  const authorInput = document.querySelector("#author");
 | 
			
		||||
  const creationDateInput = document.querySelector("#creationDate");
 | 
			
		||||
  const creatorInput = document.querySelector("#creator");
 | 
			
		||||
  const keywordsInput = document.querySelector("#keywords");
 | 
			
		||||
  const modificationDateInput = document.querySelector("#modificationDate");
 | 
			
		||||
  const producerInput = document.querySelector("#producer");
 | 
			
		||||
  const subjectInput = document.querySelector("#subject");
 | 
			
		||||
  const titleInput = document.querySelector("#title");
 | 
			
		||||
  const trappedInput = document.querySelector("#trapped");
 | 
			
		||||
  
 | 
			
		||||
  var lastPDFFileMeta = null;
 | 
			
		||||
  fileInput.addEventListener("change", async function() {
 | 
			
		||||
	  
 | 
			
		||||
	  
 | 
			
		||||
	  
 | 
			
		||||
	  while (otherMetadataEntriesDiv.firstChild) {
 | 
			
		||||
		  otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
 | 
			
		||||
	  }
 | 
			
		||||
	  while (customMetadataFormContainer.firstChild) {
 | 
			
		||||
		  customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
 | 
			
		||||
      }
 | 
			
		||||
	  
 | 
			
		||||
	  
 | 
			
		||||
	  
 | 
			
		||||
    const file = this.files[0];
 | 
			
		||||
    var url = URL.createObjectURL(file)
 | 
			
		||||
 | 
			
		||||
    const pdf = await pdfjsLib.getDocument(url).promise;
 | 
			
		||||
    const pdfMetadata = await pdf.getMetadata();
 | 
			
		||||
    lastPDFFile = pdfMetadata?.info
 | 
			
		||||
    console.log(pdfMetadata);
 | 
			
		||||
    if(!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) {
 | 
			
		||||
    	customModeCheckbox.disabled = true;
 | 
			
		||||
        customModeCheckbox.checked = false;
 | 
			
		||||
    } else {
 | 
			
		||||
    	customModeCheckbox.disabled = false;
 | 
			
		||||
    }
 | 
			
		||||
    authorInput.value = pdfMetadata?.info?.Author;
 | 
			
		||||
    creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate);
 | 
			
		||||
    creatorInput.value = pdfMetadata?.info?.Creator;
 | 
			
		||||
    keywordsInput.value = pdfMetadata?.info?.Keywords;
 | 
			
		||||
    modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate);
 | 
			
		||||
    producerInput.value = pdfMetadata?.info?.Producer;
 | 
			
		||||
    subjectInput.value = pdfMetadata?.info?.Subject;
 | 
			
		||||
    titleInput.value = pdfMetadata?.info?.Title;
 | 
			
		||||
    console.log(pdfMetadata?.info);
 | 
			
		||||
    const trappedValue = pdfMetadata?.info?.Trapped;
 | 
			
		||||
    // Get all options in the select element
 | 
			
		||||
    const options = trappedInput.options;
 | 
			
		||||
    // Loop through all options to find the one with a matching value
 | 
			
		||||
    for (let i = 0; i < options.length; i++) {
 | 
			
		||||
      if (options[i].value === trappedValue) {
 | 
			
		||||
        options[i].selected = true;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }  
 | 
			
		||||
    addExtra();
 | 
			
		||||
  });
 | 
			
		||||
  
 | 
			
		||||
  addMetadataBtn.addEventListener("click", () => {
 | 
			
		||||
	  
 | 
			
		||||
	    const keyInput = document.createElement("input");
 | 
			
		||||
	    keyInput.type = "text";
 | 
			
		||||
	    keyInput.placeholder = 'Key';
 | 
			
		||||
	    keyInput.className = "form-control";
 | 
			
		||||
	    keyInput.name = "customKey" + count;
 | 
			
		||||
	    
 | 
			
		||||
	    const valueInput = document.createElement("input");
 | 
			
		||||
	    valueInput.type = "text";
 | 
			
		||||
	    valueInput.placeholder = 'Value';
 | 
			
		||||
	    valueInput.className = "form-control";
 | 
			
		||||
	    valueInput.name = "customValue" + count;
 | 
			
		||||
	    count = count + 1;
 | 
			
		||||
	    
 | 
			
		||||
	    const formGroup = document.createElement("div");
 | 
			
		||||
	    formGroup.className = "form-group";
 | 
			
		||||
	    formGroup.appendChild(keyInput);
 | 
			
		||||
	    formGroup.appendChild(valueInput);
 | 
			
		||||
	    
 | 
			
		||||
	    
 | 
			
		||||
	    customMetadataFormContainer.appendChild(formGroup);
 | 
			
		||||
	  });
 | 
			
		||||
  function convertDateFormat(dateTimeString) {
 | 
			
		||||
	  if (!dateTimeString || dateTimeString.length < 17) {
 | 
			
		||||
	    return dateTimeString;
 | 
			
		||||
	  }
 | 
			
		||||
 | 
			
		||||
	  const year = dateTimeString.substring(2, 6);
 | 
			
		||||
	  const month = dateTimeString.substring(6, 8);
 | 
			
		||||
	  const day = dateTimeString.substring(8, 10);
 | 
			
		||||
	  const hour = dateTimeString.substring(10, 12);
 | 
			
		||||
	  const minute = dateTimeString.substring(12, 14);
 | 
			
		||||
	  const second = dateTimeString.substring(14, 16);
 | 
			
		||||
 | 
			
		||||
	  return year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second;
 | 
			
		||||
	}
 | 
			
		||||
  
 | 
			
		||||
    function addExtra() {
 | 
			
		||||
         const event = document.getElementById("customModeCheckbox");
 | 
			
		||||
        
 | 
			
		||||
    
 | 
			
		||||
        if (event.checked && lastPDFFile.Custom != null) {
 | 
			
		||||
          customMetadataDiv.style.display = 'block';
 | 
			
		||||
          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') {
 | 
			
		||||
              continue;
 | 
			
		||||
            }
 | 
			
		||||
            const entryDiv = document.createElement('div');
 | 
			
		||||
            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>`;
 | 
			
		||||
            otherMetadataEntriesDiv.appendChild(entryDiv);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          customMetadataDiv.style.display = 'none';
 | 
			
		||||
          while (otherMetadataEntriesDiv.firstChild) {
 | 
			
		||||
            otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
  customModeCheckbox.addEventListener('change', (event) => {
 | 
			
		||||
     
 | 
			
		||||
      addExtra();
 | 
			
		||||
      });
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
                        </form>
 | 
			
		||||
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
<!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=#{changeMetadata.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="#{changeMetadata.header}"></h2>
 | 
			
		||||
 | 
			
		||||
                        <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>
 | 
			
		||||
                            <p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group-inline form-check">
 | 
			
		||||
                                <input type="checkbox" class="form-check-input" id="deleteAll" name="deleteAll"> 
 | 
			
		||||
                                <label class="ml-3" for="deleteAll"  th:text="#{changeMetadata.selectText.2}" ></label>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group-inline form-check">
 | 
			
		||||
                                <input type="checkbox" class="form-check-input" id="customModeCheckbox"> 
 | 
			
		||||
                                <label class="ml-3" for="customModeCheckbox" th:text="#{changeMetadata.selectText.3}"></label>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="author" th:text="#{changeMetadata.author}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="author" name="author">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <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">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="creator" th:text="#{changeMetadata.creator}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="creator" name="creator">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="keywords" th:text="#{changeMetadata.keywords}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="keywords" name="keywords">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <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">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="producer" th:text="#{changeMetadata.producer}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="producer" name="producer">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="subject" th:text="#{changeMetadata.subject}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="subject" name="subject">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="title" th:text="#{changeMetadata.title}"></label> 
 | 
			
		||||
                                <input type="text" class="form-control" id="title" name="title">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label class="form-check-label" for="trapped" th:text="#{changeMetadata.trapped}"></label> 
 | 
			
		||||
                                <select class="form-control" id="trapped" name="trapped">
 | 
			
		||||
                                    <option value="True" th:text="#{true}"></option>
 | 
			
		||||
                                    <option value="False"  th:text="#{false}" selected></option>
 | 
			
		||||
                                    <option value="Unknown" th:text="#{unknown}"></option>
 | 
			
		||||
                                  </select>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div id="customMetadata" style="display: none;">
 | 
			
		||||
                                <h3 th:text="#{changeMetadata.selectText.4}"></h3>
 | 
			
		||||
                                <div class="form-group" id="otherMetadataEntries"></div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div id="customMetadataEntries"></div>
 | 
			
		||||
                            <button type="button" class="btn btn-secondary" id="addMetadataBtn" th:text="#{changeMetadata.selectText.5}"></button>
 | 
			
		||||
                            <br>
 | 
			
		||||
                            <br>
 | 
			
		||||
                            <button class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button>
 | 
			
		||||
                            <script>
 | 
			
		||||
  
 | 
			
		||||
    const deleteAllCheckbox = document.querySelector("#deleteAll");
 | 
			
		||||
    const inputs = document.querySelectorAll(".form-control");
 | 
			
		||||
    const customMetadataDiv = document.getElementById('customMetadata');
 | 
			
		||||
    const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries');
 | 
			
		||||
    
 | 
			
		||||
    deleteAllCheckbox.addEventListener("change", function(event) {
 | 
			
		||||
      if (event.target !== deleteAllCheckbox) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      inputs.forEach(input => {
 | 
			
		||||
        if (input === deleteAllCheckbox) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        input.disabled = deleteAllCheckbox.checked;
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
                            
 | 
			
		||||
                            
 | 
			
		||||
  const customModeCheckbox = document.getElementById('customModeCheckbox');
 | 
			
		||||
  
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
  
 | 
			
		||||
  const addMetadataBtn = document.getElementById("addMetadataBtn");
 | 
			
		||||
  const customMetadataFormContainer = document.getElementById("customMetadataEntries");
 | 
			
		||||
  var count = 1;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  const fileInput = document.querySelector("#fileInput-input");
 | 
			
		||||
  const authorInput = document.querySelector("#author");
 | 
			
		||||
  const creationDateInput = document.querySelector("#creationDate");
 | 
			
		||||
  const creatorInput = document.querySelector("#creator");
 | 
			
		||||
  const keywordsInput = document.querySelector("#keywords");
 | 
			
		||||
  const modificationDateInput = document.querySelector("#modificationDate");
 | 
			
		||||
  const producerInput = document.querySelector("#producer");
 | 
			
		||||
  const subjectInput = document.querySelector("#subject");
 | 
			
		||||
  const titleInput = document.querySelector("#title");
 | 
			
		||||
  const trappedInput = document.querySelector("#trapped");
 | 
			
		||||
  
 | 
			
		||||
  var lastPDFFileMeta = null;
 | 
			
		||||
  fileInput.addEventListener("change", async function() {
 | 
			
		||||
	  
 | 
			
		||||
	  
 | 
			
		||||
	  
 | 
			
		||||
	  while (otherMetadataEntriesDiv.firstChild) {
 | 
			
		||||
		  otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
 | 
			
		||||
	  }
 | 
			
		||||
	  while (customMetadataFormContainer.firstChild) {
 | 
			
		||||
		  customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
 | 
			
		||||
      }
 | 
			
		||||
	  
 | 
			
		||||
	  
 | 
			
		||||
	  
 | 
			
		||||
    const file = this.files[0];
 | 
			
		||||
    var url = URL.createObjectURL(file)
 | 
			
		||||
	pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
 | 
			
		||||
    const pdf = await pdfjsLib.getDocument(url).promise;
 | 
			
		||||
    const pdfMetadata = await pdf.getMetadata();
 | 
			
		||||
    lastPDFFile = pdfMetadata?.info
 | 
			
		||||
    console.log(pdfMetadata);
 | 
			
		||||
    if(!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) {
 | 
			
		||||
    	customModeCheckbox.disabled = true;
 | 
			
		||||
        customModeCheckbox.checked = false;
 | 
			
		||||
    } else {
 | 
			
		||||
    	customModeCheckbox.disabled = false;
 | 
			
		||||
    }
 | 
			
		||||
    authorInput.value = pdfMetadata?.info?.Author;
 | 
			
		||||
    creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate);
 | 
			
		||||
    creatorInput.value = pdfMetadata?.info?.Creator;
 | 
			
		||||
    keywordsInput.value = pdfMetadata?.info?.Keywords;
 | 
			
		||||
    modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate);
 | 
			
		||||
    producerInput.value = pdfMetadata?.info?.Producer;
 | 
			
		||||
    subjectInput.value = pdfMetadata?.info?.Subject;
 | 
			
		||||
    titleInput.value = pdfMetadata?.info?.Title;
 | 
			
		||||
    console.log(pdfMetadata?.info);
 | 
			
		||||
    const trappedValue = pdfMetadata?.info?.Trapped;
 | 
			
		||||
    // Get all options in the select element
 | 
			
		||||
    const options = trappedInput.options;
 | 
			
		||||
    // Loop through all options to find the one with a matching value
 | 
			
		||||
    for (let i = 0; i < options.length; i++) {
 | 
			
		||||
      if (options[i].value === trappedValue) {
 | 
			
		||||
        options[i].selected = true;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }  
 | 
			
		||||
    addExtra();
 | 
			
		||||
  });
 | 
			
		||||
  
 | 
			
		||||
  addMetadataBtn.addEventListener("click", () => {
 | 
			
		||||
	  
 | 
			
		||||
	    const keyInput = document.createElement("input");
 | 
			
		||||
	    keyInput.type = "text";
 | 
			
		||||
	    keyInput.placeholder = 'Key';
 | 
			
		||||
	    keyInput.className = "form-control";
 | 
			
		||||
	    keyInput.name = "customKey" + count;
 | 
			
		||||
	    
 | 
			
		||||
	    const valueInput = document.createElement("input");
 | 
			
		||||
	    valueInput.type = "text";
 | 
			
		||||
	    valueInput.placeholder = 'Value';
 | 
			
		||||
	    valueInput.className = "form-control";
 | 
			
		||||
	    valueInput.name = "customValue" + count;
 | 
			
		||||
	    count = count + 1;
 | 
			
		||||
	    
 | 
			
		||||
	    const formGroup = document.createElement("div");
 | 
			
		||||
	    formGroup.className = "form-group";
 | 
			
		||||
	    formGroup.appendChild(keyInput);
 | 
			
		||||
	    formGroup.appendChild(valueInput);
 | 
			
		||||
	    
 | 
			
		||||
	    
 | 
			
		||||
	    customMetadataFormContainer.appendChild(formGroup);
 | 
			
		||||
	  });
 | 
			
		||||
  function convertDateFormat(dateTimeString) {
 | 
			
		||||
	  if (!dateTimeString || dateTimeString.length < 17) {
 | 
			
		||||
	    return dateTimeString;
 | 
			
		||||
	  }
 | 
			
		||||
 | 
			
		||||
	  const year = dateTimeString.substring(2, 6);
 | 
			
		||||
	  const month = dateTimeString.substring(6, 8);
 | 
			
		||||
	  const day = dateTimeString.substring(8, 10);
 | 
			
		||||
	  const hour = dateTimeString.substring(10, 12);
 | 
			
		||||
	  const minute = dateTimeString.substring(12, 14);
 | 
			
		||||
	  const second = dateTimeString.substring(14, 16);
 | 
			
		||||
 | 
			
		||||
	  return year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second;
 | 
			
		||||
	}
 | 
			
		||||
  
 | 
			
		||||
    function addExtra() {
 | 
			
		||||
         const event = document.getElementById("customModeCheckbox");
 | 
			
		||||
        
 | 
			
		||||
    
 | 
			
		||||
        if (event.checked && lastPDFFile.Custom != null) {
 | 
			
		||||
          customMetadataDiv.style.display = 'block';
 | 
			
		||||
          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') {
 | 
			
		||||
              continue;
 | 
			
		||||
            }
 | 
			
		||||
            const entryDiv = document.createElement('div');
 | 
			
		||||
            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>`;
 | 
			
		||||
            otherMetadataEntriesDiv.appendChild(entryDiv);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          customMetadataDiv.style.display = 'none';
 | 
			
		||||
          while (otherMetadataEntriesDiv.firstChild) {
 | 
			
		||||
            otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
  customModeCheckbox.addEventListener('change', (event) => {
 | 
			
		||||
     
 | 
			
		||||
      addExtra();
 | 
			
		||||
      });
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
                        </form>
 | 
			
		||||
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
@ -1,190 +1,190 @@
 | 
			
		||||
<!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=#{compare.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-9">
 | 
			
		||||
                        <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='fileInput2', multiple=false, accept='application/pdf')}"></div>
 | 
			
		||||
                        <button class="btn btn-primary"  onclick="comparePDFs()" th:text="#{compare.submit}"></button>
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
                        <div class="row">
 | 
			
		||||
                            <div class="col-md-6">
 | 
			
		||||
                                <h3 th:text="#{compare.document.1}"></h3>
 | 
			
		||||
                                <div id="result1" class="result-column"></div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-6">
 | 
			
		||||
                                <h3 th:text="#{compare.document.2}"></h3>
 | 
			
		||||
                                <div id="result2" class="result-column"></div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <style>
 | 
			
		||||
                            .result-column {
 | 
			
		||||
                                border: 1px solid #ccc;
 | 
			
		||||
                                padding: 15px;
 | 
			
		||||
                                overflow-y: auto;
 | 
			
		||||
                                height: calc(100vh - 400px);
 | 
			
		||||
                                white-space: pre-wrap;
 | 
			
		||||
                            }
 | 
			
		||||
                        </style>
 | 
			
		||||
                        <script>
 | 
			
		||||
                        // get the elements
 | 
			
		||||
                        var result1 = document.getElementById('result1');
 | 
			
		||||
                        var result2 = document.getElementById('result2');
 | 
			
		||||
 | 
			
		||||
                        // add event listeners
 | 
			
		||||
                        result1.addEventListener('scroll', function() {
 | 
			
		||||
                            result2.scrollTop = result1.scrollTop;
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        result2.addEventListener('scroll', function() {
 | 
			
		||||
                            result1.scrollTop = result2.scrollTop;
 | 
			
		||||
                        });
 | 
			
		||||
                        
 | 
			
		||||
                        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(" ");
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
                            const [text1, text2] = await Promise.all([
 | 
			
		||||
                                extractText(pdf1),
 | 
			
		||||
                                extractText(pdf2)
 | 
			
		||||
                            ]);
 | 
			
		||||
 | 
			
		||||
                            if (text1.trim() === "" || text2.trim() === "") {
 | 
			
		||||
                                alert("One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.");
 | 
			
		||||
                                return;
 | 
			
		||||
                            }
 | 
			
		||||
                            const diff = (text1, text2) => {
 | 
			
		||||
                                const words1 = text1.split(' ');
 | 
			
		||||
                                const words2 = text2.split(' ');
 | 
			
		||||
 | 
			
		||||
                                // Create a 2D array to hold our "matrix"
 | 
			
		||||
                                const matrix = Array(words1.length + 1).fill(null).map(() => Array(words2.length + 1).fill(0));
 | 
			
		||||
 | 
			
		||||
                                // Perform standard LCS algorithm
 | 
			
		||||
                                for (let i = 1; i <= words1.length; i++) {
 | 
			
		||||
                                    for (let j = 1; j <= words2.length; j++) {
 | 
			
		||||
                                        if (words1[i - 1] === words2[j - 1]) {
 | 
			
		||||
                                            matrix[i][j] = matrix[i - 1][j - 1] + 1;
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            matrix[i][j] = Math.max(matrix[i][j - 1], matrix[i - 1][j]);
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                let i = words1.length;
 | 
			
		||||
                                let j = words2.length;
 | 
			
		||||
                                const differences = [];
 | 
			
		||||
 | 
			
		||||
                                // Backtrack through the matrix to create the diff
 | 
			
		||||
                                while (i > 0 || j > 0) {
 | 
			
		||||
                                    if (i > 0 && j > 0 && words1[i - 1] === words2[j - 1]) {
 | 
			
		||||
                                        differences.unshift(['black', words1[i - 1]]);
 | 
			
		||||
                                        i--;
 | 
			
		||||
                                        j--;
 | 
			
		||||
                                    } else if (j > 0 && (i === 0 || matrix[i][j - 1] >= matrix[i - 1][j])) {
 | 
			
		||||
                                        differences.unshift(['green', words2[j - 1]]);
 | 
			
		||||
                                        j--;
 | 
			
		||||
                                    } else if (i > 0 && (j === 0 || matrix[i][j - 1] < matrix[i - 1][j])) {
 | 
			
		||||
                                        differences.unshift(['red', words1[i - 1]]);
 | 
			
		||||
                                        i--;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                return differences;
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                            const differences = diff(text1, text2);
 | 
			
		||||
 | 
			
		||||
                            const displayDifferences = (differences) => {
 | 
			
		||||
                                const resultDiv1 = document.getElementById("result1");
 | 
			
		||||
                                const resultDiv2 = document.getElementById("result2");
 | 
			
		||||
                                resultDiv1.innerHTML = "";
 | 
			
		||||
                                resultDiv2.innerHTML = "";
 | 
			
		||||
 | 
			
		||||
                                differences.forEach(([color, word]) => {
 | 
			
		||||
                                    const span1 = 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 (color === "green") {
 | 
			
		||||
                                        span1.style.color = "transparent";
 | 
			
		||||
                                        span1.style.userSelect = "none";
 | 
			
		||||
                                        span2.style.color = color;
 | 
			
		||||
                                    } 
 | 
			
		||||
                                    // If it's a deletion, show it in red in the first document and transparent in the second
 | 
			
		||||
                                    else if (color === "red") {
 | 
			
		||||
                                        span1.style.color = color;
 | 
			
		||||
                                        span2.style.color = "transparent";
 | 
			
		||||
                                        span2.style.userSelect = "none";
 | 
			
		||||
                                    } 
 | 
			
		||||
                                    // If it's unchanged, show it in black in both
 | 
			
		||||
                                    else {
 | 
			
		||||
                                        span1.style.color = color;
 | 
			
		||||
                                        span2.style.color = color;
 | 
			
		||||
                                    }
 | 
			
		||||
 | 
			
		||||
                                    span1.textContent = word;
 | 
			
		||||
                                    span2.textContent = word;
 | 
			
		||||
                                    resultDiv1.appendChild(span1);
 | 
			
		||||
                                    resultDiv2.appendChild(span2);
 | 
			
		||||
 | 
			
		||||
                                    // Add space after each word, or a new line if the word ends with a full stop
 | 
			
		||||
                                    const spaceOrNewline1 = document.createElement("span");
 | 
			
		||||
                                    const spaceOrNewline2 = document.createElement("span");
 | 
			
		||||
                                    if (word.endsWith(".")) {
 | 
			
		||||
                                        spaceOrNewline1.innerHTML = "<br>";
 | 
			
		||||
                                        spaceOrNewline2.innerHTML = "<br>";
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        spaceOrNewline1.textContent = " ";
 | 
			
		||||
                                        spaceOrNewline2.textContent = " ";
 | 
			
		||||
                                    }
 | 
			
		||||
                                    resultDiv1.appendChild(spaceOrNewline1);
 | 
			
		||||
                                    resultDiv2.appendChild(spaceOrNewline2);
 | 
			
		||||
                                });
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
                            console.log('Differences:', differences);
 | 
			
		||||
                            displayDifferences(differences);
 | 
			
		||||
                        }
 | 
			
		||||
                        </script>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
<!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=#{compare.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-9">
 | 
			
		||||
                        <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='fileInput2', multiple=false, accept='application/pdf')}"></div>
 | 
			
		||||
                        <button class="btn btn-primary"  onclick="comparePDFs()" th:text="#{compare.submit}"></button>
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
                        <div class="row">
 | 
			
		||||
                            <div class="col-md-6">
 | 
			
		||||
                                <h3 th:text="#{compare.document.1}"></h3>
 | 
			
		||||
                                <div id="result1" class="result-column"></div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-6">
 | 
			
		||||
                                <h3 th:text="#{compare.document.2}"></h3>
 | 
			
		||||
                                <div id="result2" class="result-column"></div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <style>
 | 
			
		||||
                            .result-column {
 | 
			
		||||
                                border: 1px solid #ccc;
 | 
			
		||||
                                padding: 15px;
 | 
			
		||||
                                overflow-y: auto;
 | 
			
		||||
                                height: calc(100vh - 400px);
 | 
			
		||||
                                white-space: pre-wrap;
 | 
			
		||||
                            }
 | 
			
		||||
                        </style>
 | 
			
		||||
                        <script>
 | 
			
		||||
                        // get the elements
 | 
			
		||||
                        var result1 = document.getElementById('result1');
 | 
			
		||||
                        var result2 = document.getElementById('result2');
 | 
			
		||||
 | 
			
		||||
                        // add event listeners
 | 
			
		||||
                        result1.addEventListener('scroll', function() {
 | 
			
		||||
                            result2.scrollTop = result1.scrollTop;
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        result2.addEventListener('scroll', function() {
 | 
			
		||||
                            result1.scrollTop = result2.scrollTop;
 | 
			
		||||
                        });
 | 
			
		||||
                        
 | 
			
		||||
                        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;
 | 
			
		||||
                            }
 | 
			
		||||
                            pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
 | 
			
		||||
                            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(" ");
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
                            const [text1, text2] = await Promise.all([
 | 
			
		||||
                                extractText(pdf1),
 | 
			
		||||
                                extractText(pdf2)
 | 
			
		||||
                            ]);
 | 
			
		||||
 | 
			
		||||
                            if (text1.trim() === "" || text2.trim() === "") {
 | 
			
		||||
                                alert("One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.");
 | 
			
		||||
                                return;
 | 
			
		||||
                            }
 | 
			
		||||
                            const diff = (text1, text2) => {
 | 
			
		||||
                                const words1 = text1.split(' ');
 | 
			
		||||
                                const words2 = text2.split(' ');
 | 
			
		||||
 | 
			
		||||
                                // Create a 2D array to hold our "matrix"
 | 
			
		||||
                                const matrix = Array(words1.length + 1).fill(null).map(() => Array(words2.length + 1).fill(0));
 | 
			
		||||
 | 
			
		||||
                                // Perform standard LCS algorithm
 | 
			
		||||
                                for (let i = 1; i <= words1.length; i++) {
 | 
			
		||||
                                    for (let j = 1; j <= words2.length; j++) {
 | 
			
		||||
                                        if (words1[i - 1] === words2[j - 1]) {
 | 
			
		||||
                                            matrix[i][j] = matrix[i - 1][j - 1] + 1;
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            matrix[i][j] = Math.max(matrix[i][j - 1], matrix[i - 1][j]);
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                let i = words1.length;
 | 
			
		||||
                                let j = words2.length;
 | 
			
		||||
                                const differences = [];
 | 
			
		||||
 | 
			
		||||
                                // Backtrack through the matrix to create the diff
 | 
			
		||||
                                while (i > 0 || j > 0) {
 | 
			
		||||
                                    if (i > 0 && j > 0 && words1[i - 1] === words2[j - 1]) {
 | 
			
		||||
                                        differences.unshift(['black', words1[i - 1]]);
 | 
			
		||||
                                        i--;
 | 
			
		||||
                                        j--;
 | 
			
		||||
                                    } else if (j > 0 && (i === 0 || matrix[i][j - 1] >= matrix[i - 1][j])) {
 | 
			
		||||
                                        differences.unshift(['green', words2[j - 1]]);
 | 
			
		||||
                                        j--;
 | 
			
		||||
                                    } else if (i > 0 && (j === 0 || matrix[i][j - 1] < matrix[i - 1][j])) {
 | 
			
		||||
                                        differences.unshift(['red', words1[i - 1]]);
 | 
			
		||||
                                        i--;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                return differences;
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                            const differences = diff(text1, text2);
 | 
			
		||||
 | 
			
		||||
                            const displayDifferences = (differences) => {
 | 
			
		||||
                                const resultDiv1 = document.getElementById("result1");
 | 
			
		||||
                                const resultDiv2 = document.getElementById("result2");
 | 
			
		||||
                                resultDiv1.innerHTML = "";
 | 
			
		||||
                                resultDiv2.innerHTML = "";
 | 
			
		||||
 | 
			
		||||
                                differences.forEach(([color, word]) => {
 | 
			
		||||
                                    const span1 = 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 (color === "green") {
 | 
			
		||||
                                        span1.style.color = "transparent";
 | 
			
		||||
                                        span1.style.userSelect = "none";
 | 
			
		||||
                                        span2.style.color = color;
 | 
			
		||||
                                    } 
 | 
			
		||||
                                    // If it's a deletion, show it in red in the first document and transparent in the second
 | 
			
		||||
                                    else if (color === "red") {
 | 
			
		||||
                                        span1.style.color = color;
 | 
			
		||||
                                        span2.style.color = "transparent";
 | 
			
		||||
                                        span2.style.userSelect = "none";
 | 
			
		||||
                                    } 
 | 
			
		||||
                                    // If it's unchanged, show it in black in both
 | 
			
		||||
                                    else {
 | 
			
		||||
                                        span1.style.color = color;
 | 
			
		||||
                                        span2.style.color = color;
 | 
			
		||||
                                    }
 | 
			
		||||
 | 
			
		||||
                                    span1.textContent = word;
 | 
			
		||||
                                    span2.textContent = word;
 | 
			
		||||
                                    resultDiv1.appendChild(span1);
 | 
			
		||||
                                    resultDiv2.appendChild(span2);
 | 
			
		||||
 | 
			
		||||
                                    // Add space after each word, or a new line if the word ends with a full stop
 | 
			
		||||
                                    const spaceOrNewline1 = document.createElement("span");
 | 
			
		||||
                                    const spaceOrNewline2 = document.createElement("span");
 | 
			
		||||
                                    if (word.endsWith(".")) {
 | 
			
		||||
                                        spaceOrNewline1.innerHTML = "<br>";
 | 
			
		||||
                                        spaceOrNewline2.innerHTML = "<br>";
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        spaceOrNewline1.textContent = " ";
 | 
			
		||||
                                        spaceOrNewline2.textContent = " ";
 | 
			
		||||
                                    }
 | 
			
		||||
                                    resultDiv1.appendChild(spaceOrNewline1);
 | 
			
		||||
                                    resultDiv2.appendChild(spaceOrNewline2);
 | 
			
		||||
                                });
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
                            console.log('Differences:', differences);
 | 
			
		||||
                            displayDifferences(differences);
 | 
			
		||||
                        }
 | 
			
		||||
                        </script>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
@ -1,132 +1,132 @@
 | 
			
		||||
<!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=#{rotate.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="#{rotate.header}"></h2>
 | 
			
		||||
 | 
			
		||||
                        <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>
 | 
			
		||||
                            <input type="hidden" id="angleInput" name="angle" value="0">
 | 
			
		||||
 | 
			
		||||
                            <div id="editSection" style="display: none">
 | 
			
		||||
                                <div class="previewContainer">
 | 
			
		||||
                                    <img id="pdf-preview" />
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <div class="buttonContainer">
 | 
			
		||||
                                    <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">
 | 
			
		||||
                    						<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" />
 | 
			
		||||
                    					</svg>
 | 
			
		||||
                                    </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)">
 | 
			
		||||
                                        <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 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>
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </form>
 | 
			
		||||
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
		const angleInput = document.getElementById("angleInput");
 | 
			
		||||
		const fileInput = document.getElementById("fileInput-input");
 | 
			
		||||
		const preview = document.getElementById("pdf-preview");
 | 
			
		||||
		fileInput.addEventListener("change", async function() {
 | 
			
		||||
			console.log("loading pdf");
 | 
			
		||||
 | 
			
		||||
			document.querySelector("#editSection").style.display = "";
 | 
			
		||||
 | 
			
		||||
			var url = URL.createObjectURL(fileInput.files[0])
 | 
			
		||||
 | 
			
		||||
			const pdf = await pdfjsLib.getDocument(url).promise;
 | 
			
		||||
			const page = await pdf.getPage(1);
 | 
			
		||||
 | 
			
		||||
			const canvas = document.createElement("canvas");
 | 
			
		||||
 | 
			
		||||
			// set the canvas size to the size of the page
 | 
			
		||||
			if (page.rotate == 90 || page.rotate == 270) {
 | 
			
		||||
				canvas.width = page.view[3];
 | 
			
		||||
				canvas.height = page.view[2];
 | 
			
		||||
			} else {
 | 
			
		||||
				canvas.width = page.view[2];
 | 
			
		||||
				canvas.height = page.view[3];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// render the page onto the canvas
 | 
			
		||||
			var renderContext = {
 | 
			
		||||
			   	canvasContext: canvas.getContext("2d"),
 | 
			
		||||
			  	viewport: page.getViewport({ scale: 1 })
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			await page.render(renderContext).promise;
 | 
			
		||||
			preview.src = canvas.toDataURL();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		function rotate(deg) {
 | 
			
		||||
			var lastTransform = preview.style.rotate;
 | 
			
		||||
			if (!lastTransform) {
 | 
			
		||||
				lastTransform = "0";
 | 
			
		||||
			}
 | 
			
		||||
			const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ''));
 | 
			
		||||
			const newAngle = lastAngle + deg;
 | 
			
		||||
 | 
			
		||||
			preview.style.rotate = newAngle + "deg";
 | 
			
		||||
			angleInput.value = newAngle;
 | 
			
		||||
		}
 | 
			
		||||
	</script>
 | 
			
		||||
    <style>
 | 
			
		||||
#pdf-preview {
 | 
			
		||||
	margin: 0 auto;
 | 
			
		||||
	display: block;
 | 
			
		||||
	max-width: calc(100% - 30px);
 | 
			
		||||
	max-height: calc(100% - 30px);
 | 
			
		||||
	box-shadow: 0 0 4px rgba(100, 100, 100, .25);
 | 
			
		||||
	transition: rotate .3s;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 50%;
 | 
			
		||||
	left: 50%;
 | 
			
		||||
	translate: -50% -50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.previewContainer {
 | 
			
		||||
	aspect-ratio: 1;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	border: 1px solid rgba(0, 0, 0, .125);
 | 
			
		||||
	border-radius: 0.25rem;
 | 
			
		||||
	margin: 1rem 0;
 | 
			
		||||
	padding: 15px;
 | 
			
		||||
	display: block;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.buttonContainer {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	justify-content: space-around;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
<!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=#{rotate.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="#{rotate.header}"></h2>
 | 
			
		||||
 | 
			
		||||
                        <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>
 | 
			
		||||
                            <input type="hidden" id="angleInput" name="angle" value="0">
 | 
			
		||||
 | 
			
		||||
                            <div id="editSection" style="display: none">
 | 
			
		||||
                                <div class="previewContainer">
 | 
			
		||||
                                    <img id="pdf-preview" />
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <div class="buttonContainer">
 | 
			
		||||
                                    <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">
 | 
			
		||||
                    						<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" />
 | 
			
		||||
                    					</svg>
 | 
			
		||||
                                    </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)">
 | 
			
		||||
                                        <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 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>
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </form>
 | 
			
		||||
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
		const angleInput = document.getElementById("angleInput");
 | 
			
		||||
		const fileInput = document.getElementById("fileInput-input");
 | 
			
		||||
		const preview = document.getElementById("pdf-preview");
 | 
			
		||||
		fileInput.addEventListener("change", async function() {
 | 
			
		||||
			console.log("loading pdf");
 | 
			
		||||
 | 
			
		||||
			document.querySelector("#editSection").style.display = "";
 | 
			
		||||
 | 
			
		||||
			var url = URL.createObjectURL(fileInput.files[0])
 | 
			
		||||
			pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
 | 
			
		||||
			const pdf = await pdfjsLib.getDocument(url).promise;
 | 
			
		||||
			const page = await pdf.getPage(1);
 | 
			
		||||
 | 
			
		||||
			const canvas = document.createElement("canvas");
 | 
			
		||||
 | 
			
		||||
			// set the canvas size to the size of the page
 | 
			
		||||
			if (page.rotate == 90 || page.rotate == 270) {
 | 
			
		||||
				canvas.width = page.view[3];
 | 
			
		||||
				canvas.height = page.view[2];
 | 
			
		||||
			} else {
 | 
			
		||||
				canvas.width = page.view[2];
 | 
			
		||||
				canvas.height = page.view[3];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// render the page onto the canvas
 | 
			
		||||
			var renderContext = {
 | 
			
		||||
			   	canvasContext: canvas.getContext("2d"),
 | 
			
		||||
			  	viewport: page.getViewport({ scale: 1 })
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			await page.render(renderContext).promise;
 | 
			
		||||
			preview.src = canvas.toDataURL();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		function rotate(deg) {
 | 
			
		||||
			var lastTransform = preview.style.rotate;
 | 
			
		||||
			if (!lastTransform) {
 | 
			
		||||
				lastTransform = "0";
 | 
			
		||||
			}
 | 
			
		||||
			const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ''));
 | 
			
		||||
			const newAngle = lastAngle + deg;
 | 
			
		||||
 | 
			
		||||
			preview.style.rotate = newAngle + "deg";
 | 
			
		||||
			angleInput.value = newAngle;
 | 
			
		||||
		}
 | 
			
		||||
	</script>
 | 
			
		||||
    <style>
 | 
			
		||||
#pdf-preview {
 | 
			
		||||
	margin: 0 auto;
 | 
			
		||||
	display: block;
 | 
			
		||||
	max-width: calc(100% - 30px);
 | 
			
		||||
	max-height: calc(100% - 30px);
 | 
			
		||||
	box-shadow: 0 0 4px rgba(100, 100, 100, .25);
 | 
			
		||||
	transition: rotate .3s;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 50%;
 | 
			
		||||
	left: 50%;
 | 
			
		||||
	translate: -50% -50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.previewContainer {
 | 
			
		||||
	aspect-ratio: 1;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	border: 1px solid rgba(0, 0, 0, .125);
 | 
			
		||||
	border-radius: 0.25rem;
 | 
			
		||||
	margin: 1rem 0;
 | 
			
		||||
	padding: 15px;
 | 
			
		||||
	display: block;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.buttonContainer {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	justify-content: space-around;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
@ -47,6 +47,7 @@ select#font-select, select#font-select option {
 | 
			
		||||
                                if (file) {
 | 
			
		||||
                                	originalFileName = file.name.replace(/\.[^/.]+$/, "");
 | 
			
		||||
                                    const pdfData = await file.arrayBuffer();
 | 
			
		||||
                                    pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
 | 
			
		||||
                                    const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
 | 
			
		||||
                                    await DraggableUtils.renderPage(pdfDoc, 0);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user