Hood-CS commited on
Commit
0853b0a
Β·
verified Β·
1 Parent(s): 9269625

You are an expert in image and PDF manipulation,

Browse files

Context

I have a portrait of a document. I want it to be 2 of them in a landscape (I want the 2 pages on the same landscape page)

Task

Write a webpage that can do such, with a given PDF, and give a new pdf.

Answer order:
Logic of the code
Tree structure of the file names.
The content of each file

Files changed (5) hide show
  1. README.md +7 -4
  2. components/pdf-preview.js +37 -0
  3. index.html +91 -19
  4. script.js +138 -0
  5. style.css +15 -21
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Pdf Portrait Duplicator
3
- emoji: πŸ†
4
  colorFrom: red
5
- colorTo: green
 
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: PDF Portrait Duplicator πŸ“„βœ¨
 
3
  colorFrom: red
4
+ colorTo: red
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
components/pdf-preview.js ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class PdfPreview extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ .preview-container {
7
+ border: 2px dashed #e2e8f0;
8
+ border-radius: 0.5rem;
9
+ padding: 1rem;
10
+ background-color: #f8fafc;
11
+ text-align: center;
12
+ transition: all 0.2s;
13
+ }
14
+ .preview-container:hover {
15
+ border-color: #818cf8;
16
+ }
17
+ .preview-text {
18
+ color: #64748b;
19
+ margin-bottom: 0.5rem;
20
+ }
21
+ .preview-image {
22
+ max-width: 100%;
23
+ max-height: 300px;
24
+ margin-top: 1rem;
25
+ border-radius: 0.25rem;
26
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
27
+ }
28
+ </style>
29
+ <div class="preview-container">
30
+ <p class="preview-text">PDF preview will appear here</p>
31
+ <slot></slot>
32
+ </div>
33
+ `;
34
+ }
35
+ }
36
+
37
+ customElements.define('pdf-preview', PdfPreview);
index.html CHANGED
@@ -1,19 +1,91 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>PDF Portrait Duplicator</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/downloadjs@1.4.7"></script>
11
+ <script src="https://unpkg.com/feather-icons"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
13
+ </head>
14
+ <body class="bg-gray-100 min-h-screen">
15
+ <div class="container mx-auto px-4 py-12">
16
+ <header class="text-center mb-12">
17
+ <h1 class="text-4xl font-bold text-gray-800 mb-2">PDF Portrait Duplicator</h1>
18
+ <p class="text-gray-600">Convert single portrait pages into landscape with 2 pages side by side</p>
19
+ </header>
20
+
21
+ <main class="max-w-3xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-8">
22
+ <div class="mb-8">
23
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="pdf-input">
24
+ Upload PDF File
25
+ </label>
26
+ <div class="flex items-center justify-center w-full">
27
+ <label class="flex flex-col items-center justify-center w-full h-32 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100 transition">
28
+ <div class="flex flex-col items-center justify-center pt-5 pb-6">
29
+ <i data-feather="upload" class="w-8 h-8 text-gray-500 mb-3"></i>
30
+ <p class="mb-2 text-sm text-gray-500">Click to upload or drag and drop</p>
31
+ <p class="text-xs text-gray-500">PDF only</p>
32
+ </div>
33
+ <input id="pdf-input" type="file" class="hidden" accept=".pdf" />
34
+ </label>
35
+ </div>
36
+ </div>
37
+
38
+ <div class="mb-8">
39
+ <label class="block text-gray-700 text-sm font-bold mb-2">
40
+ Options
41
+ </label>
42
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
43
+ <div>
44
+ <label class="inline-flex items-center">
45
+ <input type="checkbox" id="reverse-order" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
46
+ <span class="ml-2 text-gray-700">Reverse page order</span>
47
+ </label>
48
+ </div>
49
+ <div>
50
+ <label class="inline-flex items-center">
51
+ <input type="checkbox" id="add-page-numbers" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
52
+ <span class="ml-2 text-gray-700">Add page numbers</span>
53
+ </label>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="flex justify-center">
59
+ <button id="process-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-6 rounded-lg shadow-md transition flex items-center">
60
+ <i data-feather="repeat" class="mr-2"></i>
61
+ Process PDF
62
+ </button>
63
+ </div>
64
+
65
+ <div id="preview-container" class="mt-8 hidden">
66
+ <h3 class="text-lg font-semibold text-gray-700 mb-4">Preview</h3>
67
+ <div id="preview" class="border border-gray-200 rounded-lg p-4 bg-gray-50">
68
+ <p class="text-gray-500 text-center">Preview will appear here</p>
69
+ </div>
70
+ </div>
71
+
72
+ <div id="download-container" class="mt-8 hidden">
73
+ <button id="download-btn" class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-6 rounded-lg shadow-md transition flex items-center justify-center">
74
+ <i data-feather="download" class="mr-2"></i>
75
+ Download Processed PDF
76
+ </button>
77
+ </div>
78
+ </main>
79
+
80
+ <footer class="mt-12 text-center text-gray-500 text-sm">
81
+ <p>Created with PDF-lib.js - Simple PDF manipulation in the browser</p>
82
+ </footer>
83
+ </div>
84
+
85
+ <script src="script.js"></script>
86
+ <script>
87
+ feather.replace();
88
+ </script>
89
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
90
+ </body>
91
+ </html>
script.js ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const pdfInput = document.getElementById('pdf-input');
3
+ const processBtn = document.getElementById('process-btn');
4
+ const downloadBtn = document.getElementById('download-btn');
5
+ const previewContainer = document.getElementById('preview-container');
6
+ const downloadContainer = document.getElementById('download-container');
7
+ const preview = document.getElementById('preview');
8
+ const reverseOrderCheckbox = document.getElementById('reverse-order');
9
+ const addPageNumbersCheckbox = document.getElementById('add-page-numbers');
10
+
11
+ let processedPdfBytes = null;
12
+
13
+ pdfInput.addEventListener('change', handleFileSelect);
14
+ processBtn.addEventListener('click', processPDF);
15
+ downloadBtn.addEventListener('click', downloadPDF);
16
+
17
+ async function handleFileSelect(event) {
18
+ const file = event.target.files[0];
19
+ if (!file) return;
20
+
21
+ previewContainer.classList.remove('hidden');
22
+ preview.innerHTML = '<p class="text-gray-500 text-center">Loading preview...</p>';
23
+
24
+ try {
25
+ const arrayBuffer = await file.arrayBuffer();
26
+ const pdfDoc = await PDFLib.PDFDocument.load(arrayBuffer);
27
+ const firstPage = pdfDoc.getPages()[0];
28
+
29
+ // Create a simple preview
30
+ const { width, height } = firstPage.getSize();
31
+ preview.innerHTML = `
32
+ <div class="text-center">
33
+ <p class="text-gray-700 mb-2"><strong>Original PDF Info</strong></p>
34
+ <p class="text-gray-600">Pages: ${pdfDoc.getPageCount()}</p>
35
+ <p class="text-gray-600">Dimensions: ${width.toFixed(0)} Γ— ${height.toFixed(0)}</p>
36
+ <p class="text-gray-600">Orientation: ${width > height ? 'Landscape' : 'Portrait'}</p>
37
+ </div>
38
+ `;
39
+ } catch (error) {
40
+ preview.innerHTML = '<p class="text-red-500 text-center">Error loading PDF. Please try another file.</p>';
41
+ console.error('Error loading PDF:', error);
42
+ }
43
+ }
44
+
45
+ async function processPDF() {
46
+ const file = pdfInput.files[0];
47
+ if (!file) {
48
+ alert('Please select a PDF file first');
49
+ return;
50
+ }
51
+
52
+ processBtn.disabled = true;
53
+ processBtn.innerHTML = '<i data-feather="loader" class="animate-spin mr-2"></i> Processing...';
54
+ feather.replace();
55
+
56
+ try {
57
+ const arrayBuffer = await file.arrayBuffer();
58
+ const originalPdfDoc = await PDFLib.PDFDocument.load(arrayBuffer);
59
+ const newPdfDoc = await PDFLib.PDFDocument.create();
60
+
61
+ const originalPages = originalPdfDoc.getPages();
62
+ const pageCount = originalPages.length;
63
+ const reverseOrder = reverseOrderCheckbox.checked;
64
+ const addPageNumbers = addPageNumbersCheckbox.checked;
65
+
66
+ // Process pages in pairs
67
+ for (let i = 0; i < pageCount; i += 2) {
68
+ const page1Index = reverseOrder ? pageCount - 1 - i : i;
69
+ const page2Index = reverseOrder ? pageCount - 2 - i : i + 1;
70
+
71
+ const page1 = originalPages[page1Index];
72
+ const page2 = page2Index < pageCount ? originalPages[page2Index] : null;
73
+
74
+ // Create a new landscape page (double width)
75
+ const { width: pw, height: ph } = page1.getSize();
76
+ const newPage = newPdfDoc.addPage([pw * 2, ph]);
77
+
78
+ // Embed the first page
79
+ const embeddedPage1 = await newPdfDoc.embedPage(page1);
80
+ newPage.drawPage(embeddedPage1, {
81
+ x: 0,
82
+ y: 0,
83
+ width: pw,
84
+ height: ph,
85
+ });
86
+
87
+ // Embed the second page if it exists
88
+ if (page2) {
89
+ const embeddedPage2 = await newPdfDoc.embedPage(page2);
90
+ newPage.drawPage(embeddedPage2, {
91
+ x: pw,
92
+ y: 0,
93
+ width: pw,
94
+ height: ph,
95
+ });
96
+ }
97
+
98
+ // Add page numbers if enabled
99
+ if (addPageNumbers) {
100
+ const pageNumText = `Page ${page1Index + 1}${page2 ? `-${page2Index + 1}` : ''}`;
101
+ newPage.drawText(pageNumText, {
102
+ x: 10,
103
+ y: 10,
104
+ size: 12,
105
+ color: PDFLib.rgb(0.5, 0.5, 0.5),
106
+ });
107
+ }
108
+ }
109
+
110
+ // Save the processed PDF
111
+ processedPdfBytes = await newPdfDoc.save();
112
+
113
+ // Update UI
114
+ preview.innerHTML = `
115
+ <div class="text-center">
116
+ <p class="text-green-600 mb-2"><strong>Processing Complete!</strong></p>
117
+ <p class="text-gray-700">Original pages: ${pageCount}</p>
118
+ <p class="text-gray-700">New pages: ${Math.ceil(pageCount / 2)}</p>
119
+ </div>
120
+ `;
121
+ downloadContainer.classList.remove('hidden');
122
+ } catch (error) {
123
+ console.error('Error processing PDF:', error);
124
+ preview.innerHTML = '<p class="text-red-500 text-center">Error processing PDF. Please try again.</p>';
125
+ } finally {
126
+ processBtn.disabled = false;
127
+ processBtn.innerHTML = '<i data-feather="repeat" class="mr-2"></i> Process PDF';
128
+ feather.replace();
129
+ }
130
+ }
131
+
132
+ function downloadPDF() {
133
+ if (!processedPdfBytes) return;
134
+
135
+ const fileName = pdfInput.files[0].name.replace('.pdf', '') + '_processed.pdf';
136
+ download(processedPdfBytes, fileName, 'application/pdf');
137
+ }
138
+ });
style.css CHANGED
@@ -1,28 +1,22 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
  }
 
 
 
 
1
+ /* Custom styles that can't be done with Tailwind */
2
+ #pdf-input:hover + label {
3
+ border-color: #818cf8;
4
  }
5
 
6
+ #process-btn:disabled {
7
+ background-color: #cbd5e1;
8
+ cursor: not-allowed;
9
  }
10
 
11
+ #download-btn:hover {
12
+ transform: translateY(-2px);
13
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
 
 
14
  }
15
 
16
+ /* Animation for processing */
17
+ @keyframes spin {
18
+ to { transform: rotate(360deg); }
 
 
 
 
 
 
 
19
  }
20
+ .animate-spin {
21
+ animation: spin 1s linear infinite;
22
+ }