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…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
-
-
-
+
+
+ PDF Booklet Creator
+ Create print-ready booklets from your PDF files
+
+
+
+
+
+
+
+
+
+
Drag & drop a PDF file here or click to select
+
+
+
+
+
PDF Information
+
Filename:
+
+
+
+
Paper Sheets Required:
+
+
+
+
+
Booklet Size Recommendations
+
+
+
+ | Booklet Size |
+ Number of Booklets |
+ Last Booklet Size |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Click "Preview" to see the imposed page order.
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+