Read complete PDFs and surface per-page OCR failures

This commit is contained in:
2026-09-06 09:42:35 +03:00
parent 01d4acf246
commit b340cf5772
4 changed files with 109 additions and 67 deletions
+39 -67
View File
@@ -1,80 +1,52 @@
/**
* PDF'ten metin çıkarma (tarayıcı içinde, pdfjs-dist ile).
* Dinamik import edilir — ana bundle'a girmez.
*/
import { readPdfPages } from './pdfDocumentReader.js';
export async function extractPdfText(file, { maxPages = 40 } = {}) {
async function openPdf(file) {
const pdfjs = await import('pdfjs-dist');
// Worker'ı Vite'ın bundle ettiği dosyadan al (CDN'e çıkmaz)
const workerUrl = (await import('pdfjs-dist/build/pdf.worker.min.mjs?url')).default;
pdfjs.GlobalWorkerOptions.workerSrc = workerUrl;
const buf = await file.arrayBuffer();
let doc;
try { doc = await pdfjs.getDocument({ data: buf }).promise; }
catch (error) {
if (error.name === 'InvalidPDFException') throw new Error('PDF dosyası bozuk veya geçerli PDF biçiminde değil.');
if (error.name === 'PasswordException') throw new Error('PDF parola korumalı; okunabilir bir kopyasını yükleyin.');
pdfjs.GlobalWorkerOptions.workerSrc = (await import('pdfjs-dist/build/pdf.worker.min.mjs?url')).default;
let task;
try {
task = pdfjs.getDocument({data:await file.arrayBuffer()});
return {doc:await task.promise,task};
}
catch(error) {
await task?.destroy();
if(error.name === 'InvalidPDFException') throw new Error('PDF dosyası bozuk veya geçerli PDF biçiminde değil.');
if(error.name === 'PasswordException') throw new Error('PDF parola korumalı; okunabilir bir kopyasını yükleyin.');
throw new Error('PDF okunamadı. Dosyayı kontrol edip yeniden deneyin.');
}
const pages = Math.min(doc.numPages, maxPages);
const parts = [];
for (let i = 1; i <= pages; i++) {
const page = await doc.getPage(i);
const content = await page.getTextContent();
parts.push(content.items.map((it) => it.str).join(' '));
}
const text = parts.join('\n\n').replace(/[ \t]+/g, ' ').trim();
return { text, pageCount: doc.numPages, usedPages: pages };
}
// Metin dosyaları için basit okuyucu
export async function extractPlainText(file) {
const text = await file.text();
return { text: text.trim(), pageCount: 1, usedPages: 1 };
}
/**
* Taranmış (görüntü) PDF için OCR — tesseract.js, Türkçe dil paketi,
* tarayıcıda. Ölçüldü: sentetik teklif cetvelinde güven 87, sayfa başı
* ~0,3 sn (Node). Yalnızca metin katmanı boşsa devreye girer; ilk 8 sayfa.
*/
export async function ocrPdfText(file, { maxPages = 8, onProgress } = {}) {
const pdfjs = await import('pdfjs-dist');
const workerUrl = (await import('pdfjs-dist/build/pdf.worker.min.mjs?url')).default;
pdfjs.GlobalWorkerOptions.workerSrc = workerUrl;
const { createWorker } = await import('tesseract.js');
const doc = await pdfjs.getDocument({ data: await file.arrayBuffer() }).promise;
const pages = Math.min(doc.numPages, maxPages);
const worker = await createWorker('tur');
const parts = [];
async function readPdf(file,{ocr=false,forceOcr=false,maxPages=Infinity,onProgress}={}) {
const {doc,task}=await openPdf(file);
let worker;
try {
for (let i = 1; i <= pages; i++) {
onProgress?.({ sayfa: i, toplam: pages });
const page = await doc.getPage(i);
const viewport = page.getViewport({ scale: 2 }); // ~150 dpi: OCR için yeterli, hızlı
const canvas = document.createElement('canvas');
canvas.width = viewport.width; canvas.height = viewport.height;
await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;
const { data } = await worker.recognize(canvas);
parts.push(data.text);
}
const result=await readPdfPages(doc,{maxPages,forceOcr,onProgress,ocrPage:ocr ? async(page)=>{
if(!worker) {
const {createWorker}=await import('tesseract.js');
worker=await createWorker('tur');
}
const viewport=page.getViewport({scale:2});
const canvas=document.createElement('canvas');
canvas.width=viewport.width;canvas.height=viewport.height;
try {
await page.render({canvasContext:canvas.getContext('2d'),viewport}).promise;
return (await worker.recognize(canvas)).data.text;
} finally { canvas.width=0;canvas.height=0; }
} : undefined});
return result;
} finally {
await worker.terminate();
try { if(worker) await worker.terminate(); }
finally { await task.destroy(); }
}
const text = parts.join('\n\n').replace(/[ \t]+/g, ' ').trim();
return { text, pageCount: doc.numPages, usedPages: pages, ocr: true };
}
export async function extractText(file, { ocr = true, onProgress } = {}) {
const name = (file.name || '').toLowerCase();
if (name.endsWith('.pdf') || file.type === 'application/pdf') {
const r = await extractPdfText(file);
// Metin katmanı yoksa taranmış PDF'tir — OCR'a düş
if (ocr && (r.text || '').replace(/\s/g, '').length < 100) {
try { return await ocrPdfText(file, { onProgress }); } catch { return r; }
}
return r;
}
export const extractPdfText=(file,options={})=>readPdf(file,{...options,ocr:false});
export const ocrPdfText=(file,options={})=>readPdf(file,{...options,ocr:true,forceOcr:true});
export async function extractPlainText(file) {
return {text:(await file.text()).trim(),pageCount:1,usedPages:1,complete:true};
}
export async function extractText(file,{ocr=true,onProgress}={}) {
if((file.name || '').toLowerCase().endsWith('.pdf') || file.type==='application/pdf')
return readPdf(file,{ocr,onProgress});
return extractPlainText(file);
}