Tools / PDF /images_to_pdf.html
jebin2's picture
image-pdf
2b3b710
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Images to PDF - Tools Collection</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
min-height: 100vh;
color: #e4e4e4;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 40px 20px;
}
.header {
text-align: center;
margin-bottom: 40px;
}
.back-button {
position: absolute;
left: 20px;
top: 20px;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 20px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
color: #e4e4e4;
text-decoration: none;
font-size: 0.9rem;
transition: all 0.3s ease;
}
.back-button:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateX(-5px);
}
.back-button svg {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.logo {
font-size: 4rem;
margin-bottom: 15px;
}
h1 {
font-size: 2.5rem;
background: linear-gradient(135deg, #e94560, #f39c12);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 10px;
}
.subtitle {
color: #8892b0;
font-size: 1.1rem;
}
.tool-card {
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
padding: 40px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.upload-section {
border: 2px dashed rgba(233, 69, 96, 0.5);
border-radius: 15px;
padding: 60px 40px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 30px;
}
.upload-section:hover {
border-color: #e94560;
background: rgba(233, 69, 96, 0.1);
}
.upload-section.dragover {
border-color: #e94560;
background: rgba(233, 69, 96, 0.15);
transform: scale(1.02);
}
.upload-icon {
font-size: 3rem;
margin-bottom: 15px;
}
.upload-text {
font-size: 1.2rem;
margin-bottom: 10px;
}
.upload-hint {
color: #8892b0;
font-size: 0.9rem;
}
#file-input {
display: none;
}
.info-box {
background: rgba(23, 162, 184, 0.1);
border: 1px solid rgba(23, 162, 184, 0.3);
border-radius: 10px;
padding: 15px 20px;
margin-bottom: 30px;
display: flex;
align-items: center;
gap: 12px;
}
.info-box span {
font-size: 1.3rem;
}
.info-box p {
font-size: 0.9rem;
margin: 0;
}
.files-preview {
margin-bottom: 30px;
}
.preview-title {
font-size: 1.1rem;
margin-bottom: 15px;
color: #e4e4e4;
}
.files-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
}
.file-card {
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
padding: 15px;
text-align: center;
position: relative;
}
.file-card .remove-button {
position: absolute;
top: 5px;
right: 5px;
width: 24px;
height: 24px;
border-radius: 50%;
border: none;
background: #dc3545;
color: white;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
}
.file-preview {
width: 100%;
height: 100px;
border-radius: 8px;
overflow: hidden;
margin-bottom: 10px;
background: rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.file-preview img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.file-info h4 {
font-size: 0.8rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 5px;
}
.file-info p {
font-size: 0.7rem;
color: #8892b0;
}
.file-status {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
margin-top: 8px;
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: #28a745;
}
.status-indicator.processing {
background: #ffc107;
animation: pulse 1s infinite;
}
.status-indicator.error {
background: #dc3545;
}
.action-buttons {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.btn {
padding: 15px 30px;
border-radius: 10px;
border: none;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, #e94560, #f39c12);
color: white;
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(233, 69, 96, 0.4);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
color: #e4e4e4;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.2);
}
.btn-success {
background: linear-gradient(135deg, #28a745, #20c997);
color: white;
display: none;
}
.progress-section {
display: none;
margin-bottom: 20px;
}
.progress-bar-container {
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
overflow: hidden;
margin-bottom: 10px;
}
.progress-bar {
height: 100%;
background: linear-gradient(135deg, #e94560, #f39c12);
width: 0%;
transition: width 0.3s ease;
}
.progress-text {
font-size: 0.9rem;
color: #8892b0;
}
.error-message {
display: none;
background: rgba(220, 53, 69, 0.1);
border: 1px solid rgba(220, 53, 69, 0.3);
border-radius: 10px;
padding: 15px 20px;
margin-bottom: 20px;
color: #dc3545;
}
.success-message {
display: none;
background: rgba(40, 167, 69, 0.1);
border: 1px solid rgba(40, 167, 69, 0.3);
border-radius: 10px;
padding: 15px 20px;
margin-bottom: 20px;
color: #28a745;
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
}
</style>
</head>
<body>
<a href="/" class="back-button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 12H5M12 19l-7-7 7-7" />
</svg>
Back to Tools
</a>
<div class="container">
<div class="header">
<div class="logo">πŸ“„</div>
<h1>Images to PDF</h1>
<p class="subtitle">Convert multiple images to a single PDF file</p>
</div>
<div class="tool-card">
<div class="info-box">
<span>ℹ️</span>
<p>Upload multiple images and convert them to a single PDF. Drag to reorder before converting.</p>
</div>
<div id="error-message" class="error-message"></div>
<div id="success-message" class="success-message"></div>
<div class="upload-section" id="upload-section">
<div class="upload-icon">πŸ“€</div>
<p class="upload-text">Drop images here or click to browse</p>
<p class="upload-hint">Supports JPG, PNG, WebP, BMP, GIF, TIFF</p>
<input type="file" id="file-input" accept="image/*" multiple>
</div>
<div class="files-preview" id="files-preview" style="display: none;">
<h3 class="preview-title">πŸ“‹ Selected Images</h3>
<div class="files-grid" id="files-grid"></div>
</div>
<div class="progress-section" id="progress-section">
<div class="progress-bar-container">
<div class="progress-bar" id="progress-bar"></div>
</div>
<p class="progress-text" id="progress-text">0% complete</p>
</div>
<div class="action-buttons">
<button class="btn btn-primary" id="convert-button" disabled>
πŸ“„ Convert to PDF
</button>
<button class="btn btn-secondary" id="clear-button">
πŸ—‘οΈ Clear All
</button>
<button class="btn btn-success" id="download-button">
⬇️ Download PDF
</button>
</div>
</div>
</div>
<script>
let selectedFiles = [];
let pdfUrl = null;
document.addEventListener('DOMContentLoaded', function () {
setupEventListeners();
});
function setupEventListeners() {
const uploadSection = document.getElementById('upload-section');
const fileInput = document.getElementById('file-input');
const convertButton = document.getElementById('convert-button');
const clearButton = document.getElementById('clear-button');
const downloadButton = document.getElementById('download-button');
uploadSection.addEventListener('click', () => fileInput.click());
uploadSection.addEventListener('dragover', handleDragOver);
uploadSection.addEventListener('dragleave', handleDragLeave);
uploadSection.addEventListener('drop', handleDrop);
fileInput.addEventListener('change', handleFileSelect);
convertButton.addEventListener('click', convertToPDF);
clearButton.addEventListener('click', clearAllFiles);
downloadButton.addEventListener('click', downloadPDF);
}
function showMessage(message, type = 'error') {
const errorMsg = document.getElementById('error-message');
const successMsg = document.getElementById('success-message');
errorMsg.style.display = 'none';
successMsg.style.display = 'none';
if (type === 'error') {
errorMsg.textContent = message;
errorMsg.style.display = 'block';
} else {
successMsg.textContent = message;
successMsg.style.display = 'block';
}
setTimeout(() => {
errorMsg.style.display = 'none';
successMsg.style.display = 'none';
}, 5000);
}
function handleDragOver(e) {
e.preventDefault();
e.currentTarget.classList.add('dragover');
}
function handleDragLeave(e) {
e.preventDefault();
e.currentTarget.classList.remove('dragover');
}
function handleDrop(e) {
e.preventDefault();
e.currentTarget.classList.remove('dragover');
const files = Array.from(e.dataTransfer.files);
processFiles(files);
}
function handleFileSelect(e) {
const files = Array.from(e.target.files);
processFiles(files);
}
function generateUniqueId(file, length = 12) {
let ext = "." + file.name.split(".")[file.name.split(".").length - 1];
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('') + ext;
}
function processFiles(files) {
files.forEach((file) => {
if (!file.type.startsWith('image/')) {
showMessage(`${file.name} is not an image file`, 'error');
return;
}
const id = generateUniqueId(file);
const fileData = {
id: id,
file: file,
name: file.name,
size: formatFileSize(file.size),
status: 'ready',
preview: null
};
const reader = new FileReader();
reader.onload = (e) => {
fileData.preview = e.target.result;
updateUI();
};
reader.readAsDataURL(file);
selectedFiles.push(fileData);
});
updateUI();
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function updateUI() {
const filesPreview = document.getElementById('files-preview');
const convertButton = document.getElementById('convert-button');
if (selectedFiles.length > 0) {
filesPreview.style.display = 'block';
convertButton.disabled = false;
renderFileCards();
} else {
filesPreview.style.display = 'none';
convertButton.disabled = true;
}
}
function renderFileCards() {
const filesGrid = document.getElementById('files-grid');
filesGrid.innerHTML = selectedFiles.map((file, index) => `
<div class="file-card" id="file-${file.id}">
<button class="remove-button" onclick="removeFile('${file.id}')">Γ—</button>
<div class="file-preview">
${file.preview ? `<img src="${file.preview}" alt="${file.name}">` : '<span>πŸ–ΌοΈ</span>'}
</div>
<div class="file-info">
<h4>${index + 1}. ${file.name}</h4>
<p>${file.size}</p>
</div>
</div>
`).join('');
}
function removeFile(fileId) {
selectedFiles = selectedFiles.filter(file => file.id !== fileId);
updateUI();
if (selectedFiles.length === 0) {
document.getElementById('download-button').style.display = 'none';
}
}
function clearAllFiles() {
selectedFiles = [];
pdfUrl = null;
updateUI();
document.getElementById('download-button').style.display = 'none';
document.getElementById('progress-section').style.display = 'none';
}
async function convertToPDF() {
if (selectedFiles.length === 0) return;
const progressSection = document.getElementById('progress-section');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const convertButton = document.getElementById('convert-button');
progressSection.style.display = 'block';
convertButton.disabled = true;
convertButton.innerHTML = 'πŸ”„ Converting...';
try {
// Upload all files first
const uploadedIds = [];
for (let i = 0; i < selectedFiles.length; i++) {
const file = selectedFiles[i];
const formData = new FormData();
formData.append('image', file.file);
formData.append('id', file.id);
await fetch('/pdf/upload', {
method: 'POST',
body: formData
});
uploadedIds.push(file.id);
const progress = Math.round(((i + 1) / selectedFiles.length) * 50);
progressBar.style.width = progress + '%';
progressText.textContent = `Uploading: ${progress}%`;
}
// Convert to PDF
progressText.textContent = 'Converting to PDF...';
progressBar.style.width = '75%';
const convertFormData = new FormData();
convertFormData.append('ids', uploadedIds.join(','));
convertFormData.append('output_name', 'images.pdf');
const response = await fetch('/pdf/convert', {
method: 'POST',
body: convertFormData
});
if (!response.ok) {
throw new Error('Conversion failed');
}
const result = await response.json();
pdfUrl = '/pdf/download?id=' + result.new_filename;
progressBar.style.width = '100%';
progressText.textContent = '100% complete';
document.getElementById('download-button').style.display = 'inline-flex';
showMessage('PDF created successfully!', 'success');
} catch (error) {
console.error('Conversion error:', error);
showMessage('Error converting to PDF: ' + error.message, 'error');
}
convertButton.disabled = false;
convertButton.innerHTML = 'πŸ“„ Convert to PDF';
setTimeout(() => {
progressSection.style.display = 'none';
}, 1000);
}
function downloadPDF() {
if (pdfUrl) {
const link = document.createElement('a');
link.href = pdfUrl;
link.download = 'images.pdf';
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
</script>
</body>
</html>