Features Implemented
Core Features :
- PDF Upload and Viewing: Users can upload PDF files which are rendered in the browser using PDF.js
- Page Rendering: Pages are rendered using canvas elements
- View Modes: Supports both single-page view and all-pages scrollable view
User Options :
- Print: Opens the browser's print dialog for printing the PDF
- Save as Images: Converts all pages to JPG images and packages them in a ZIP file for download
Basic Controls :
- Navigation: Previous/Next buttons for moving between pages
- Page Info: Displays current page number and total pages (e.g., "Page 3 of 10")
- Zoom: Zoom in/out functionality with percentage display
- Technical Implementation
PDF.js: Used for PDF rendering and processing - JSZip: For creating ZIP archives of the images
- Canvas-to-Blob: For converting canvas elements to image files
- FileSaver.js: For triggering the file download
UI Features :
- Responsive Design: Works on both desktop and mobile devices
- Modern Styling: Uses Inter font and clean, intuitive interface
- Font Awesome Icons: For better visual cues
- Loading States: Visual feedback during processing
The application is completely self-contained - just copy and paste the code into an HTML file and it will work without any additional dependencies.
:root {
--primary-color: #4a6fa5;
--secondary-color: #6c757d;
--light-color: #f8f9fa;
--dark-color: #343a40;
--border-color: #dee2e6;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Inter', sans-serif;
}
body {
background-color: #f5f5f5;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid var(--border-color);
}
h1 {
font-size: 2rem;
margin-bottom: 10px;
color: var(--primary-color);
}
.upload-section {
background-color: white;
border-radius: 8px;
padding: 30px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
text-align: center;
}
.file-input-wrapper {
position: relative;
display: inline-block;
margin-bottom: 20px;
}
.file-input {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.file-input-label {
display: inline-block;
padding: 12px 24px;
background-color: var(--primary-color);
color: white;
border-radius: 6px;
font-weight: 500;
transition: background-color 0.3s;
cursor: pointer;
}
.file-input-label:hover {
background-color: #3a5a8c;
}
.file-name {
margin-top: 10px;
font-size: 0.9rem;
color: var(--secondary-color);
}
.viewer-section {
display: none;
background-color: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 10px;
}
.toolbar-group {
display: flex;
align-items: center;
gap: 10px;
}
.btn {
padding: 8px 16px;
background-color: var(--light-color);
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn:hover {
background-color: #e9ecef;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.btn-primary:hover {
background-color: #3a5a8c;
border-color: #3a5a8c;
}
.btn-danger {
background-color: #dc3545;
color: white;
border-color: #dc3545;
}
.btn-danger:hover {
background-color: #bb2d3b;
border-color: #bb2d3b;
}
.page-controls {
display: flex;
align-items: center;
gap: 10px;
}
.page-info {
font-weight: 500;
}
.zoom-controls {
display: flex;
align-items: center;
gap: 10px;
}
.zoom-level {
min-width: 40px;
text-align: center;
}
.canvas-container {
width: 100%;
overflow-x: auto;
text-align: center;
margin-bottom: 20px;
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 10px;
background-color: #f0f0f0;
}
#pdf-canvas {
max-width: 100%;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.view-mode-selector {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.view-mode-btn {
padding: 8px 16px;
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s;
}
.view-mode-btn.active {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
#all-pages-container {
display: none;
flex-direction: column;
gap: 20px;
}
.page-canvas {
width: 100%;
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
border: 1px solid var(--border-color);
}
footer {
text-align: center;
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid var(--border-color);
color: var(--secondary-color);
font-size: 0.9rem;
}
@media (max-width: 768px) {
.toolbar {
flex-direction: column;
align-items: stretch;
}
.toolbar-group {
justify-content: space-between;
margin-bottom: 10px;
}
.page-controls {
order: -1;
margin-bottom: 10px;
}
}
// Set current year in footer
document.getElementById('year').textContent = new Date().getFullYear();
// Initialize PDF.js
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.worker.min.js';
// DOM elements
const fileInput = document.getElementById('file-input');
const fileName = document.getElementById('file-name');
const viewerSection = document.getElementById('viewer-section');
const pdfCanvas = document.getElementById('pdf-canvas');
const allPagesContainer = document.getElementById('all-pages-container');
const pageInfo = document.getElementById('page-info');
const zoomLevel = document.getElementById('zoom-level');
const prevPageBtn = document.getElementById('prev-page');
const nextPageBtn = document.getElementById('next-page');
const zoomInBtn = document.getElementById('zoom-in');
const zoomOutBtn = document.getElementById('zoom-out');
const printBtn = document.getElementById('print-btn');
const saveImagesBtn = document.getElementById('save-images-btn');
const closeBtn = document.getElementById('close-btn');
const singlePageBtn = document.getElementById('single-page-btn');
const allPagesBtn = document.getElementById('all-pages-btn');
const singlePageContainer = document.getElementById('single-page-container');
// PDF variables
let pdfDoc = null;
let pageNum = 1;
let pageRendering = false;
let pageNumPending = null;
let scale = 1.0;
const scaleStep = 0.25;
const minScale = 0.5;
const maxScale = 3.0;
// File input handler
fileInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
fileName.textContent = file.name;
loadPDF(file);
}
});
// Close PDF handler
closeBtn.addEventListener('click', function() {
viewerSection.style.display = 'none';
fileInput.value = '';
fileName.textContent = 'No file selected';
});
// Load PDF file
function loadPDF(file) {
const fileReader = new FileReader();
fileReader.onload = function() {
const typedArray = new Uint8Array(this.result);
// Load the PDF
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
pdfDoc = pdf;
pageNum = 1;
scale = 1.0;
// Show the viewer section
viewerSection.style.display = 'block';
// Reset view mode to single page
singlePageBtn.classList.add('active');
allPagesBtn.classList.remove('active');
singlePageContainer.style.display = 'block';
allPagesContainer.style.display = 'none';
// Render the first page
renderPage(pageNum);
// Update page info
updatePageInfo();
// Render all pages for "All Pages" view
renderAllPages();
}).catch(function(error) {
alert('Error loading PDF: ' + error.message);
});
};
fileReader.readAsArrayBuffer(file);
}
// Render a page
function renderPage(num) {
pageRendering = true;
pdfDoc.getPage(num).then(function(page) {
const viewport = page.getViewport({ scale: scale });
const canvas = pdfCanvas;
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: context,
viewport: viewport
};
const renderTask = page.render(renderContext);
renderTask.promise.then(function() {
pageRendering = false;
if (pageNumPending !== null) {
renderPage(pageNumPending);
pageNumPending = null;
}
});
});
updatePageInfo();
}
// Render all pages for "All Pages" view
function renderAllPages() {
allPagesContainer.innerHTML = '';
for (let i = 1; i <= pdfDoc.numPages; i++) {
const pageDiv = document.createElement('div');
pageDiv.className = 'page-wrapper';
pageDiv.innerHTML = `Page ${i}
`;
const canvas = document.createElement('canvas');
canvas.className = 'page-canvas';
pageDiv.appendChild(canvas);
allPagesContainer.appendChild(pageDiv);
pdfDoc.getPage(i).then(function(page) {
const viewport = page.getViewport({ scale: 1.0 });
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: canvas.getContext('2d'),
viewport: viewport
};
page.render(renderContext);
});
}
}
// Queue rendering of a page
function queueRenderPage(num) {
if (pageRendering) {
pageNumPending = num;
} else {
renderPage(num);
}
}
// Update page info display
function updatePageInfo() {
pageInfo.textContent = `Page ${pageNum} of ${pdfDoc.numPages}`;
zoomLevel.textContent = `${Math.round(scale * 100)}%`;
}
// Previous page button
prevPageBtn.addEventListener('click', function() {
if (pageNum <= 1) return;
pageNum--;
queueRenderPage(pageNum);
});
// Next page button
nextPageBtn.addEventListener('click', function() {
if (pageNum >= pdfDoc.numPages) return;
pageNum++;
queueRenderPage(pageNum);
});
// Zoom in button
zoomInBtn.addEventListener('click', function() {
if (scale >= maxScale) return;
scale += scaleStep;
queueRenderPage(pageNum);
});
// Zoom out button
zoomOutBtn.addEventListener('click', function() {
if (scale <= minScale) return;
scale -= scaleStep;
queueRenderPage(pageNum);
});
// Print button
printBtn.addEventListener('click', function () {
if (!pdfDoc) return;
pdfDoc.getPage(pageNum).then(function (page) {
const viewport = page.getViewport({ scale: scale });
const printCanvas = document.createElement('canvas');
const context = printCanvas.getContext('2d');
printCanvas.width = viewport.width;
printCanvas.height = viewport.height;
page.render({ canvasContext: context, viewport: viewport }).promise.then(function () {
// Open a new window for printing
const dataUrl = printCanvas.toDataURL();
const printWindow = window.open('', '_blank');
printWindow.document.write(`
Print PDF Page
`);
printWindow.document.close();
});
});
});
// Save as images button
saveImagesBtn.addEventListener('click', async function() {
if (!pdfDoc) return;
saveImagesBtn.disabled = true;
saveImagesBtn.innerHTML = ' Processing...';
try {
const zip = new JSZip();
const imgFolder = zip.folder("pdf_images");
// Capture all pages
for (let i = 1; i <= pdfDoc.numPages; i++) {
const page = await pdfDoc.getPage(i);
const viewport = page.getViewport({ scale: 2.0 }); // Higher scale for better quality
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
await page.render({
canvasContext: context,
viewport: viewport
}).promise;
// Convert canvas to blob and add to zip
const blob = await new Promise(resolve => {
canvas.toBlob(resolve, 'image/jpeg', 0.9);
});
imgFolder.file(`page_${i}.jpg`, blob);
}
// Generate zip file
const content = await zip.generateAsync({ type: 'blob' });
saveAs(content, `${fileName.textContent.replace('.pdf', '')}_images.zip`);
} catch (error) {
alert('Error generating images: ' + error.message);
console.error(error);
} finally {
saveImagesBtn.disabled = false;
saveImagesBtn.innerHTML = ' Save as Images (ZIP)';
}
});
// View mode buttons
singlePageBtn.addEventListener('click', function() {
singlePageBtn.classList.add('active');
allPagesBtn.classList.remove('active');
singlePageContainer.style.display = 'block';
allPagesContainer.style.display = 'none';
});
allPagesBtn.addEventListener('click', function() {
allPagesBtn.classList.add('active');
singlePageBtn.classList.remove('active');
singlePageContainer.style.display = 'none';
allPagesContainer.style.display = 'flex';
});
// Keyboard navigation
document.addEventListener('keydown', function(e) {
if (!viewerSection.style.display || viewerSection.style.display === 'none') return;
if (e.key === 'ArrowLeft' || e.key === 'PageUp') {
if (pageNum > 1) {
pageNum--;
queueRenderPage(pageNum);
e.preventDefault();
}
} else if (e.key === 'ArrowRight' || e.key === 'PageDown') {
if (pageNum < pdfDoc.numPages) {
pageNum++;
queueRenderPage(pageNum);
e.preventDefault();
}
} else if (e.key === '+' || e.key === '=') {
if (scale < maxScale) {
scale += scaleStep;
queueRenderPage(pageNum);
e.preventDefault();
}
} else if (e.key === '-' || e.key === '_') {
if (scale > minScale) {
scale -= scaleStep;
queueRenderPage(pageNum);
e.preventDefault();
}
}
});
function updatePageInfo() {
pageInfo.textContent = `Page ${pageNum} of ${pdfDoc.numPages}`;
zoomLevel.textContent = `${Math.round(scale * 100)}%`;
}
// Button event handlers
prevPageBtn.addEventListener('click', function() {
if (pageNum <= 1) return;
pageNum--;
queueRenderPage(pageNum);
});
nextPageBtn.addEventListener('click', function() {
if (pageNum >= pdfDoc.numPages) return;
pageNum++;
queueRenderPage(pageNum);
});
zoomInBtn.addEventListener('click', function() {
if (scale >= maxScale) return;
scale += scaleStep;
queueRenderPage(pageNum);
zoomLevel.textContent = `${Math.round(scale * 100)}%`;
});
zoomOutBtn.addEventListener('click', function() {
if (scale <= minScale) return;
scale -= scaleStep;
queueRenderPage(pageNum);
zoomLevel.textContent = `${Math.round(scale * 100)}%`;
});
إرسال تعليق
Thank you
Learning robo team