markitdown-ocr: columns are glued together on PDF pages that contain an embedded image
Package: markitdown-ocr 0.1.1 (the block is unchanged on current main, b8f79c5), with markitdown 0.1.8 and pdfplumber 0.11.10.
What happens
When OCR is enabled and a PDF page contains at least one embedded image (a logo, for instance), PdfConverterWithOCR drops the text it could have extracted normally and rebuilds the page from raw page.chars, joined with "":
page.chars carries no separator between adjacent columns, so text from different columns of the same line comes out glued together. The same page is extracted correctly when no OCR client is configured, so this only affects pages that have an image and OCR turned on. Invoices, letters and forms with a logo are the typical case.
Repro
import io
from types import SimpleNamespace
import fitz # PyMuPDF
from markitdown import MarkItDown
# One page: two columns sharing a baseline, plus a small image (think: a logo).
doc = fitz.open()
page = doc.new_page(width=595, height=842)
page.insert_text((50, 100), "Customer Name", fontsize=11)
page.insert_text((350, 100), "Vendor Name Ltd", fontsize=11)
pix = fitz.Pixmap(fitz.csRGB, fitz.IRect(0, 0, 60, 40), False)
pix.set_rect(pix.irect, (200, 30, 30))
page.insert_image(fitz.Rect(50, 30, 110, 70), stream=pix.tobytes("png"))
pdf = doc.tobytes()
def fake_create(**_kwargs):
return SimpleNamespace(
choices=[SimpleNamespace(message=SimpleNamespace(content="LOGO"))]
)
llm = SimpleNamespace(chat=SimpleNamespace(completions=SimpleNamespace(create=fake_create)))
with_ocr = MarkItDown(enable_plugins=True, llm_client=llm, llm_model="x")
without_ocr = MarkItDown(enable_plugins=True)
print("WITH OCR :", repr(with_ocr.convert_stream(io.BytesIO(pdf)).markdown))
print("WITHOUT OCR:", repr(without_ocr.convert_stream(io.BytesIO(pdf)).markdown))Output:
WITH OCR : '## Page 1\n\n*[Image OCR]\nLOGO\n[End OCR]*\n\nCustomer NameVendor Name Ltd'
WITHOUT OCR: '## Page 1\n\nCustomer Name Vendor Name Ltd'Expected Customer Name Vendor Name Ltd in both cases. (The fake client keeps the repro off any real model; what is under test is the text extraction, not the OCR.)
The existing test suite encodes it
tests/test_pdf_converter.py::test_pdf_complex_layout expects ItemQuantity and Widget A5. In pdf_complex_layout.pdf, "Item" spans x=72–95 and "Quantity" x=216–261 (about 120 pt apart on the same line), so the snapshot is the glued form.
Proposed fix
Use page.extract_text_lines(), which keeps inter-word spacing, instead of joining chars by hand. I have a small PR ready.
I searched the open issues and PRs before filing: none reports this, and #2276 re-indents this block into a try/except but keeps the "".join.
Source: microsoft/markitdown