diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..de7fe82 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,56 @@ +/* Custom Styles */ +#drop-area { + cursor: pointer; + transition: background-color 0.2s ease-in-out; +} + +#drop-area.drag-over { + background-color: #e9ecef; +} + +/* Imposition Preview Styles */ +.sheet-preview { + display: flex; + justify-content: space-around; + border: 1px solid #dee2e6; + border-radius: 5px; + padding: 10px; + margin-bottom: 15px; + background-color: #fff; + box-shadow: 0 4px 8px rgba(0,0,0,0.05); +} + +.sheet-side { + width: 48%; + border: 1px dashed #6c757d; + padding: 10px; + text-align: center; +} + +.page-preview { + display: inline-block; + width: 45%; + height: 100px; + line-height: 100px; + border: 1px solid #ced4da; + margin: 5px; + background-color: #e9ecef; + font-weight: bold; + text-align: center; + vertical-align: middle; +} + +.blank-page { + background-color: #6c757d; + color: #fff; +} + +#recommendation-table th, +#recommendation-table td { + padding: 0.25rem 0.5rem; +} + +#drop-area.loaded { + padding: 1rem !important; + min-height: auto; +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..bf280a6 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,301 @@ +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 += `
Booklet ${index + 1}
`; + for (let i = 0; i < booklet.length; i += 4) { + const sheetPages = booklet.slice(i, i + 4); + html += ` +
+
+ Front +
${sheetPages[0].type === 'blank' ? 'BLANK' : 'Page ' + sheetPages[0].pageNum}
+
${sheetPages[1].type === 'blank' ? 'BLANK' : 'Page ' + sheetPages[1].pageNum}
+
+
+ Back +
${sheetPages[2].type === 'blank' ? 'BLANK' : 'Page ' + sheetPages[2].pageNum}
+
${sheetPages[3].type === 'blank' ? 'BLANK' : 'Page ' + sheetPages[3].pageNum}
+
+
+ `; + } + }); + + 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 = ' 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 = ' 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 = ` + + ${size} + ${numBooklets} + ${lastBookletSize} + + `; + tableBody.innerHTML += row; + + if (numBooklets === 1) { + break; // Stop after the first size that fits all pages + } + } + } +}); \ No newline at end of file diff --git a/index.php b/index.php index 7205f3d..ab5b4f6 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,124 @@ - - + - - - New Style - - - - - - - - - - - - - - - - - - - + + + PDF Booklet Creator + + + + + + + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

-
-
- + +
+

PDF Booklet Creator

+

Create print-ready booklets from your PDF files

+
+ +
+
+
+
+
+ PDF Import +
+
+
+

Drag & drop a PDF file here or click to select

+ + +
+
+
PDF Information
+

Filename:

+
+
+

Page Count:

+
+
+

Paper Sheets Required:

+
+
+
+
+
Booklet Size Recommendations
+ + + + + + + + + + + +
Booklet SizeNumber of BookletsLast Booklet Size
+
+
+
+
+
+
+
+ Booklet Settings +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+
+
+
+
+ +
+
+
+
+ Imposition Preview +
+
+
+

Click "Preview" to see the imposed page order.

+
+
+
+
+
+
+ + + + + + + - + \ No newline at end of file