Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>PDF Reader</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary-color: #2563eb; | |
| --secondary-color: #1e40af; | |
| --background-color: #f8fafc; | |
| --card-background: #ffffff; | |
| --text-color: #1e293b; | |
| --text-secondary: #64748b; | |
| --border-color: #e2e8f0; | |
| --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background-color: var(--background-color); | |
| color: var(--text-color); | |
| line-height: 1.6; | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .header { | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
| color: white; | |
| padding: 1rem 2rem; | |
| box-shadow: var(--shadow); | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| } | |
| .header-content { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| } | |
| .logo { | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .logo i { | |
| font-size: 1.8rem; | |
| } | |
| .anycoder-link { | |
| color: white; | |
| text-decoration: none; | |
| font-weight: 500; | |
| padding: 0.5rem 1rem; | |
| border-radius: 0.5rem; | |
| transition: all 0.3s ease; | |
| background-color: rgba(255, 255, 255, 0.2); | |
| } | |
| .anycoder-link:hover { | |
| background-color: rgba(255, 255, 255, 0.3); | |
| transform: translateY(-2px); | |
| } | |
| .main-container { | |
| max-width: 1200px; | |
| margin: 2rem auto; | |
| padding: 0 1rem; | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2rem; | |
| } | |
| .upload-section { | |
| background: var(--card-background); | |
| border-radius: 1rem; | |
| padding: 2rem; | |
| box-shadow: var(--shadow); | |
| transition: all 0.3s ease; | |
| } | |
| .upload-section:hover { | |
| box-shadow: var(--shadow-lg); | |
| transform: translateY(-2px); | |
| } | |
| .upload-area { | |
| border: 2px dashed var(--border-color); | |
| border-radius: 0.75rem; | |
| padding: 3rem 2rem; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| background-color: var(--background-color); | |
| } | |
| .upload-area:hover { | |
| border-color: var(--primary-color); | |
| background-color: rgba(37, 99, 235, 0.05); | |
| } | |
| .upload-area.dragover { | |
| border-color: var(--primary-color); | |
| background-color: rgba(37, 99, 235, 0.1); | |
| } | |
| .upload-icon { | |
| font-size: 3rem; | |
| color: var(--primary-color); | |
| margin-bottom: 1rem; | |
| } | |
| .upload-text { | |
| font-size: 1.2rem; | |
| color: var(--text-color); | |
| margin-bottom: 0.5rem; | |
| } | |
| .upload-subtext { | |
| color: var(--text-secondary); | |
| font-size: 0.9rem; | |
| } | |
| #fileInput { | |
| display: none; | |
| } | |
| .pdf-viewer-section { | |
| background: var(--card-background); | |
| border-radius: 1rem; | |
| padding: 2rem; | |
| box-shadow: var(--shadow); | |
| display: none; | |
| flex-direction: column; | |
| gap: 1.5rem; | |
| } | |
| .pdf-viewer-section.active { | |
| display: flex; | |
| } | |
| .pdf-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| } | |
| .pdf-title { | |
| font-size: 1.25rem; | |
| font-weight: 600; | |
| color: var(--text-color); | |
| } | |
| .pdf-controls { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| flex-wrap: wrap; | |
| } | |
| .control-group { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| background: var(--background-color); | |
| padding: 0.5rem; | |
| border-radius: 0.5rem; | |
| } | |
| .control-btn { | |
| background: none; | |
| border: none; | |
| color: var(--text-color); | |
| cursor: pointer; | |
| padding: 0.5rem; | |
| border-radius: 0.25rem; | |
| transition: all 0.2s ease; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 2.5rem; | |
| height: 2.5rem; | |
| } | |
| .control-btn:hover { | |
| background-color: var(--primary-color); | |
| color: white; | |
| } | |
| .control-btn:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| .zoom-controls { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .zoom-value { | |
| min-width: 3rem; | |
| text-align: center; | |
| font-weight: 500; | |
| } | |
| .pdf-container { | |
| flex: 1; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| background-color: #f1f5f9; | |
| border-radius: 0.5rem; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| #pdfViewer { | |
| max-width: 100%; | |
| max-height: 100%; | |
| box-shadow: var(--shadow); | |
| } | |
| .loading-spinner { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| display: none; | |
| } | |
| .spinner { | |
| width: 50px; | |
| height: 50px; | |
| border: 5px solid var(--border-color); | |
| border-top: 5px solid var(--primary-color); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .page-info { | |
| text-align: center; | |
| color: var(--text-secondary); | |
| font-size: 0.9rem; | |
| } | |
| .error-message { | |
| background-color: #fee2e2; | |
| color: #dc2626; | |
| padding: 1rem; | |
| border-radius: 0.5rem; | |
| margin-top: 1rem; | |
| display: none; | |
| } | |
| .error-message.show { | |
| display: block; | |
| } | |
| @media (max-width: 768px) { | |
| .header-content { | |
| flex-direction: column; | |
| text-align: center; | |
| } | |
| .main-container { | |
| margin: 1rem auto; | |
| padding: 0 0.5rem; | |
| } | |
| .upload-section, .pdf-viewer-section { | |
| padding: 1.5rem; | |
| } | |
| .pdf-controls { | |
| justify-content: center; | |
| } | |
| .control-group { | |
| flex-direction: column; | |
| width: fit-content; | |
| margin: 0 auto; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .header { | |
| padding: 1rem; | |
| } | |
| .logo { | |
| font-size: 1.2rem; | |
| } | |
| .upload-icon { | |
| font-size: 2rem; | |
| } | |
| .upload-text { | |
| font-size: 1rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header class="header"> | |
| <div class="header-content"> | |
| <div class="logo"> | |
| <i class="fas fa-file-pdf"></i> | |
| <span>PDF Reader</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank"> | |
| Built with anycoder | |
| </a> | |
| </div> | |
| </header> | |
| <main class="main-container"> | |
| <section class="upload-section"> | |
| <div class="upload-area" id="uploadArea"> | |
| <i class="fas fa-cloud-upload-alt upload-icon"></i> | |
| <div class="upload-text">Drop your PDF file here or click to browse</div> | |
| <div class="upload-subtext">Supports PDF files only</div> | |
| <input type="file" id="fileInput" accept=".pdf"> | |
| </div> | |
| <div class="error-message" id="errorMessage"></div> | |
| </section> | |
| <section class="pdf-viewer-section" id="pdfViewerSection"> | |
| <div class="pdf-header"> | |
| <div class="pdf-title" id="pdfTitle">Document Title</div> | |
| <div class="pdf-controls"> | |
| <div class="control-group"> | |
| <button class="control-btn" id="prevPage" title="Previous Page"> | |
| <i class="fas fa-chevron-left"></i> | |
| </button> | |
| <span id="pageInfo">Page 1 of 1</span> | |
| <button class="control-btn" id="nextPage" title="Next Page"> | |
| <i class="fas fa-chevron-right"></i> | |
| </button> | |
| </div> | |
| <div class="control-group zoom-controls"> | |
| <button class="control-btn" id="zoomOut" title="Zoom Out"> | |
| <i class="fas fa-search-minus"></i> | |
| </button> | |
| <span class="zoom-value" id="zoomValue">100%</span> | |
| <button class="control-btn" id="zoomIn" title="Zoom In"> | |
| <i class="fas fa-search-plus"></i> | |
| </button> | |
| <button class="control-btn" id="zoomReset" title="Reset Zoom"> | |
| <i class="fas fa-compress-arrows-alt"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="pdf-container"> | |
| <div class="loading-spinner" id="loadingSpinner"> | |
| <div class="spinner"></div> | |
| </div> | |
| <canvas id="pdfViewer"></canvas> | |
| </div> | |
| <div class="page-info" id="pageInfoBottom">Page 1 of 1</div> | |
| </section> | |
| </main> | |
| <script> | |
| // Set PDF.js worker | |
| pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; | |
| const uploadArea = document.getElementById('uploadArea'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const pdfViewerSection = document.getElementById('pdfViewerSection'); | |
| const pdfViewer = document.getElementById('pdfViewer'); | |
| const pdfTitle = document.getElementById('pdfTitle'); | |
| const prevPageBtn = document.getElementById('prevPage'); | |
| const nextPageBtn = document.getElementById('nextPage'); | |
| const zoomInBtn = document.getElementById('zoomIn'); | |
| const zoomOutBtn = document.getElementById('zoomOut'); | |
| const zoomResetBtn = document.getElementById('zoomReset'); | |
| const zoomValue = document.getElementById('zoomValue'); | |
| const pageInfo = document.getElementById('pageInfo'); | |
| const pageInfoBottom = document.getElementById('pageInfoBottom'); | |
| const loadingSpinner = document.getElementById('loadingSpinner'); | |
| const errorMessage = document.getElementById('errorMessage'); | |
| let pdfDoc = null; | |
| let currentPage = 1; | |
| let zoomLevel = 1.0; | |
| let pageRendering = false; | |
| let pageNumPending = null; | |
| // Upload area click handler | |
| uploadArea.addEventListener('click', () => { | |
| fileInput.click(); | |
| }); | |
| // File input change handler | |
| fileInput.addEventListener('change', (e) => { | |
| const file = e.target.files[0]; | |
| if (file && file.type === 'application/pdf') { | |
| loadPDF(file); | |
| } else { | |
| showError('Please select a valid PDF file.'); | |
| } | |
| }); | |
| // Drag and drop handlers | |
| uploadArea.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| uploadArea.classList.add('dragover'); | |
| }); | |
| uploadArea.addEventListener('dragleave', () => { | |
| uploadArea.classList.remove('dragover'); | |
| }); | |
| uploadArea.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| uploadArea.classList.remove('dragover'); | |
| const file = e.dataTransfer.files[0]; | |
| if (file && file.type === 'application/pdf') { | |
| loadPDF(file); | |
| } else { | |
| showError('Please drop a valid PDF file.'); | |
| } | |
| }); | |
| // Load PDF function | |
| function loadPDF(file) { | |
| const fileReader = new FileReader(); | |
| loadingSpinner.style.display = 'block'; | |
| fileReader.onload = function() { | |
| const typedarray = new Uint8Array(this.result); | |
| pdfjsLib.getDocument(typedarray).promise.then(function(pdf) { | |
| pdfDoc = pdf; | |
| pdfTitle.textContent = file.name; | |
| currentPage = 1; | |
| zoomLevel = 1.0; | |
| updateZoomDisplay(); | |
| renderPage(currentPage); | |
| pdfViewerSection.classList.add('active'); | |
| loadingSpinner.style.display = 'none'; | |
| updatePageButtons(); | |
| }).catch(function(error) { | |
| loadingSpinner.style.display = 'none'; | |
| showError('Error loading PDF: ' + error.message); | |
| }); | |
| }; | |
| fileReader.readAsArrayBuffer(file); | |
| } | |
| // Render page function | |
| function renderPage(num) { | |
| pageRendering = true; | |
| pdfDoc.getPage(num).then(function(page) { | |
| const viewport = page.getViewport({ scale: zoomLevel }); | |
| pdfViewer.height = viewport.height; | |
| pdfViewer.width = viewport.width; | |
| const renderContext = { | |
| canvas: pdfViewer, | |
| viewport: viewport | |
| }; | |
| const renderTask = page.render(renderContext); | |
| renderTask.promise.then(function() { | |
| pageRendering = false; | |
| if (pageNumPending !== null) { | |
| renderPage(pageNumPending); | |
| pageNumPending = null; | |
| } | |
| updatePageInfo(); | |
| }); | |
| }); | |
| } | |
| // Queue render page | |
| function queueRenderPage(num) { | |
| if (pageRendering) { | |
| pageNumPending = num; | |
| } else { | |
| renderPage(num); | |
| } | |
| } | |
| // Show previous page | |
| prevPageBtn.addEventListener('click', () => { | |
| if (currentPage <= 1) return; | |
| currentPage--; | |
| queueRenderPage(currentPage); | |
| updatePageButtons(); | |
| }); | |
| // Show next page | |
| nextPageBtn.addEventListener('click', () => { | |
| if (currentPage >= pdfDoc.numPages) return; | |
| currentPage++; | |
| queueRenderPage(currentPage); | |
| updatePageButtons(); | |
| }); | |
| // Zoom in | |
| zoomInBtn.addEventListener('click', () => { | |
| if (zoomLevel >= 3.0) return; | |
| zoomLevel += 0.25; | |
| updateZoomDisplay(); | |
| queueRenderPage(currentPage); | |
| }); | |
| // Zoom out | |
| zoomOutBtn.addEventListener('click', () => { | |
| if (zoomLevel <= 0.5) return; | |
| zoomLevel -= 0.25; | |
| updateZoomDisplay(); | |
| queueRenderPage(currentPage); | |
| }); | |
| // Reset zoom | |
| zoomResetBtn.addEventListener('click', () => { | |
| zoomLevel = 1.0; | |
| updateZoomDisplay(); | |
| queueRenderPage(currentPage); | |
| }); | |
| // Update zoom display | |
| function updateZoomDisplay() { | |
| zoomValue.textContent = Math.round(zoomLevel * 100) + '%'; | |
| } | |
| // Update page info | |
| function updatePageInfo() { | |
| pageInfo.textContent = `Page ${currentPage} of ${pdfDoc.numPages}`; | |
| pageInfoBottom.textContent = `Page ${currentPage} of ${pdfDoc.numPages}`; | |
| } | |
| // Update page buttons | |
| function updatePageButtons() { | |
| prevPageBtn.disabled = currentPage <= 1; | |
| nextPageBtn.disabled = currentPage >= pdfDoc.numPages; | |
| } | |
| // Show error message | |
| function showError(message) { | |
| errorMessage.textContent = message; | |
| errorMessage.classList.add('show'); | |
| setTimeout(() => { | |
| errorMessage.classList.remove('show'); | |
| }, 5000); | |
| } | |
| // Keyboard navigation | |
| document.addEventListener('keydown', (e) => { | |
| if (!pdfViewerSection.classList.contains('active')) return; | |
| switch(e.key) { | |
| case 'ArrowLeft': | |
| prevPageBtn.click(); | |
| break; | |
| case 'ArrowRight': | |
| nextPageBtn.click(); | |
| break; | |
| case '+': | |
| case '=': | |
| zoomInBtn.click(); | |
| break; | |
| case '-': | |
| case '_': | |
| zoomOutBtn.click(); | |
| break; | |
| case '0': | |
| zoomResetBtn.click(); | |
| break; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |