| <!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" /> |
|
|
| |
| <link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;600;700&display=swap" rel="stylesheet"> |
| <link rel="stylesheet" href="style.css" /> |
|
|
| |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script> |
| |
| <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> |
|
|
| |
| <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; |
| } |
| |
| |
| 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> |