diff --git a/docling_core/types/doc/tokens.py b/docling_core/types/doc/tokens.py index 5da137cd..4dcfad06 100644 --- a/docling_core/types/doc/tokens.py +++ b/docling_core/types/doc/tokens.py @@ -302,14 +302,20 @@ def get_location( ysize: int = 500, # TODO review self_closing: bool = False, ): - """Get the location string give bbox and page-dim.""" - assert bbox[0] <= bbox[2], f"bbox[0]<=bbox[2] => {bbox[0]}<={bbox[2]}" - assert bbox[1] <= bbox[3], f"bbox[1]<=bbox[3] => {bbox[1]}<={bbox[3]}" - - x0 = bbox[0] / page_w - y0 = bbox[1] / page_h - x1 = bbox[2] / page_w - y1 = bbox[3] / page_h + """Get the location string given bbox and page-dim.""" + # Normalize potentially inverted coordinates instead of asserting on + # them. Near-degenerate elements (e.g. a thin rule whose layout-model + # regression produced a slightly inverted bbox) can arrive with + # bbox[0] > bbox[2] or bbox[1] > bbox[3]. The min()/max() calls below + # already emit correctly-ordered tokens, so sorting here mirrors that + # normalization and avoids crashing export_to_doctags on valid output. + left, right = sorted((bbox[0], bbox[2])) + top, bottom = sorted((bbox[1], bbox[3])) + + x0 = left / page_w + y0 = top / page_h + x1 = right / page_w + y1 = bottom / page_h x0_tok = DocumentToken.get_location_token(val=min(x0, x1), rnorm=xsize, self_closing=self_closing) y0_tok = DocumentToken.get_location_token(val=min(y0, y1), rnorm=ysize, self_closing=self_closing) diff --git a/test/test_tokens.py b/test/test_tokens.py new file mode 100644 index 00000000..50286b57 --- /dev/null +++ b/test/test_tokens.py @@ -0,0 +1,51 @@ +"""Unit tests for document tokens.""" + +from docling_core.types.doc.tokens import DocumentToken + + +def test_get_location_normal_bbox(): + """A well-ordered bbox produces correctly-ordered location tokens.""" + loc = DocumentToken.get_location( + bbox=(100.0, 200.0, 300.0, 400.0), + page_w=1000.0, + page_h=1000.0, + ) + assert loc == "" + + +def test_get_location_inverted_bbox_does_not_crash(): + """A near-degenerate, slightly inverted bbox must not raise. + + Regression test for #620: layout-model regression can emit a bbox with + bbox[1] > bbox[3] (or bbox[0] > bbox[2]) on thin/degenerate elements. + get_location already normalizes coordinates via min()/max() downstream, + so it should serialize such boxes instead of crashing export_to_doctags. + """ + # bbox from the issue: y is inverted by 7.7pt (633.39 > 625.68). + inverted = DocumentToken.get_location( + bbox=(0.0, 633.3925132751465, 100.0, 625.6789665222168), + page_w=1000.0, + page_h=1000.0, + ) + # The result must equal the same box with y-coordinates ordered correctly. + normalized = DocumentToken.get_location( + bbox=(0.0, 625.6789665222168, 100.0, 633.3925132751465), + page_w=1000.0, + page_h=1000.0, + ) + assert inverted == normalized + + +def test_get_location_fully_inverted_bbox(): + """Both axes inverted still normalizes to the ordered box.""" + inverted = DocumentToken.get_location( + bbox=(300.0, 400.0, 100.0, 200.0), + page_w=1000.0, + page_h=1000.0, + ) + ordered = DocumentToken.get_location( + bbox=(100.0, 200.0, 300.0, 400.0), + page_w=1000.0, + page_h=1000.0, + ) + assert inverted == ordered