{"id":1145,"date":"2025-11-05T08:42:33","date_gmt":"2025-11-05T08:42:33","guid":{"rendered":"https:\/\/bookfax.com\/?page_id=1145"},"modified":"2025-11-05T09:03:08","modified_gmt":"2025-11-05T09:03:08","slug":"pdf-to-word","status":"publish","type":"page","link":"https:\/\/bookfax.com\/index.php\/pdf-to-word\/","title":{"rendered":"PDF to Word"},"content":{"rendered":"\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" \/>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" \/>\n  <title>PDF \u2192 Word Converter \u2014 Debug Friendly Single File<\/title>\n\n  <!-- Tailwind for styling -->\n  <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n\n  <!-- PDF.js -->\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/2.16.105\/pdf.min.js\"><\/script>\n  <script>\n    \/\/ try set worker path for pdf.js; if CDN blocked this will be reported in UI\n    try {\n      if (window.pdfjsLib) {\n        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/2.16.105\/pdf.worker.min.js';\n      }\n    } catch (e) { console.warn('workerSrc set failed', e); }\n  <\/script>\n\n  <!-- docx UMD -->\n  <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/docx@7.2.0\/build\/index.umd.js\"><\/script>\n\n  <style>\n    .dashed-drop { border-style: dashed; border-width: 2px; border-color: rgba(148,163,184,0.45); }\n    .thumb { max-width:160px; max-height:120px; object-fit:contain; border-radius:6px; box-shadow:0 6px 18px rgba(2,6,23,0.06); }\n    .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, \"Roboto Mono\", \"Courier New\", monospace; font-size:12px; }\n  <\/style>\n<\/head>\n<body class=\"bg-slate-50 text-slate-800 antialiased\">\n  <main class=\"max-w-4xl mx-auto p-6\">\n    <div class=\"bg-white shadow rounded-2xl p-6\">\n      <div class=\"flex items-center justify-between\">\n        <div>\n          <h1 class=\"text-2xl font-semibold\">PDF \u2192 Word Converter<\/h1>\n          <p class=\"text-sm text-slate-500\">Client-side conversion. Best opened via <strong>https:\/\/<\/strong> or <strong>http:\/\/<\/strong>.<\/p>\n        <\/div>\n        <div class=\"text-xs text-slate-400\">No server required<\/div>\n      <\/div>\n\n      <div class=\"mt-6 grid grid-cols-1 md:grid-cols-3 gap-4\">\n        <section class=\"md:col-span-2\">\n          <label class=\"block text-sm font-medium text-slate-600\">Select or drop a PDF<\/label>\n          <div id=\"dropzone\" class=\"mt-3 p-4 rounded-lg bg-slate-50 dashed-drop cursor-pointer\">\n            <input id=\"fileInput\" accept=\"application\/pdf\" type=\"file\" class=\"hidden\">\n            <div class=\"flex items-center gap-4\">\n              <svg class=\"w-10 h-10 text-slate-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M12 4v16m8-8H4\" \/><\/svg>\n              <div>\n                <div class=\"text-slate-700 font-medium\">Click to upload or drop a PDF<\/div>\n                <div class=\"text-xs text-slate-500 mt-0.5\">Try small test PDFs first (1\u20135 pages).<\/div>\n              <\/div>\n            <\/div>\n            <div id=\"drop-hint\" class=\"mt-3 text-xs text-slate-500\">No file selected<\/div>\n          <\/div>\n\n          <div class=\"mt-4 flex flex-wrap gap-3 items-center\">\n            <label class=\"flex items-center gap-2 text-sm text-slate-600\">\n              <input id=\"includeImages\" type=\"checkbox\" checked class=\"w-4 h-4\">\n              Include page images (preserve layout)\n            <\/label>\n\n            <label class=\"flex items-center gap-2 text-sm text-slate-600\">\n              Output filename\n              <input id=\"fileName\" placeholder=\"converted.docx\" class=\"ml-2 px-3 py-1 rounded border border-slate-200 text-sm\">\n            <\/label>\n          <\/div>\n\n          <div class=\"mt-4 flex gap-3\">\n            <button id=\"convertBtn\" class=\"px-4 py-2 bg-indigo-600 text-white rounded disabled:opacity-60\" disabled>Convert<\/button>\n            <a id=\"downloadBtn\" class=\"px-4 py-2 bg-white border border-slate-200 text-slate-700 rounded hidden\" download>Download<\/a>\n            <button id=\"resetBtn\" class=\"px-4 py-2 bg-slate-50 border border-slate-200 rounded hidden\">Reset<\/button>\n          <\/div>\n\n          <div class=\"mt-4\">\n            <div id=\"progressWrap\" class=\"hidden\">\n              <div class=\"text-xs text-slate-500\">Progress<\/div>\n              <div class=\"w-full bg-slate-100 rounded-full h-2 mt-2 overflow-hidden\">\n                <div id=\"progressBar\" style=\"width:0%\" class=\"h-2 rounded-full bg-indigo-500\"><\/div>\n              <\/div>\n              <div id=\"progressText\" class=\"text-xs text-slate-500 mt-2\">\u2014<\/div>\n            <\/div>\n          <\/div>\n\n          <div class=\"mt-4 text-sm text-slate-500\">\n            If conversion fails: make sure you&#8217;re opening the page over <strong>http\/https<\/strong>, use a modern browser (Chrome or Edge recommended), and test with a small PDF (1\u20133 pages).\n          <\/div>\n        <\/section>\n\n        <aside class=\"bg-slate-50 p-4 rounded-lg\">\n          <div class=\"flex items-center justify-between\">\n            <div class=\"text-sm font-medium text-slate-700\">Preview<\/div>\n            <div id=\"meta\" class=\"text-xs text-slate-500\">No PDF<\/div>\n          <\/div>\n          <div id=\"preview\" class=\"mt-3 space-y-3 text-sm text-slate-600\">\n            Upload a PDF to preview extracted text and thumbnails.\n          <\/div>\n        <\/aside>\n      <\/div>\n\n      <div class=\"mt-6\">\n        <details>\n          <summary class=\"text-sm text-slate-600 cursor-pointer\">Debug \/ Error output (click to open)<\/summary>\n          <pre id=\"debug\" class=\"mono p-3 bg-black\/5 rounded mt-2 h-40 overflow-auto\"><\/pre>\n        <\/details>\n      <\/div>\n    <\/div>\n  <\/main>\n\n  <script>\n  (function(){\n    \/\/ nodes\n    const drop = document.getElementById('dropzone');\n    const fileInput = document.getElementById('fileInput');\n    const dropHint = document.getElementById('drop-hint');\n    const preview = document.getElementById('preview');\n    const meta = document.getElementById('meta');\n    const convertBtn = document.getElementById('convertBtn');\n    const downloadBtn = document.getElementById('downloadBtn');\n    const resetBtn = document.getElementById('resetBtn');\n    const progressWrap = document.getElementById('progressWrap');\n    const progressBar = document.getElementById('progressBar');\n    const progressText = document.getElementById('progressText');\n    const includeImages = document.getElementById('includeImages');\n    const fileNameInput = document.getElementById('fileName');\n    const debugEl = document.getElementById('debug');\n\n    let currentFile = null;\n    let extracted = { textPages: [], images: [] };\n    let lastBlobUrl = null;\n\n    function logDebug(...args){\n      const txt = args.map(a => (typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a))).join(' ');\n      debugEl.textContent += txt + '\\n';\n      debugEl.scrollTop = debugEl.scrollHeight;\n      console.log(...args);\n    }\n\n    function showError(msg){\n      logDebug('ERROR: ' + msg);\n      alert(msg);\n      meta.textContent = 'Error \u2014 see Debug';\n      progressWrap.classList.add('hidden');\n    }\n\n    function setProgress(p, txt){\n      progressWrap.classList.remove('hidden');\n      progressBar.style.width = Math.min(100, Math.max(0, p)) + '%';\n      progressText.textContent = txt || (p + '%');\n    }\n\n    function prettyBytes(bytes){\n      if (!bytes) return '0 B';\n      const k = 1024; const sizes = ['B','KB','MB','GB']; const i = Math.floor(Math.log(bytes)\/Math.log(k));\n      return Number((bytes\/Math.pow(k,i)).toFixed(2)) + ' ' + sizes[i];\n    }\n\n    \/\/ drag\/drop\n    drop.addEventListener('click', ()=> fileInput.click());\n    drop.addEventListener('dragover', (e)=> { e.preventDefault(); drop.classList.add('bg-slate-100'); });\n    drop.addEventListener('dragleave', ()=> drop.classList.remove('bg-slate-100'));\n    drop.addEventListener('drop', (e)=> {\n      e.preventDefault();\n      drop.classList.remove('bg-slate-100');\n      if (e.dataTransfer.files && e.dataTransfer.files.length) handleFile(e.dataTransfer.files[0]);\n    });\n\n    fileInput.addEventListener('change', (e)=> { if (e.target.files && e.target.files[0]) handleFile(e.target.files[0]); });\n\n    function handleFile(file){\n      try {\n        if (!file) return;\n        if (file.type !== 'application\/pdf' && !file.name.toLowerCase().endsWith('.pdf')) {\n          showError('Please provide a PDF file.');\n          return;\n        }\n        currentFile = file;\n        dropHint.textContent = `${file.name} \u2014 ${prettyBytes(file.size)}`;\n        meta.textContent = `Selected: ${file.name}`;\n        fileNameInput.value = file.name.replace(\/\\.pdf$\/i,'') + '.docx';\n        convertBtn.disabled = false;\n        preview.innerHTML = `<div class=\"text-sm\">Preparing preview for <strong>${file.name}<\/strong>\u2026<\/div>`;\n        logDebug('Selected file:', file.name, file.size, file.type);\n      } catch (e) {\n        showError('handleFile failed: ' + e.message);\n      }\n    }\n\n    \/\/ core extract\n    async function extractPdf(file, opts = { includeImages: true }){\n      extracted = { textPages: [], images: [] };\n      try {\n        const arr = await file.arrayBuffer();\n        if (!window.pdfjsLib) throw new Error('PDF.js library not available');\n        setProgress(2, 'Loading PDF.js document...');\n        const loading = pdfjsLib.getDocument({ data: arr });\n        const pdf = await loading.promise;\n        const total = pdf.numPages;\n        meta.textContent = `Pages: ${total} \u2022 ${file.name}`;\n        logDebug('PDF loaded pages:', total);\n\n        const maxRenderWidth = 1400;\n        for (let i = 1; i <= total; i++) {\n          setProgress(Math.round((i-1)\/total*80), `Processing page ${i}\/${total}...`);\n          const page = await pdf.getPage(i);\n          \/\/ text\n          const text = await page.getTextContent();\n          const strings = text.items.map(it => it.str || '');\n          const pageText = strings.join(' ').replace(\/\\s+\/g,' ').trim();\n          extracted.textPages.push(pageText);\n\n          \/\/ image render\n          if (opts.includeImages) {\n            try {\n              \/\/ render with scale\n              const desiredScale = 1.5;\n              const viewport = page.getViewport({ scale: desiredScale });\n              const canvas = document.createElement('canvas');\n              const ctx = canvas.getContext('2d');\n              const targetWidth = Math.min(viewport.width, maxRenderWidth);\n              const scaleFactor = targetWidth \/ viewport.width;\n              canvas.width = Math.round(viewport.width * scaleFactor);\n              canvas.height = Math.round(viewport.height * scaleFactor);\n              const renderViewport = page.getViewport({ scale: desiredScale * scaleFactor });\n              await page.render({ canvasContext: ctx, viewport: renderViewport }).promise;\n              const dataUrl = canvas.toDataURL('image\/png', 0.92);\n              extracted.images.push(dataUrl);\n            } catch (imgErr) {\n              logDebug('Image render failed for page', i, imgErr.message || imgErr);\n              extracted.images.push(null);\n            }\n          } else {\n            extracted.images.push(null);\n          }\n          await new Promise(r => setTimeout(r, 20));\n        }\n        setProgress(92, 'Extraction complete');\n        return extracted;\n      } catch (err) {\n        throw err;\n      }\n    }\n\n    function renderPreview(ex){\n      preview.innerHTML = '';\n      ex.textPages.forEach((t, idx) => {\n        const item = document.createElement('div');\n        item.className = 'p-2 bg-white rounded shadow-sm';\n        item.innerHTML = `<div style=\"font-weight:600\">Page ${idx+1}<\/div>`;\n        if (ex.images[idx]) {\n          const img = document.createElement('img');\n          img.src = ex.images[idx];\n          img.className = 'thumb mt-2';\n          item.appendChild(img);\n        } else {\n          const no = document.createElement('div');\n          no.className = 'mt-2 text-xs text-slate-400';\n          no.textContent = '[No page image]';\n          item.appendChild(no);\n        }\n        const p = document.createElement('div');\n        p.className = 'mt-2 text-xs text-slate-500';\n        p.textContent = t ? (t.length > 300 ? t.slice(0,300) + '\u2026' : t) : '[No text]';\n        item.appendChild(p);\n        preview.appendChild(item);\n      });\n    }\n\n    \/\/ build docx\n    async function buildDocx(ex, opts = { includeImages:true, filename: 'converted.docx' }){\n      if (!window.docx) throw new Error('docx library not available');\n      try {\n        const { Document, Packer, Paragraph, TextRun, Media } = docx;\n        const doc = new Document({ sections: [] });\n        const children = [];\n\n        children.push(new Paragraph({ children: [ new TextRun({ text: opts.filename.replace(\/\\.docx$\/,''), bold: true, size: 28 }) ] }));\n        children.push(new Paragraph({ children: [ new TextRun({ text: 'Converted: '+new Date().toLocaleString(), italics: true, size: 18 }) ] }));\n        children.push(new Paragraph({ children: [ new TextRun({ text: '' }) ] }));\n\n        for (let i = 0; i < ex.textPages.length; i++) {\n          const pageText = ex.textPages[i];\n          if (pageText &#038;&#038; pageText.trim()) {\n            const sentences = pageText.split(\/(?<=[.?!])\\s+\/).filter(Boolean);\n            sentences.forEach(s => children.push(new Paragraph({ children: [ new TextRun({ text: s }) ] })));\n          } else {\n            children.push(new Paragraph({ children: [ new TextRun({ text: '[No text on this page]' }) ] }));\n          }\n\n          if (opts.includeImages && ex.images[i]) {\n            try {\n              const resp = await fetch(ex.images[i]);\n              const arr = await resp.arrayBuffer();\n              const image = Media.addImage(doc, arr);\n              children.push(new Paragraph(image));\n            } catch (imgErr) {\n              logDebug('Failed to embed image for page', i+1, imgErr.message || imgErr);\n              children.push(new Paragraph({ children: [ new TextRun({ text: '[Image embed failed]' }) ] }));\n            }\n          }\n          \/\/ spacer\n          if (i < ex.textPages.length - 1) children.push(new Paragraph({ children: [ new TextRun({ text: '' }) ] }));\n          await new Promise(r => setTimeout(r, 10));\n        }\n\n        doc.addSection({ children });\n        const blob = await Packer.toBlob(doc);\n        return blob;\n      } catch (e) {\n        throw e;\n      }\n    }\n\n    async function onConvert(){\n      if (!currentFile) return showError('No PDF selected');\n      convertBtn.disabled = true;\n      try {\n        setProgress(1, 'Starting extraction');\n        const incl = includeImages.checked;\n        const outName = (fileNameInput.value || (currentFile.name.replace(\/\\.pdf$\/i,'') + '.docx')).trim();\n        const ex = await extractPdf(currentFile, { includeImages: incl });\n        renderPreview(ex);\n        setProgress(93, 'Building .docx file');\n        const blob = await buildDocx(ex, { includeImages: incl, filename: outName });\n        if (lastBlobUrl) URL.revokeObjectURL(lastBlobUrl);\n        lastBlobUrl = URL.createObjectURL(blob);\n        downloadBtn.href = lastBlobUrl;\n        downloadBtn.download = outName.toLowerCase().endsWith('.docx') ? outName : outName + '.docx';\n        downloadBtn.classList.remove('hidden');\n        resetBtn.classList.remove('hidden');\n        meta.textContent = 'Ready';\n        setProgress(100, 'Ready \u2014 click Download');\n        logDebug('Conversion success. Blob size:', blob.size);\n      } catch (err) {\n        logDebug('Conversion failed:', err && err.message ? err.message : err);\n        showError('Conversion error: ' + (err && err.message ? err.message : String(err)));\n      } finally {\n        convertBtn.disabled = false;\n      }\n    }\n\n    function onReset(){\n      currentFile = null;\n      extracted = { textPages: [], images: [] };\n      preview.innerHTML = 'Upload a PDF to preview extracted text and thumbnails.';\n      meta.textContent = 'No PDF';\n      dropHint.textContent = 'No file selected';\n      convertBtn.disabled = true;\n      downloadBtn.classList.add('hidden');\n      resetBtn.classList.add('hidden');\n      progressWrap.classList.add('hidden');\n      if (lastBlobUrl) { URL.revokeObjectURL(lastBlobUrl); lastBlobUrl = null; }\n      fileInput.value = '';\n      fileNameInput.value = '';\n    }\n\n    convertBtn.addEventListener('click', onConvert);\n    resetBtn.addEventListener('click', onReset);\n    document.addEventListener('paste', (e)=> {\n      if (e.clipboardData && e.clipboardData.files && e.clipboardData.files.length) handleFile(e.clipboardData.files[0]);\n    });\n\n    \/\/ quick health checks on load\n    window.addEventListener('load', ()=> {\n      logDebug('Page loaded. Checking libraries...');\n      if (!window.pdfjsLib) {\n        logDebug('pdfjsLib missing. PDF.js failed to load from CDN.');\n        debugEl.textContent += '\\nPDF.js library missing \u2014 please ensure you have internet and open file over http(s).';\n      } else {\n        logDebug('pdfjsLib ok. Worker src:', (pdfjsLib && pdfjsLib.GlobalWorkerOptions && pdfjsLib.GlobalWorkerOptions.workerSrc) || 'n\/a');\n      }\n      if (!window.docx) {\n        logDebug('docx missing. docx UMD failed to load.');\n        debugEl.textContent += '\\ndocx library missing \u2014 please ensure you have internet.';\n      } else {\n        logDebug('docx ok.');\n      }\n\n      \/\/ common user mistake: opening via file:\/\/ (local file)\n      if (location.protocol === 'file:') {\n        debugEl.textContent += '\\nWarning: you opened this page using file:\/\/ \u2014 open it over http(s) instead (upload to your site or run a local server). file:\/\/ often breaks PDF.js worker and fetch calls.\\n';\n        alert('Warning: open this HTML using http:\/\/ or https:\/\/ (upload to your web host). Opening with file:\/\/ often breaks functionality.');\n      }\n    });\n  })();\n  <\/script>\n<\/body>\n<\/html>\n\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>PDF \u2192 Word Converter \u2014 Debug Friendly Single File PDF \u2192 Word Converter Client-side conversion. Best opened via https:\/\/ or http:\/\/. No server required Select or drop a PDF Click to upload or drop a PDF Try small test PDFs first (1\u20135 pages). No file selected Include page images (preserve layout) Output filename Convert Download [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"class_list":["post-1145","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/bookfax.com\/index.php\/wp-json\/wp\/v2\/pages\/1145","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bookfax.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/bookfax.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/bookfax.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bookfax.com\/index.php\/wp-json\/wp\/v2\/comments?post=1145"}],"version-history":[{"count":4,"href":"https:\/\/bookfax.com\/index.php\/wp-json\/wp\/v2\/pages\/1145\/revisions"}],"predecessor-version":[{"id":1150,"href":"https:\/\/bookfax.com\/index.php\/wp-json\/wp\/v2\/pages\/1145\/revisions\/1150"}],"wp:attachment":[{"href":"https:\/\/bookfax.com\/index.php\/wp-json\/wp\/v2\/media?parent=1145"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}