Repository navigation
Segfault in get_image(scale=3.0) on a scanned CCITT page (regression in 7.10.0) #365
Description
Summary
PageParseResult.get_image(scale=3.0) segfaults on page 7 of a public scanned PDF. The same page renders at 2.99 and at 3.01, and every other page of the document renders at 3.0. It's a regression: 7.9.0 renders the page, 7.10.0 through 7.22.1 segfault.
We hit it through Docling: RapidOCR renders every page at its default scale=3.0, so a full conversion with OCR on crashes the whole process.
Reproduce
The PDF is a Honda engine owner's manual (39 pages, Acrobat Capture 3.0 "Searchable Image"):
https://cdn.powerequipment.honda.com/engines/pdf/manuals/31ZH7600.pdf (sha256 cf9ddf8f0890ed28082d9c5dfbea383635755dbbc56d56c0c6c68d15e64c6141)
# pip install docling-parse==7.22.1
# python -X faulthandler repro.py 31ZH7600.pdf 3.0 7
import sys
from docling_parse.pdf_parser import DoclingThreadedPdfParser, RenderConfig, ThreadedPdfParserConfig
path, scale = sys.argv[1], float(sys.argv[2])
pages = [int(p) for p in sys.argv[3:]] or None
render = RenderConfig()
render.scale = 1.0
parser = DoclingThreadedPdfParser(parser_config=ThreadedPdfParserConfig(threads=1, render_config=render))
key = parser.load(path, page_numbers=pages)
for result in parser.iterate_results():
n = result.page_number
print(f"page {n}: size {result.page_width:.2f} x {result.page_height:.2f} pt, rendering at {scale}", flush=True)
img = result.get_image(scale=scale)
print(f"page {n}: ok {img.size}", flush=True)
parser.unload(key)
print("done")Output on 7.22.1 (python:3.12-slim, linux/arm64; linux/amd64 crashes the same way):
page 7: size 426.24 x 598.56 pt, rendering at 3.0
Fatal Python error: Segmentation fault
Current thread 0x0000ffff97a874c0 (most recent call first):
File ".../docling_parse/pdf_parser.py", line 1378 in _render_image_at_scale
File ".../docling_parse/pdf_parser.py", line 1443 in get_image
File "/w/repro.py", line 18 in <module>
It doesn't depend on threading: threads=1, a single page (page_numbers=[7]), and no other work in the process.
What varies
Page 7 at different scales (7.22.1):
| scale | 2.0 | 2.5 | 2.8 | 2.9 | 2.95 | 2.99 | 3.0 | 3.01 | 3.05 | 3.1 | 3.5 | 4.0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| page 7 | ok | ok | ok | ok | ok | ok | SIGSEGV | ok | ok | ok | ok | ok |
Versions (page 7, scale 3.0):
| docling-parse | 7.0.0 | 7.5.0 | 7.8.0 | 7.8.1 | 7.9.0 | 7.10.0 | 7.12.1 | 7.18.0 | 7.21.0 | 7.22.1 |
|---|---|---|---|---|---|---|---|---|---|---|
| result | ok | ok | ok | ok | ok (1278×1794) | SIGSEGV | SIGSEGV | SIGSEGV | SIGSEGV | SIGSEGV |
7.10.0 is #305 (2229782), which changed canvas sizing and added the rotation path in src/render/blend2d_renderer.h. That might be a place to start. We haven't located the faulting line.
About the page: its crop box is 426.24 × 598.56 pt, and its media box is 624.24 × 796.56 pt. It has no /Rotate. Its only content is one 2592×3312 1-bit /CCITTFaxDecode image (/K -1, /Uncompressed true, /DeviceGray) plus an invisible OCR text layer. Page 6 of the same file has the same structure (a 2568×3299 CCITT G4 image) and renders at every scale above.
Environment
- docling-parse 7.22.1 (also 7.10.0 to 7.21.0), from PyPI wheels
- Python 3.12, Debian (
python:3.12-slim), linux/arm64 native, and linux/amd64 - Through Docling 2.131.0 (
ThreadedDoclingParseDocumentBackend, RapidOCR defaultscale=3.0), the full pipeline crashes the same way;PyPdfiumDocumentBackendconverts the file.
Activity
wittjeff commentedon Oct 6, 2026
Thanks for the precise report, it made this straightforward to pin down.
Root cause. This is a bug in Blend2D's JIT analytic fill, not in docling-parse code. The page is one CCITT bitmap placed with a 0.17° skew, so it is blitted through the affine path. At scale 3.0 the canvas is 1279 px wide; 1279 % 256 = 255, which puts the clip's x_end cell on bit 63 of the last 64-bit coverage word in Blend2D's rasterizer. The skewed top edge crosses the right clip edge within row 0 and flags every cell of that word. Blend2D's
FillAnalyticPartthen re-enters its bit-scan loop with a stale span length (0, left over from the constant-coverage loop) instead of 64, computes a zero-length span, and its per-pixeldo/whileloop runs 2^32 times until the destination pointer leaves the heap. Details, a 30-line standalone reproducer, and the gdb trace are in blend2d/blend2d#267; the fix is blend2d/blend2d#268.Why only 3.0, and why only since 7.10.0. The condition is
canvas_width % 256in 252..255 plus an image edge that reaches the clip edge. 2.99 and 3.01 give 1275 and 1283 px canvases, which are outside that range (2.992..3.0 all crash, 2.991 and 3.001 do not). Affine image blits exist since v5.9; 7.9.0 rendered the page only because its int-truncated crop box produced a 1278 x 1794 canvas with the top edge 1.7 px higher, which kept the skewed edge above the canvas. #305 moved the geometry by about 2 px into the failing configuration. The same crash reproduces with Blend2D alone for any pattern or image fill in this configuration, independent of CPU features, thread count, sampling quality, or source image size.Fix. #387 carries the Blend2D fix as a patch on the pinned commit until the pin moves past the upstream merge, and adds a regression test with a synthetic page on the crashing geometry. With it, page 7 renders at 3.0, all 39 pages render, and the patched output is byte-identical to Blend2D's reference pipeline.