Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>PDF Slide Viewer</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.min.js"></script> | |
<style> | |
body { | |
margin: 0; | |
overflow: hidden; | |
font-family: Arial, sans-serif; | |
background-color: #000; | |
color: #fff; | |
position: relative; | |
} | |
canvas { | |
display: block; | |
margin: 0 auto; | |
width: 100vw; | |
height: 100vh; | |
object-fit: contain; | |
cursor: crosshair; | |
} | |
.arrow-button { | |
position: absolute; | |
top: 50%; | |
transform: translateY(-50%); | |
background-color: rgba(0, 0, 0, 0.5); | |
color: #fff; | |
border: none; | |
font-size: 24px; | |
padding: 10px; | |
cursor: pointer; | |
z-index: 10; | |
} | |
.arrow-button:disabled { | |
cursor: not-allowed; | |
background-color: rgba(100, 100, 100, 0.5); | |
} | |
#prev-slide { | |
left: 10px; | |
} | |
#next-slide { | |
right: 10px; | |
} | |
.links-container { | |
position: absolute; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
display: flex; | |
flex-wrap: wrap; | |
gap: 10px; | |
justify-content: center; | |
} | |
.link-button { | |
padding: 10px 15px; | |
background-color: #1a73e8; | |
color: #fff; | |
text-decoration: none; | |
font-size: 14px; | |
border-radius: 5px; | |
border: none; | |
cursor: pointer; | |
} | |
.link-button:hover { | |
background-color: #1558b8; | |
} | |
.toolbar { | |
position: absolute; | |
top: 20px; | |
left: 10px; | |
z-index: 10; | |
display: flex; | |
flex-direction: column; | |
gap: 10px; | |
} | |
.toolbar button { | |
width: 30px; | |
height: 30px; | |
font-size: 16px; | |
cursor: pointer; | |
border: none; | |
border-radius: 50%; | |
background-color: #444; | |
color: #fff; | |
text-align: center; | |
} | |
.toolbar button.active { | |
background-color: #1a73e8; | |
} | |
.color-button { | |
width: 30px; | |
height: 30px; | |
border-radius: 50%; | |
border: 2px solid #fff; | |
cursor: pointer; | |
} | |
.color-button.red { | |
background-color: red; | |
} | |
.color-button.green { | |
background-color: green; | |
} | |
.color-button.blue { | |
background-color: blue; | |
} | |
.color-button.yellow { | |
background-color: yellow; | |
} | |
.slider-container { | |
position: absolute; | |
bottom: 20px; | |
left: 10px; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
z-index: 10; | |
} | |
#page-slider { | |
width: 200px; | |
} | |
#page-number-input { | |
margin-top: 10px; | |
width: 60px; | |
text-align: center; | |
font-size: 16px; | |
} | |
.flash-message { | |
position: absolute; | |
top: 10px; | |
left: 50%; | |
transform: translateX(-50%); | |
background-color: red; | |
color: white; | |
padding: 5px 10px; | |
border-radius: 5px; | |
font-size: 14px; | |
display: none; | |
z-index: 10; | |
} | |
/* Add styles for full-screen button */ | |
#fullscreen-btn { | |
position: absolute; | |
top: 20px; | |
right: 10px; | |
z-index: 10; | |
background-color: #444; | |
color: #fff; | |
border: none; | |
font-size: 16px; | |
padding: 10px; | |
cursor: pointer; | |
border-radius: 5px; | |
} | |
#fullscreen-btn:hover { | |
background-color: #1a73e8; | |
} | |
</style> | |
</head> | |
<body> | |
<canvas id="pdf-canvas"></canvas> | |
<button id="prev-slide" class="arrow-button">‹</button> | |
<button id="next-slide" class="arrow-button">›</button> | |
<div id="links-container" class="links-container"></div> | |
<div class="toolbar"> | |
<button id="toggle-draw" class="color-button">D</button> | |
<button id="clear-draw" class="color-button">E</button> | |
<button class="color-button red" data-color="red"></button> | |
<button class="color-button green" data-color="green"></button> | |
<button class="color-button blue" data-color="blue"></button> | |
<button class="color-button yellow" data-color="yellow"></button> | |
</div> | |
<div class="slider-container"> | |
<input type="range" id="page-slider" min="1" max="1" value="1"> | |
<input type="number" id="page-number-input" min="1" value="1"> | |
</div> | |
<div id="flash-message" class="flash-message">Invalid Page Number!</div> | |
<button id="fullscreen-btn">Full Screen</button> | |
<script> | |
const pdfUrl = 'mypdf.pdf'; // Replace with the path to your PDF file | |
const pdfCanvas = document.getElementById('pdf-canvas'); | |
const ctx = pdfCanvas.getContext('2d'); | |
const prevSlideBtn = document.getElementById('prev-slide'); | |
const nextSlideBtn = document.getElementById('next-slide'); | |
const linksContainer = document.getElementById('links-container'); | |
const toggleDrawBtn = document.getElementById('toggle-draw'); | |
const clearDrawBtn = document.getElementById('clear-draw'); | |
const colorButtons = document.querySelectorAll('.color-button'); | |
const pageSlider = document.getElementById('page-slider'); | |
const pageNumberInput = document.getElementById('page-number-input'); | |
const flashMessage = document.getElementById('flash-message'); | |
// Get the toolbar (drawing tools) element for toggling visibility | |
const toolbar = document.querySelector('.toolbar'); | |
let pdfDoc = null; | |
let currentPage = 1; | |
let totalPages = 0; | |
let isDrawing = false; | |
let drawMode = false; | |
let drawColor = 'red'; // Default drawing color | |
function clearLinks() { | |
linksContainer.innerHTML = ''; | |
} | |
async function renderPage(pageNumber) { | |
const page = await pdfDoc.getPage(pageNumber); | |
const viewport = page.getViewport({ scale: 2 }); | |
pdfCanvas.width = viewport.width; | |
pdfCanvas.height = viewport.height; | |
const renderContext = { | |
canvasContext: ctx, | |
viewport: viewport, | |
enableWebGL: true | |
}; | |
ctx.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height); | |
clearLinks(); | |
await page.render(renderContext).promise; | |
const annotations = await page.getAnnotations(); | |
annotations.forEach(annotation => { | |
if (annotation.url) { | |
const linkButton = document.createElement('a'); | |
linkButton.href = annotation.url; | |
linkButton.target = '_blank'; | |
linkButton.className = 'link-button'; | |
linkButton.textContent = annotation.title || annotation.url; | |
linksContainer.appendChild(linkButton); | |
} | |
}); | |
prevSlideBtn.disabled = currentPage === 1; | |
nextSlideBtn.disabled = currentPage === totalPages; | |
} | |
async function loadPdf() { | |
pdfDoc = await pdfjsLib.getDocument(pdfUrl).promise; | |
totalPages = pdfDoc.numPages; | |
pageSlider.max = totalPages; | |
renderPage(currentPage); | |
} | |
function showFlashMessage() { | |
flashMessage.style.display = 'block'; | |
setTimeout(() => { | |
flashMessage.style.display = 'none'; | |
}, 2000); | |
} | |
// Keyboard events | |
document.addEventListener('keydown', (event) => { | |
switch (event.key) { | |
case 'ArrowLeft': | |
if (currentPage > 1) { | |
currentPage--; | |
pageSlider.value = currentPage; | |
pageNumberInput.value = currentPage; | |
renderPage(currentPage); | |
} | |
break; | |
case 'ArrowRight': | |
if (currentPage < totalPages) { | |
currentPage++; | |
pageSlider.value = currentPage; | |
pageNumberInput.value = currentPage; | |
renderPage(currentPage); | |
} | |
break; | |
case 'd': | |
drawMode = true; | |
toggleDrawBtn.classList.add('active'); | |
break; | |
case 's': | |
drawMode = false; | |
toggleDrawBtn.classList.remove('active'); | |
break; | |
case 'e': | |
ctx.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height); | |
renderPage(currentPage); | |
break; | |
case 'r': | |
drawColor = 'red'; | |
break; | |
case 'g': | |
drawColor = 'green'; | |
break; | |
case 'b': | |
drawColor = 'blue'; | |
break; | |
case 'y': | |
drawColor = 'yellow'; | |
break; | |
case 'h': | |
// Toggle the display of the toolbar (drawing tools) | |
if (toolbar.style.display === 'none') { | |
toolbar.style.display = 'flex'; | |
} else { | |
toolbar.style.display = 'none'; | |
} | |
break; | |
} | |
}); | |
// Arrow button click events to change slides | |
prevSlideBtn.addEventListener('click', () => { | |
if (currentPage > 1) { | |
currentPage--; | |
pageSlider.value = currentPage; | |
pageNumberInput.value = currentPage; | |
renderPage(currentPage); | |
} | |
}); | |
nextSlideBtn.addEventListener('click', () => { | |
if (currentPage < totalPages) { | |
currentPage++; | |
pageSlider.value = currentPage; | |
pageNumberInput.value = currentPage; | |
renderPage(currentPage); | |
} | |
}); | |
// Slider and number input events | |
pageSlider.addEventListener('input', (event) => { | |
currentPage = parseInt(event.target.value); | |
pageNumberInput.value = currentPage; | |
renderPage(currentPage); | |
}); | |
pageNumberInput.addEventListener('change', (event) => { | |
const page = parseInt(event.target.value); | |
if (page >= 1 && page <= totalPages) { | |
currentPage = page; | |
pageSlider.value = currentPage; | |
renderPage(currentPage); | |
} else { | |
showFlashMessage(); | |
} | |
}); | |
// ---- CHANGES START HERE: Scale mouse coordinates to match canvas's internal dimensions ---- | |
pdfCanvas.addEventListener('mousedown', (e) => { | |
if (!drawMode) return; | |
isDrawing = true; | |
const rect = pdfCanvas.getBoundingClientRect(); | |
const scaleX = pdfCanvas.width / rect.width; | |
const scaleY = pdfCanvas.height / rect.height; | |
const x = (e.clientX - rect.left) * scaleX; | |
const y = (e.clientY - rect.top) * scaleY; | |
ctx.beginPath(); | |
ctx.moveTo(x, y); | |
}); | |
pdfCanvas.addEventListener('mousemove', (e) => { | |
if (!isDrawing || !drawMode) return; | |
const rect = pdfCanvas.getBoundingClientRect(); | |
const scaleX = pdfCanvas.width / rect.width; | |
const scaleY = pdfCanvas.height / rect.height; | |
const x = (e.clientX - rect.left) * scaleX; | |
const y = (e.clientY - rect.top) * scaleY; | |
ctx.lineTo(x, y); | |
ctx.strokeStyle = drawColor; | |
ctx.lineWidth = 2; | |
ctx.stroke(); | |
}); | |
pdfCanvas.addEventListener('mouseup', () => { | |
if (!drawMode) return; | |
isDrawing = false; | |
ctx.closePath(); | |
}); | |
// ---- CHANGES END HERE ---- | |
loadPdf().catch(error => { | |
console.error('Error loading PDF:', error); | |
alert('Failed to load PDF. Please check the file path.'); | |
}); | |
// Fullscreen functionality | |
document.getElementById('fullscreen-btn').addEventListener('click', () => { | |
if (!document.fullscreenElement) { | |
document.documentElement.requestFullscreen(); | |
} else { | |
if (document.exitFullscreen) { | |
document.exitFullscreen(); | |
} | |
} | |
}); | |
</script> | |
</body> | |
</html> | |