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

Closed
#343 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.