Return only large prose blocks that look like running paragraphs. PyMuPDF often groups an entire tabular column ("DS-Ulysses 629.9 418.3 ...") into a single text block, so the legacy ``_find_body_text_blocks`` filter catches table cells too aggressively. For deciding whether we have wa
(page, *, min_chars: int = 200)
| 455 | |
| 456 | |
| 457 | def _find_paragraph_blocks(page, *, min_chars: int = 200) -> list[tuple[float, float, float, float, str]]: |
| 458 | """Return only large prose blocks that look like running paragraphs. |
| 459 | |
| 460 | PyMuPDF often groups an entire tabular column ("DS-Ulysses 629.9 418.3 ...") |
| 461 | into a single text block, so the legacy ``_find_body_text_blocks`` filter |
| 462 | catches table cells too aggressively. For deciding whether we have walked |
| 463 | out of a table region we want a stricter notion: only blocks whose total |
| 464 | text mass and line count look like real prose count as paragraph blocks. |
| 465 | """ |
| 466 | results: list[tuple[float, float, float, float, str]] = [] |
| 467 | blocks = page.get_text("dict", flags=fitz.TEXT_PRESERVE_WHITESPACE)["blocks"] |
| 468 | for block in blocks: |
| 469 | if block.get("type") != 0: |
| 470 | continue |
| 471 | lines = block.get("lines", []) |
| 472 | full_text = "" |
| 473 | for line in lines: |
| 474 | for span in line.get("spans", []): |
| 475 | full_text += span.get("text", "") |
| 476 | full_text = full_text.strip() |
| 477 | if len(full_text) < min_chars: |
| 478 | continue |
| 479 | if CAPTION_RE.match(full_text): |
| 480 | continue |
| 481 | # Real prose paragraphs have many lines and few numeric-heavy lines. |
| 482 | if len(lines) < 3: |
| 483 | continue |
| 484 | numeric_line_share = 0 |
| 485 | for line in lines: |
| 486 | line_text = "".join(s.get("text", "") for s in line.get("spans", [])).strip() |
| 487 | if _looks_like_data_row(line_text): |
| 488 | numeric_line_share += 1 |
| 489 | if numeric_line_share > len(lines) * 0.4: |
| 490 | continue |
| 491 | bb = block["bbox"] |
| 492 | results.append((bb[0], bb[1], bb[2], bb[3], full_text)) |
| 493 | results.sort(key=lambda b: b[1]) |
| 494 | return results |
| 495 | |
| 496 | |
| 497 | def _count_paragraph_text_chars_in_bbox( |
no test coverage detected