From 1f6aacf7444c7f1339c2e0b2a0d1364fb626226c Mon Sep 17 00:00:00 2001 From: Yi-111-a <153097222+Yi-111-a@users.noreply.github.com> Date: Tue, 20 Oct 2026 07:44:12 +0800 Subject: [PATCH] fix(deepseek-ocr): keep continuation lines in non-table blocks _collect_annotation_content stopped after the first non-empty line for labels other than tables and pictures, so a multi-line text/title block lost every line after the first one. Unlimited OCR delegates to the same collector after normalizing its markers, so it lost the same lines. Now every non-table block collects consecutive non-empty lines until the next annotation or the first blank line, so all content in the first block survives until the next annotation. Empty lines, following annotations, captions, and table markup keep their boundaries. Fixes #4905 Signed-off-by: Yi-111-a <153097222+Yi-111-a@users.noreply.github.com> --- docling/utils/deepseekocr_utils.py | 9 ++--- tests/test_unlimited_ocr_markdown.py | 55 ++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/docling/utils/deepseekocr_utils.py b/docling/utils/deepseekocr_utils.py index 76a39aaca5..f7d8161796 100644 --- a/docling/utils/deepseekocr_utils.py +++ b/docling/utils/deepseekocr_utils.py @@ -158,17 +158,16 @@ def _collect_annotation_content( break ii += 1 else: - # Original logic for other labels + # Collect the block's content lines: consecutive non-empty lines up to the + # next annotation or the first blank line, so continuation lines survive. while i < len(lines): content_line = lines[i].strip() + if re.match(annotation_pattern, content_line): + break if content_line: - if re.match(annotation_pattern, content_line): - break visited_lines.add(i) content_lines.append(lines[i].rstrip()) i += 1 - if label_str not in ["figure", "image"]: - break else: i += 1 if content_lines: diff --git a/tests/test_unlimited_ocr_markdown.py b/tests/test_unlimited_ocr_markdown.py index dd0d44da46..4cabf21c15 100644 --- a/tests/test_unlimited_ocr_markdown.py +++ b/tests/test_unlimited_ocr_markdown.py @@ -71,3 +71,58 @@ def test_parse_builds_document_with_heading_and_table(): def test_response_format_enum_value(): assert ResponseFormat.UNLIMITED_OCR_MARKDOWN.value == "unlimited_ocr_markdown" + + +def test_continuation_lines_survive_in_a_text_block(): + raw = ( + "text[[100,100,900,300]]\n" + "First line\n" + "Second line\n" + "\n" + "text[[100,400,900,500]]\n" + "Next block\n" + ) + + doc = parse_unlimited_ocr_markdown(raw, PAGE_SIZE, page_no=1, filename="page.png") + markdown = doc.export_to_markdown() + + assert "First line" in markdown + assert "Second line" in markdown + assert "Next block" in markdown + texts = [t.text for t in doc.texts] + assert any("First line" in t and "Second line" in t for t in texts) + + +def test_unlimited_ocr_continuation_lines_survive_in_a_text_block(): + raw = ( + "<|det|>text [100, 100, 900, 300]<|/det|>First line\n" + "Second line\n" + "\n" + "<|det|>text [100, 400, 900, 500]<|/det|>Next block\n" + ) + + doc = parse_unlimited_ocr_markdown(raw, PAGE_SIZE, page_no=1, filename="page.png") + texts = [t.text for t in doc.texts] + + assert len(texts) >= 2 + assert any("First line" in t and "Second line" in t for t in texts) + + +def test_annotation_and_blank_line_still_end_a_block(): + raw = ( + "text[[100,100,900,300]]\n" + "First line\n" + "Second line\n" + "\n" + "\n" + "Loose trailing text\n" + "text[[100,400,900,500]]\n" + "Next block\n" + ) + + doc = parse_unlimited_ocr_markdown(raw, PAGE_SIZE, page_no=1, filename="page.png") + texts = [t.text for t in doc.texts] + + assert any(t == "First line\nSecond line" for t in texts) + assert any("Next block" in t for t in texts) + assert not any("Loose trailing text" in t for t in texts)