JPG / index.html
stat2025's picture
Update index.html
0144c98 verified
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8" />
<title>تحويل PDF إلى صور</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- خط Tajawal -->
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css" />
<!-- pdf.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<!-- JSZip -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
</head>
<body>
<div class="page">
<main class="main">
<!-- الهيرو -->
<section class="hero">
<div class="logo-mark">JPG</div>
<h1>تحويل PDF إلى صور</h1>
</section>
<!-- الخطوات -->
<section class="steps">
<div class="step">
<span class="step-number">1</span>
<span class="step-text">اختاري ملف PDF واحد.</span>
</div>
<div class="step">
<span class="step-number">2</span>
<span class="step-text">سيتم تحويل الصفحات إلى JPG بجودة عالية تلقائيًا.</span>
</div>
<div class="step">
<span class="step-number">3</span>
<span class="step-text">إذا كان الملف صفحة واحدة سيتم تنزيل الصورة مباشرة، وإذا أكثر سيتم تنزيل ZIP.</span>
</div>
</section>
<!-- الكارد الرئيسي -->
<section class="card main-card">
<!-- اختيار الملف -->
<div class="card-section card-select">
<h2 class="card-title">اختيار ملف PDF</h2>
<label class="file-picker">
<span class="file-picker-text">اضغطي لاختيار ملف PDF (ملف واحد فقط)</span>
<input id="pdfFile" type="file" accept=".pdf,application/pdf" />
</label>
</div>
<!-- معلومات الملف -->
<div class="card-section">
<div id="fileInfo" class="file-list hidden"></div>
</div>
<!-- اسم ZIP -->
<div class="card-section card-output">
<div class="card-row inline">
<label for="outputName" class="card-label">
اسم ملف ZIP (اختياري)
<span class="label-note">
هذا الاسم يُستخدم فقط إذا كان الملف أكثر من صفحة.
</span>
</label>
<input
id="outputName"
type="text"
class="output-input"
placeholder="اكتب اسم ZIP هنا أو اتركه فارغًا"
/>
</div>
<div class="actions">
<button id="convertBtn" class="btn-main">
تحويل PDF إلى صور JPG
</button>
<button id="clearBtn" class="btn-secondary" type="button">
مسح الاختيار
</button>
</div>
<div id="status" class="status"></div>
<div id="progress" class="progress hidden">
<div id="progressText" class="progress-text">جاري المعالجة...</div>
<div class="progress-bar">
<div id="progressFill" class="progress-fill"></div>
</div>
</div>
</div>
</section>
<!-- التوقيع بعد القسم الأخير -->
<div class="footer-credit">
تصميم وإعداد الدعم الفني: نوف الناصر
</div>
</main>
</div>
<script>
const pdfInput = document.getElementById("pdfFile");
const convertBtn = document.getElementById("convertBtn");
const clearBtn = document.getElementById("clearBtn");
const statusDiv = document.getElementById("status");
const fileInfoDiv = document.getElementById("fileInfo");
const outputNameInput = document.getElementById("outputName");
const progressDiv = document.getElementById("progress");
const progressText = document.getElementById("progressText");
const progressFill = document.getElementById("progressFill");
let selectedPdfFile = null;
// توازن جودة/حجم
const JPG_QUALITY = 0.92;
function setStatus(msg, type = "") {
statusDiv.textContent = msg || "";
statusDiv.className = "status" + (type ? " " + type : "");
}
function showProgress(show) {
progressDiv.classList.toggle("hidden", !show);
if (!show) {
progressFill.style.width = "0%";
progressText.textContent = "";
}
}
function setProgress(current, total) {
const percent = Math.floor((current / total) * 100);
progressFill.style.width = percent + "%";
progressText.textContent = `تم تحويل ${current} من ${total} صفحة — ${percent}%`;
}
function basePdfName() {
if (!selectedPdfFile) return "output";
return selectedPdfFile.name.replace(/\.pdf$/i, "") || "output";
}
function renderFileInfo() {
if (!selectedPdfFile) {
fileInfoDiv.classList.add("hidden");
fileInfoDiv.innerHTML = "";
return;
}
fileInfoDiv.classList.remove("hidden");
fileInfoDiv.innerHTML = `<div class="mode-label">${selectedPdfFile.name}</div>`;
}
pdfInput.addEventListener("change", () => {
const file = pdfInput.files && pdfInput.files[0];
if (!file) {
selectedPdfFile = null;
renderFileInfo();
setStatus("");
return;
}
const name = file.name.toLowerCase();
const isPdf = file.type === "application/pdf" || name.endsWith(".pdf");
if (!isPdf) {
selectedPdfFile = null;
pdfInput.value = "";
renderFileInfo();
setStatus("الرجاء اختيار ملف PDF فقط.", "error");
return;
}
selectedPdfFile = file;
setStatus("");
renderFileInfo();
});
clearBtn.addEventListener("click", () => {
selectedPdfFile = null;
pdfInput.value = "";
outputNameInput.value = "";
renderFileInfo();
setStatus("تم مسح الملف المختار.", "ok");
showProgress(false);
});
function downloadBlobAsFile(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
function getZipDefaultName() {
return basePdfName() + "_images.zip";
}
function canvasToJpegBlob(canvas, quality = JPG_QUALITY) {
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (blob) resolve(blob);
else reject(new Error("تعذر إنشاء صورة JPG"));
}, "image/jpeg", quality);
});
}
function computeAutoScale(page) {
// دقة عالية تلقائية مع حماية من الضخامة
let scale = 4;
const v1 = page.getViewport({ scale: 1 });
const megaPixels = (v1.width * v1.height) / 1_000_000;
if (megaPixels > 8) scale = 3;
if (megaPixels > 16) scale = 2;
if (megaPixels > 24) scale = 1.5;
return scale;
}
convertBtn.addEventListener("click", async () => {
if (!selectedPdfFile) {
setStatus("الرجاء اختيار ملف PDF.", "error");
return;
}
try {
setStatus("جاري تحميل PDF...", "loading");
showProgress(true);
convertBtn.disabled = true;
pdfInput.disabled = true;
clearBtn.disabled = true;
pdfjsLib.GlobalWorkerOptions.workerSrc =
"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
const arrayBuffer = await selectedPdfFile.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
const totalPages = pdf.numPages;
// ✅ حالة صفحة واحدة: تنزيل الصورة مباشرة
if (totalPages === 1) {
setProgress(0, 1);
const page = await pdf.getPage(1);
const scale = computeAutoScale(page);
const viewport = page.getViewport({ scale });
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d", { alpha: false });
canvas.width = Math.floor(viewport.width);
canvas.height = Math.floor(viewport.height);
await page.render({ canvasContext: ctx, viewport }).promise;
const blob = await canvasToJpegBlob(canvas, JPG_QUALITY);
downloadBlobAsFile(blob, basePdfName() + ".jpg");
setProgress(1, 1);
setStatus("تم تنزيل الصورة مباشرة بنجاح.", "ok");
showProgress(false);
return;
}
// ✅ أكثر من صفحة: ZIP كما هو
const zip = new JSZip();
const folder = zip.folder("images");
for (let i = 1; i <= totalPages; i++) {
setProgress(i - 1, totalPages);
const page = await pdf.getPage(i);
const scale = computeAutoScale(page);
const viewport = page.getViewport({ scale });
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d", { alpha: false });
canvas.width = Math.floor(viewport.width);
canvas.height = Math.floor(viewport.height);
await page.render({ canvasContext: ctx, viewport }).promise;
const blob = await canvasToJpegBlob(canvas, JPG_QUALITY);
const num = String(i).padStart(3, "0");
folder.file(`page_${num}.jpg`, blob);
}
setProgress(totalPages, totalPages);
const zipBlob = await zip.generateAsync({ type: "blob" });
const zipName = (outputNameInput.value.trim() || getZipDefaultName());
downloadBlobAsFile(zipBlob, zipName);
setStatus("تم التحويل وتنزيل ملف ZIP بنجاح.", "ok");
showProgress(false);
} catch (e) {
console.error(e);
setStatus("حدث خطأ أثناء التحويل.", "error");
showProgress(false);
} finally {
convertBtn.disabled = false;
pdfInput.disabled = false;
clearBtn.disabled = false;
}
});
</script>
</body>
</html>