Skip to content

Commit 83f7589

Browse files
committed
fix: OCR scanned pages when mixed with text pages
1 parent e144e0a commit 83f7589

3 files changed

Lines changed: 44 additions & 0 deletions

File tree

packages/markitdown-ocr/src/markitdown_ocr/_pdf_converter_with_ocr.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,13 @@ def convert(
280280
text_content = page.extract_text() or ""
281281
if text_content.strip():
282282
markdown_content.append(text_content.strip())
283+
else:
284+
# Scanned page with no image objects or text layer:
285+
# render the whole page and OCR it, so it doesn't
286+
# silently vanish from the output.
287+
page_ocr = self._ocr_page(page, ocr_service)
288+
if page_ocr:
289+
markdown_content.append(page_ocr)
283290
else:
284291
# No OCR, just extract text
285292
text_content = page.extract_text() or ""
@@ -337,6 +344,27 @@ def _extract_page_images(self, pdf_bytes: io.BytesIO, page_num: int) -> list[dic
337344

338345
return images
339346

347+
def _ocr_page(self, page: Any, ocr_service: LLMVisionOCRService) -> str:
348+
"""
349+
Render a single pdfplumber page to PNG and run OCR on it.
350+
351+
Returns a formatted OCR block, a 'no text' marker if OCR is empty,
352+
or an error marker if rendering/OCR raises.
353+
"""
354+
try:
355+
page_img = page.to_image(resolution=300)
356+
img_stream = io.BytesIO()
357+
page_img.original.save(img_stream, format="PNG")
358+
img_stream.seek(0)
359+
360+
ocr_result = ocr_service.extract_text(img_stream)
361+
text = (ocr_result.text or "").strip()
362+
if text:
363+
return f"*[Image OCR]\n{text}\n[End OCR]*"
364+
return "*[No text could be extracted from this page]*"
365+
except Exception as e:
366+
return f"*[Error processing page {page.page_number}: {str(e)}]*"
367+
340368
def _ocr_full_pages(
341369
self, pdf_bytes: io.BytesIO, ocr_service: LLMVisionOCRService
342370
) -> str:
Binary file not shown.

packages/markitdown-ocr/tests/test_pdf_converter.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,22 @@ def test_pdf_scanned_report(svc: MockOCRService) -> None:
189189
assert _convert("pdf_scanned_report.pdf", svc) == expected
190190

191191

192+
# ---------------------------------------------------------------------------
193+
# Mixed text + scanned pages: a page with neither image objects nor a text
194+
# layer must still be OCR'd inline rather than silently dropped.
195+
# Regression test for microsoft/markitdown#1791.
196+
# ---------------------------------------------------------------------------
197+
198+
199+
def test_pdf_text_then_blank_page_is_ocred(svc: MockOCRService) -> None:
200+
expected = (
201+
"## Page 1\n\n\n"
202+
"First page content, extracted as text.\n\n\n"
203+
f"## Page 2\n\n\n{_OCR_BLOCK}"
204+
)
205+
assert _convert("pdf_text_then_blank.pdf", svc) == expected
206+
207+
192208
# ---------------------------------------------------------------------------
193209
# Scanned PDF fallback path (pdfplumber finds no text → full-page OCR)
194210
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)