35615-vm/assets/js/main.js
2025-11-10 14:57:50 +00:00

301 lines
12 KiB
JavaScript

document.addEventListener('DOMContentLoaded', function () {
const { PDFDocument } = PDFLib;
const dropArea = document.getElementById('drop-area');
const fileInput = document.getElementById('file-input');
const fileSelectBtn = document.getElementById('file-select-btn');
const pdfInfo = document.getElementById('pdf-info');
const pdfFilename = document.getElementById('pdf-filename');
const pdfPageCount = document.getElementById('pdf-page-count');
const pdfPaperCount = document.getElementById('pdf-paper-count');
const previewBtn = document.getElementById('preview-btn');
const exportSingleBtn = document.getElementById('export-single-btn');
const exportZipBtn = document.getElementById('export-zip-btn');
const bookletSizeInput = document.getElementById('booklet-size');
const firstBookletSizeInput = document.getElementById('first-booklet-size');
const blankPagesInput = document.getElementById('blank-pages-start');
let originalPdfBytes = null;
let originalPageCount = 0;
// Event Listeners
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
['dragenter', 'dragover'].forEach(eventName => dropArea.addEventListener(eventName, () => dropArea.classList.add('drag-over'), false));
['dragleave', 'drop'].forEach(eventName => dropArea.addEventListener(eventName, () => dropArea.classList.remove('drag-over'), false));
dropArea.addEventListener('drop', handleDrop, false);
fileSelectBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', handleFileSelect, false);
previewBtn.addEventListener('click', updatePreview);
exportSingleBtn.addEventListener('click', exportAsSinglePdf);
exportZipBtn.addEventListener('click', exportAsZip);
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
function handleDrop(e) {
handleFiles(e.dataTransfer.files);
}
function handleFileSelect(e) {
handleFiles(e.target.files);
}
async function handleFiles(files) {
if (files.length > 0) {
const file = files[0];
if (file.type === 'application/pdf') {
pdfFilename.textContent = file.name;
dropArea.classList.add('loaded');
dropArea.querySelector('p').textContent = 'Drag & drop or click to select a different PDF.';
const fileReader = new FileReader();
fileReader.onload = async function() {
originalPdfBytes = new Uint8Array(this.result);
try {
const pdfDoc = await PDFDocument.load(originalPdfBytes);
originalPageCount = pdfDoc.getPageCount();
pdfPageCount.textContent = originalPageCount;
const paperCount = Math.ceil(originalPageCount / 4);
pdfPaperCount.textContent = paperCount;
pdfInfo.classList.remove('d-none');
document.getElementById('recommendation-section').classList.remove('d-none');
previewBtn.disabled = false;
exportSingleBtn.disabled = false;
exportZipBtn.disabled = false;
updateRecommendationTable();
updatePreview(); // Auto-update preview on new file
} catch (e) {
console.error(e);
alert('Failed to load PDF. The file may be corrupt.');
}
};
fileReader.readAsArrayBuffer(file);
} else {
alert('Please select a PDF file.');
}
}
}
function padAndImposeBooklet(bookletPages) {
const paddedBookletSize = Math.ceil(bookletPages.length / 4) * 4;
while (bookletPages.length < paddedBookletSize) {
bookletPages.push({ type: 'blank' });
}
return imposeBooklet(bookletPages);
}
function calculateImposition() {
const bookletSize = parseInt(bookletSizeInput.value, 10);
let firstBookletSize = parseInt(firstBookletSizeInput.value, 10) || bookletSize;
const blankPagesAtStart = parseInt(blankPagesInput.value, 10) || 0;
if (bookletSize % 4 !== 0 || (firstBookletSize && firstBookletSize % 4 !== 0)) {
alert('Booklet sizes must be a multiple of 4.');
return null;
}
const totalPages = originalPageCount + blankPagesAtStart;
let sourcePages = [];
for (let i = 0; i < blankPagesAtStart; i++) sourcePages.push({ type: 'blank' });
for (let i = 1; i <= originalPageCount; i++) sourcePages.push({ type: 'pdf', pageNum: i });
let booklets = [];
// First booklet
let firstBookletActualSize = (firstBookletSize > 0 && totalPages > 0) ? firstBookletSize : bookletSize;
if (totalPages < firstBookletSize) {
firstBookletActualSize = Math.ceil(totalPages / 4) * 4;
}
const firstBookletPages = sourcePages.splice(0, firstBookletActualSize);
booklets.push(padAndImposeBooklet(firstBookletPages));
// Remaining booklets
while (sourcePages.length > 0) {
const bookletPages = sourcePages.splice(0, bookletSize);
booklets.push(padAndImposeBooklet(bookletPages));
}
return booklets;
}
function imposeBooklet(bookletPages) {
const size = bookletPages.length;
const imposed = [];
for (let i = 0; i < size / 2; i += 2) {
imposed.push(bookletPages[size - 1 - i]);
imposed.push(bookletPages[i]);
imposed.push(bookletPages[i + 1]);
imposed.push(bookletPages[size - 2 - i]);
}
return imposed;
}
function updatePreview() {
const booklets = calculateImposition();
if (!booklets) return;
const previewArea = document.getElementById('imposition-preview');
let html = '';
booklets.forEach((booklet, index) => {
html += `<h5 class="mt-3">Booklet ${index + 1}</h5>`;
for (let i = 0; i < booklet.length; i += 4) {
const sheetPages = booklet.slice(i, i + 4);
html += `
<div class="sheet-preview">
<div class="sheet-side">
<strong>Front</strong>
<div class="page-preview ${sheetPages[0].type === 'blank' ? 'blank-page' : ''}">${sheetPages[0].type === 'blank' ? 'BLANK' : 'Page ' + sheetPages[0].pageNum}</div>
<div class="page-preview ${sheetPages[1].type === 'blank' ? 'blank-page' : ''}">${sheetPages[1].type === 'blank' ? 'BLANK' : 'Page ' + sheetPages[1].pageNum}</div>
</div>
<div class="sheet-side">
<strong>Back</strong>
<div class="page-preview ${sheetPages[2].type === 'blank' ? 'blank-page' : ''}">${sheetPages[2].type === 'blank' ? 'BLANK' : 'Page ' + sheetPages[2].pageNum}</div>
<div class="page-preview ${sheetPages[3].type === 'blank' ? 'blank-page' : ''}">${sheetPages[3].type === 'blank' ? 'BLANK' : 'Page ' + sheetPages[3].pageNum}</div>
</div>
</div>
`;
}
});
previewArea.innerHTML = html;
}
async function createBookletPdf(booklet, srcDoc) {
const { width, height } = srcDoc.getPage(0).getSize();
const newDoc = await PDFDocument.create();
for (const pageInfo of booklet) {
if (pageInfo.type === 'pdf') {
const [copiedPage] = await newDoc.copyPages(srcDoc, [pageInfo.pageNum - 1]);
newDoc.addPage(copiedPage);
} else {
newDoc.addPage([width, height]);
}
}
return newDoc;
}
async function exportAsSinglePdf() {
if (!originalPdfBytes) {
alert('Please upload a PDF first.');
return;
}
exportSingleBtn.disabled = true;
exportSingleBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Exporting...';
const booklets = calculateImposition();
if (!booklets) {
exportSingleBtn.disabled = false;
exportSingleBtn.textContent = 'Export as Single PDF';
return;
}
try {
const srcDoc = await PDFDocument.load(originalPdfBytes);
const mergedPdf = await PDFDocument.create();
for (const booklet of booklets) {
const bookletPdf = await createBookletPdf(booklet, srcDoc);
const copiedPages = await mergedPdf.copyPages(bookletPdf, bookletPdf.getPageIndices());
copiedPages.forEach((page) => mergedPdf.addPage(page));
}
const pdfBytes = await mergedPdf.save();
download(pdfBytes, `imposed-booklet.pdf`, "application/pdf");
} catch (e) {
console.error(e);
alert('An error occurred while exporting the PDF.');
} finally {
exportSingleBtn.disabled = false;
exportSingleBtn.textContent = 'Export as Single PDF';
}
}
async function exportAsZip() {
if (!originalPdfBytes) {
alert('Please upload a PDF first.');
return;
}
exportZipBtn.disabled = true;
exportZipBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Exporting...';
const booklets = calculateImposition();
if (!booklets) {
exportZipBtn.disabled = false;
exportZipBtn.textContent = 'Export as ZIP';
return;
}
try {
const srcDoc = await PDFDocument.load(originalPdfBytes);
const zip = new JSZip();
for (let i = 0; i < booklets.length; i++) {
const booklet = booklets[i];
const bookletPdf = await createBookletPdf(booklet, srcDoc);
const pdfBytes = await bookletPdf.save();
zip.file(`booklet-${i + 1}.pdf`, pdfBytes);
}
const zipBytes = await zip.generateAsync({ type: "blob" });
download(zipBytes, 'booklets.zip', 'application/zip');
} catch (e) {
console.error(e);
alert('An error occurred while exporting the ZIP file.');
} finally {
exportZipBtn.disabled = false;
exportZipBtn.textContent = 'Export as ZIP';
}
}
function download(data, filename, type) {
const blob = new Blob([data], { type: type });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
function updateRecommendationTable() {
if (originalPageCount === 0) return;
const tableBody = document.getElementById('recommendation-table-body');
tableBody.innerHTML = ''; // Clear existing rows
const totalPages = originalPageCount + (parseInt(blankPagesInput.value, 10) || 0);
for (let size = 4; size <= 32; size += 4) {
const numBooklets = Math.ceil(totalPages / size);
const lastBookletSize = totalPages % size || size;
const row = `
<tr>
<td>${size}</td>
<td>${numBooklets}</td>
<td>${lastBookletSize}</td>
</tr>
`;
tableBody.innerHTML += row;
if (numBooklets === 1) {
break; // Stop after the first size that fits all pages
}
}
}
});