Skip to content

fix(pptx): clamp the clip raster scale and surface rasterized regions#442

Merged
DemchaAV merged 3 commits into
developfrom
fix/pptx-clip-raster-clamp
Jul 25, 2026
Merged

fix(pptx): clamp the clip raster scale and surface rasterized regions#442
DemchaAV merged 3 commits into
developfrom
fix/pptx-clip-raster-clamp

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

The clip raster fallback aims for a 2048-pixel long edge, but that ratio was only capped from above:

double scale = Math.min(4.0, 2048.0 / Math.max(1.0, Math.max(width, height)));

The inner Math.max guards the divisor, not the result. A clip region wider than 2048 pt therefore drove the scale below 1.0 and rasterized below native size — while the picture stayed anchored at the full clip size regardless. A 3370 pt box landed at ~44 DPI, a 4000 pt box at ~37, and the deck showed a visibly blurred composite with no warning.

No existing test caught it because every other clip test uses a page a few hundred points wide, where the ratio saturates at the upper cap — the branch was unreachable from the suite.

What changed

  • Clamp the scale between native size and 4×, behind named constants (CLIP_RASTER_TARGET_PIXELS, CLIP_RASTER_MAX_SCALE, CLIP_RASTER_MIN_SCALE) that state what each bound is for. Decks whose clip regions fit a normal page are unaffected — their scale already saturated at the cap.
  • Make the fallback's cost visible. Each rasterized region is a full sub-render through the PDF backend and loses text editability inside its bounds, yet nothing reported how often it happened. PptxRenderEnvironment now counts rasterized regions and their pixel volume and emits one DEBUG line per pass on com.demcha.compose.engine.render. Silent when nothing was rasterized, so an all-vector deck adds no noise.

The summary is wired into the two paths that can reach the clip fallback. The raster-slide path turns the whole page into one picture and never gets there, so a summary there would always report zero.

Verification

  • ./mvnw -B -ntp clean verify — BUILD SUCCESS, 1519 tests (from 1518), 0 failures.
  • The new test was proven, not assumed: removing only the floor from the scale expression turns it red with its own message — "a rasterized clip must never be published below native 72 DPI".
  • The fixture asserts its own premise first (clipFragment.width() > 2048.0), so it cannot silently stop exercising the branch if the page size is ever edited.
  • docs/architecture/backend-capability-matrix.md updated in the same change, per the repository rule.

Tradeoff

Raising the 2048 target instead would inflate every ordinary clip; the target is a memory ceiling for the common case. The floor engages only above 2048 pt, where the alternative is shipping a blurred picture. Cost stays bounded — the long edge becomes max(2048, clipWidth) px for a render the caller explicitly asked for at that size.

The clip raster fallback aims for a 2048-pixel long edge, but that ratio was
only capped from above. A clip region wider than 2048 pt therefore drove the
scale below 1.0 and rasterized *below* native size, while the picture stayed
anchored at the full clip size — an A0-scale composite landed near 44 DPI and
read as visibly blurry. The inner Math.max guarded the divisor, not the result.

Clamp the scale between native size and 4x, behind named constants that say what
each bound is for. Decks whose clip regions fit a normal page are unaffected:
their scale already saturated at the upper cap, which is also why no existing
test covered this branch — every other clip test uses a page a few hundred
points wide.

Also make the fallback's cost visible. Each rasterized region is a full
sub-render through the PDF backend and loses text editability inside its bounds,
yet nothing reported how often it happened. The render environment now counts
rasterized regions and their pixel volume, and emits one DEBUG line per pass on
com.demcha.compose.engine.render — silent when nothing was rasterized, so an
all-vector deck adds no noise. It is wired into the two paths that can reach the
clip fallback; the raster-slide path turns the whole page into one picture and
never gets there.

Tests: a clip box on a 3400x2400 pt page, asserting the picture is published at
no less than native resolution. The fixture asserts its own premise — that the
clip really exceeds 2048 pt — so it cannot silently stop exercising the branch.
Removing only the floor turns it red.

Verified: full reactor clean verify green (1519 tests, 0 failures). Capability
matrix updated in the same change, per the repository rule.
@DemchaAV DemchaAV closed this Jul 25, 2026
@DemchaAV DemchaAV reopened this Jul 25, 2026
The clamp removes the implicit ~17MB cap a downscaled raster used to impose, so
a very large clip region now costs transient memory proportional to its size.
That is the intended trade — blur is worse than a transient allocation, and the
floor only engages above 2048pt — but the capability matrix described the
resolution change without its resource consequence.
@DemchaAV
DemchaAV force-pushed the fix/pptx-clip-raster-clamp branch from a5d037c to f49e99c Compare July 25, 2026 12:34
@DemchaAV
DemchaAV merged commit 9fbf76d into develop Jul 25, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/pptx-clip-raster-clamp branch July 25, 2026 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant