docling-project / docling-project/docling-parse
get_shape_lines() and get_connected_shape_bounding_boxes() return raw PDF coordinates while text cells are relative to the page boundary
- Dominant language
- C++
- Stars
- 333
- Forks
- 80
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 12
Description
## Summary
`PageParseResult.get_shape_lines()` and `PageParseResult.get_connected_shape_bounding_boxes()` return boxes in raw PDF user space. The text cells of the same page are relative to the page boundary chosen at decode time (`page_boundary`, the crop box by default), and `page_width` / `page_height` are the boundary size. On any page whose boundary does not start at (0, 0), shapes and text therefore disagree by exactly the boundary origin.
docling's `ThreadedDoclingParsePageBackend` inherits this: it only flips the y axis with the page height, so its `get_shape_lines()` and `get_connected_shape_bounding_boxes()` are displaced by the crop-box origin as well.
## Reproduction
A one-page PDF with media box `[0 0 400 300]`, crop box `[50 40 350 240]`, the word `Hello` drawn at user-space (100, 120), and a filled 40x1 point underline directly below it (`100 116 40 1 re f`). Parsed with `DoclingThreadedPdfParser` defaults, then converted to a top-left frame with the reported page height:
| Item | Reported | Expected |
|---|---|---|
| `page_width`, `page_height` | 300, 200 (the crop box) | 300, 200 |
| word `Hello` (from `get_page()`) | l 50.0, t 111.4, r 77.3, b 122.5 | as reported |
| underline (`get_connected_shape_bounding_boxes()`) | l 100.0, t 83.0, r 140.0, b 84.0 | l 50.0, t 123.0, r 90.0, b 124.0 |
The word is normalized to the crop box (user-space x 100 becomes 50); the underline is not (x stays 100), and its y is flipped with the crop-box height from raw coordinates, so it lands 40 points too high.
A public document that shows it: WSDOT contract 9424, Appendix U2, Existing Utility Listing, , page 6. Its crop box is `[-11.99 11.96 1212.01 803.96]`. The underline drawn under `Project Owner: WSDOT` is reported at x 91.6 to 310.3, y 50.2 (top-left frame), while the word `WSDOT` sits at x 105 to 126, y 57 to 62. Moved by the boundary origin, the underline is at x 103.6 to 322.3, y 62.2: exactly under the text.
## Where it matters
- docling reads these shapes in `BaseOcrModel` (when the backend has no `has_content_in()`) to decide which layout clusters contain non-text content and need OCR, and in the `visualize_cells` debug drawing.
- Anything built on the API that relates drawn geometry to text, for example finding the ruled frame of a table from the connected shapes, gets boxes that are off by the crop-box origin. That is how this was found: the frame of a full-page table appeared 12 points above and to the left of the table's text.
## Suggested fix
Translate each shape box by the boundary origin before returning it, in the same place that already knows the boundary: `_get_boundary_bbox(page_dim, boundary_type)`, which `page_width` / `page_height` use. The regression test can build the shifted-crop-box page in memory with `tests/pdf_builder.py`, so no binary fixture is needed. I have this change ready and will open a pull request that references this issue.
Rotated pages are not covered by that test; the translation matches how text cells are normalized for the unrotated case, which is the only case I verified.
## Environment
docling 2.126.0, docling-parse 7.17.0, docling-ibm-models 4.0.2, docling-core 2.95.0; macOS 26.6 (Apple M1 Pro), Python 3.12.12, wheels from PyPI.
Contributor guide
Research direction
Read _get_boundary_bbox(page_dim, boundary_type), which already supplies the boundary used for page dimensions, then inspect the shape-returning methods and tests/pdf_builder.py. Build the shifted-crop-box page described in the issue and verify that shape boxes align with text cells for the unrotated case.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 38/100