Repository navigation
Conversation
The stroke branch of render_shape painted straight onto the page, so a stroke under a curved clip (or under one W over several rectangles) ran past the clip. The clip tests also used the path's own bbox: a horizontal or vertical rule has a zero-area bbox, the mask area came out empty, and the rule disappeared (a regression since 7.15.0). A stroke whose path lies just outside a rectangular clip, but whose width reaches inside it, was dropped for the same reason. - size the clip tests and the mask window by the stroke's reach - paint the stroke through the clip mask, like the fill - rasterise fill and stroke into an A8 coverage window and composite with fill_mask, instead of a PRGB32 layer - build the mask once over the clip box and reuse it while consecutive shapes share the clip; fall back to a per-shape mask when the clip box is too large to cache - build_clip_mask: treat a group of several rectangle subpaths as needing the mask, so a shape outside the union is not painted unclipped Fixes docling-project#378 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 branch has not been deployed
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 #378.
Problem
render_shapesends the fill through the non-rectangular clip mask, but not the stroke. The stroke is drawn directly onto the page. All the clip tests also use the bbox of the path itself, not the area the stroke paints. Measured on the 7.22.2 wheel and onmain(e0e678c):Wover several rectangles, which also goes through the mask.build_clip_maskonly checks area overlap for groups that contain a curved path. A shape outside a two-rectangleWgets no mask and is painted with no clip at all.Changes
src/render/blend2d_renderer.h:render_shapecomputes a paint box from the stroke's reach: half the width, times √2 for square caps, times the miter limit for miter joins. The rectangle reject test and the mask window use this box.ctx.fill_maskin the paint colour. This replaces the PRGB32 layer, and the output is pixel-identical on the cases below.fill_maskuses the context's blend mode and global alpha. Fill and stroke still composite separately, in PDF order, each with its own alpha.clip_state_canvas_bbox). It is reused while consecutive shapes have an equal clip state (same_clip_state), and each shape reads only its own window (multiply_a8_by_a8with an offset). The cache is reset inset_size. If the clip box is larger than the 8192 px limit ofbuild_clip_mask, the mask is built over the shape's own window and is not kept.build_clip_masktreats a group with more than one subpath as needing the mask. A shape outside the union then counts as clipped away.Bitmaps and text keep their own
build_clip_maskcalls. They gain only the grouped-rectangle fix.Performance
The reporter's candidate (a separate PRGB32 stroke layer per shape) was 2.5x slower on a dense circular-clip page. This is relevant to the CAD slowdown in docling#4490. CPU time per page render at scale 4, median of 7, with base and fix built with the same compiler and run alternately:
WThe second row costs more because
maindrops many of those rules and draws the rest without the clip. The circular-clip row is faster thanmain, because each coverage window covers only the part of the line inside the clip box.Tests
New in
tests/test_unit_clipping.py. All five fail onmainand pass here:W: nothing outside the rectangles or in the gap between themUnit lane (
tests/test_unit_*.py): 237 passed, 6 skipped.Regression suite
test_rendered_pages_match_groundtruthpasses on this branch against the pinned dataset (6d3458e2). Groundtruth does not need to be regenerated. The other 21 tests in that file also pass. I ran it onmainand on this branch and compared the per-page image metrics: 29 of 940 pages change, all within tolerance. To check the direction of each change, I rendered those pages with both builds and with PDFium at scale 2. Inside the pixels that differ betweenmainand this branch:Typical changes: a stroked rounded-pill border that spilled outside its clip (
921558209587984065-1.pdf, error vs PDFium 146 → 1.4), table rules under clips (15424333971388669153-2.pdfp4,14570626672493314948-53.pdfp34–38,11926187848345237766-2.pdf), and a dashed border that PDFium clips away (7143857365833452481-1.pdf).Note
#390 also edits
render_shape(the tiling paint). Whichever PR merges second needs a small rebase.AI-assisted (Claude Code). I checked the reproductions, measurements and regression comparison above locally.
🤖 Generated with Claude Code