Repository navigation
Conversation
…ot segfault Blend2D's JIT analytic fill reused a stale span length when the last coverage word of a scanline was all ones, which happens when an affine image blit's edge crosses the right clip edge and the clip width is 252..255 mod 256. The zero-length span then ran the per-pixel loop 2^32 times and walked the destination pointer off the heap. Page 7 of the PDF in docling-project#365 hits exactly that at scale 3.0 (1279 px canvas). Carry the upstream fix (blend2d/blend2d#268, for blend2d/blend2d#267) as a FetchContent PATCH_COMMAND on the pinned commit, applied once by cmake/apply_patch.cmake, and add a regression test that renders a synthetic page on the crashing geometry in a child process. Fixes docling-project#365 Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
…endering Blend2D's stroker offsets each quad of a cubic's approximation by splitting it where its tangent has turned by a fixed angle, until the split parameter falls outside (1e-5, 1 - 1e-5). On a nearly collinear quad that turns back on itself, a split can land on the turning point, where the derivative of the rest is only rounding noise. The next parameter then comes from that noise, passes the check, and the split returns the same curve, so the loop never ends. A hairline cubic of about 0.6 x 0.4 px hung get_image() inside the native call, where neither Python nor Docling's document timeout can stop it. Carry blend2d/blend2d#270 as a second FetchContent patch step: when a split returns the same curve, offset the rest of the part as its chord and leave the loop. It only triggers where the old loop could not terminate, so no output that rendered before changes. Add a regression test that renders the reporter's page in a child process with a timeout. Fixes docling-project#393 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Jeff Witt <1848307+wittjeff@users.noreply.github.com>
Contributor
|
✅ DCO Check Passed Thanks @wittjeff, all your commits are properly signed off. 🎉 |
Contributor
Merge Protections🟢 Merge protection satisfied — ready to merge. Show 1 satisfied protection🟢 Enforce conventional commitMake sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
|
This was referenced Oct 9, 2026
7 tasks
Member
|
Thanks for this contribution. This PR is on hold until it has been consolidated as described in #428. Please resubmit it as part of the topic PR listed there, with the evidence the issue asks for. |
This was referenced Oct 31, 2026
Member
|
superseded by #433 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #393. Stacked on #387: it reuses that PR's
cmake/apply_patch.cmakeand adds a secondPATCH_COMMANDstep. The first commit (d91aea1) is #387; only the second commit (c4a0d00) is new here. Merge #387 first; I can rebase this onto main if you prefer to take them separately.Problem. With rendering enabled, page 29 of the reporter's document never finished: one core at 100 %, memory flat, inside the native call, so neither Python nor Docling's
document_timeoutcould stop it. The trigger is one hairline (0 w) with round caps and joins, a line and a cubic, scaled to about 0.6 x 0.4 px on the canvas.Cause. Blend2D's stroker (
PathStroker::offset_quad()) splits each quad of the cubic's approximation where its tangent has turned by a fixed angle. One quad was nearly collinear and turned back on itself. After a split landed on the turning point, the derivative of the rest was only rounding noise, the next split parameter came from that noise, and the split returned the same curve forever. Details and measurements are in blend2d/blend2d#269 and blend2d/blend2d#270.Change.
cmake/blend2d-stroke-offset-quad-stall.patch: the Blend2D fix. If a split returns the same curve, offset the rest of the part as its chord and leave the loop. It only triggers where the old loop could not terminate, so it changes no output that was produced before (bit-identical stroked outlines in 256,446 swept cases).cmake/extlib_blend2d.cmake: apply it after the fix(render): patch Blend2D so a sheared image at the clip edge does not segfault #387 patch. Drop both when the pin moves past the upstream fixes.tests/test_unit_subpixel_stroke_render.py: the reporter's reproducer, stroked in black, rendered at scale 2 in a child process with a 60 s timeout. It also checks that the stroke's ink lands inside the stroke's box.Validation (local, Linux x86-64, GCC 13).
tests/test_unit_*.py: 239 passed, 1 skipped.tests/data/regressionat scale 2 against a build of fix(render): patch Blend2D so a sheared image at the clip edge does not segfault #387: all 3,364 page images byte-identical. Two documents hit the 180 s per-document limit in both runs; their pages that rendered in either run are identical too.🤖 Generated with Claude Code