Skip to content

Commit e9d4963

Browse files
hBouananeclaude
andcommitted
fix(documents): never emit a page that has no content
A block that fits in no column of a given page shape produced a blank image on every remaining page, each carrying an annotation claiming content the image did not have - and the block stayed at the head of the queue, so the loop ran to max_pages. The block is now dropped: a page with a confident label and no ink is worse than a page that is missing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent be840a6 commit e9d4963

2 files changed

Lines changed: 85 additions & 2 deletions

File tree

src/ocrsmith/core/documents/flow.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ def iter_pages(
107107

108108
page_number = 0
109109
while pending and page_number < max_pages:
110-
page_number += 1
111110
image = self._new_canvas(spec, background)
112111
regions: list[Region] = []
113112

@@ -116,14 +115,25 @@ def iter_pages(
116115
if footer_template and spec.footer_height:
117116
block = ContentBlock(
118117
RegionType.PAGE_NUMBER,
119-
text=str(footer_template).replace("{page}", str(page_number)),
118+
text=str(footer_template).replace("{page}", str(page_number + 1)),
120119
)
121120
regions.extend(self._draw_band(image, block, spec.footer_box, typography, direction, rng))
121+
furniture = len(regions)
122122

123123
pending = self._fill_columns(
124124
image, pending, spec, typography, direction, regions, table_style, rng
125125
)
126126

127+
if len(regions) == furniture:
128+
# Nothing could be placed anywhere on this page, so the leading block does
129+
# not fit *any* column of this page shape. Emitting the page would produce
130+
# a blank image with a confident label, and keeping the block would loop
131+
# until max_pages; dropping it is the only honest option.
132+
if pending:
133+
pending.pop(0)
134+
continue
135+
136+
page_number += 1
127137
page = Page(spec.width, spec.height, tuple(regions), direction).clipped()
128138
yield RenderedPage(image, page, page_number)
129139

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""A page that describes nothing must never be emitted.
2+
3+
A block that fits in no column of a given page shape used to produce a blank image on
4+
every remaining page, each carrying an annotation that claimed content the image did not
5+
have. The block is dropped instead, because a page with a confident label and no ink is
6+
worse than a page that is missing.
7+
"""
8+
9+
import random
10+
from pathlib import Path
11+
12+
import pytest
13+
14+
from ocrsmith.core.documents import (
15+
DocumentBuilder,
16+
DocumentRenderer,
17+
PageSpec,
18+
TypographySampler,
19+
)
20+
21+
FONT_DIR = Path(__file__).resolve().parents[1] / "assets" / "fonts"
22+
23+
pytestmark = pytest.mark.skipif(not FONT_DIR.exists(), reason="bundled fonts unavailable")
24+
25+
26+
@pytest.fixture
27+
def typography():
28+
fonts = sorted(FONT_DIR.glob("NotoSansArabic-*.ttf"))
29+
return TypographySampler(fonts, body_size_range=(16, 18)).sample(random.Random(3))
30+
31+
32+
@pytest.fixture
33+
def renderer():
34+
return DocumentRenderer()
35+
36+
37+
def test_every_emitted_page_carries_at_least_one_region(renderer, typography):
38+
content = DocumentBuilder().title("Title").paragraph("Body text goes here.").build()
39+
40+
pages = renderer.render(content, PageSpec.from_paper("a6", dpi=90), typography, max_pages=10)
41+
42+
assert pages
43+
for page in pages:
44+
assert page.page.regions
45+
46+
47+
def test_an_unplaceable_block_is_dropped_rather_than_blanking_pages(renderer, typography):
48+
# A table far too wide for the column fits nowhere, and tables never split.
49+
rows = [[f"column-{index}" * 4 for index in range(12)] for _ in range(3)]
50+
content = DocumentBuilder().paragraph("Readable prose.").table(rows).build()
51+
52+
pages = renderer.render(content, PageSpec.from_paper("a6", dpi=80), typography, max_pages=10)
53+
54+
assert pages
55+
assert all(page.page.regions for page in pages)
56+
57+
58+
def test_page_numbers_stay_dense_when_a_block_is_dropped(renderer, typography):
59+
rows = [[f"wide-{index}" * 5 for index in range(10)] for _ in range(2)]
60+
content = DocumentBuilder().paragraph("First.").table(rows).paragraph("Second.").build()
61+
62+
pages = renderer.render(content, PageSpec.from_paper("a6", dpi=80), typography, max_pages=10)
63+
64+
assert [page.number for page in pages] == list(range(1, len(pages) + 1))
65+
66+
67+
def test_a_document_of_nothing_but_unplaceable_content_yields_no_pages(renderer, typography):
68+
rows = [[f"enormous-{index}" * 8 for index in range(14)]]
69+
content = DocumentBuilder().table(rows).build()
70+
71+
pages = renderer.render(content, PageSpec.from_paper("a6", dpi=80), typography, max_pages=10)
72+
73+
assert pages == []

0 commit comments

Comments
 (0)