Skip to content

Treat repeated stitching Bounds as the empty subdomain they describe - #9

Merged
soadzoor merged 1 commit into
soadzoor:mainfrom
sebgoubier:fix/stitching-bounds-plateau
Sep 17, 2026
Merged

soadzoor merged 1 commit into
soadzoor:mainfrom
sebgoubier:fix/stitching-bounds-plateau

Conversation

@sebgoubier

Copy link
Copy Markdown
Contributor

What this fixes

A real PDF fails to render with A stitching PDF function has unordered Bounds. Probed at the failure point:

domain = [0, 1]
bounds = [0.14286, 0.14286]

The bounds are not out of order. They are equal, which describes an empty subdomain — and an empty subdomain can never be selected.

evaluateStitching picks the segment with bounds.findIndex(bound => input < bound). For equal bounds B:

  • input < B → segment before the plateau
  • input >= B → findIndex returns -1 → the last segment

The segment between the two equal bounds is unreachable by construction, and its subfunction is never evaluated. Rejecting the function discards the whole page over a subfunction that would never run. There is also no division-by-zero risk in the degenerate interval, precisely because it is never selected.

So the check now rejects only a decreasing bound, which is genuine disorder. Everything else is untouched: the last bound may still equal Domain[1] under the existing ISO exception, and the multi-segment empty-domain guard above still fires first — the domain: [1, 1] case in testStitchingFunctions still rejects, through that guard.

Both evaluators

The same validation and the same selection logic exist twice: nativeFunctions.ts for the parser and heprFunctionEvaluator.ts for the renderer-owned store. The first fix only moved the error message from one to the other, so both are updated together.

The new case in test-hepr-function-evaluator.mjs goes through the existing parity table, so the two evaluators are asserted to agree on it. In both new cases the unreachable segment is high, whose outputs start at 10 — selecting it by mistake would be unmistakable rather than subtle.

Checks

  • test-native-function-semantics.mjs, test-hepr-function-evaluator.mjs — pass
  • npm test — tsc --noEmit clean, 36 / 37 fast files pass
  • npm run test:integration — 41 / 41
  • npm run test:unit — 66 / 68
  • the source PDF gets past this error (it then hits a gradient subdivision limit, unrelated)

The two failing files (test-text-lod-core.mjs, test-room-segment-extractor.mjs) also fail on main; Windows-only path failures.

Scope

Stitching bounds only. Independent of #4 through #8.

Refs #2

A real PDF declares Bounds [0.14286, 0.14286] over Domain [0, 1] and
failed to render as unordered. The bounds are not out of order: they are
equal, which describes an empty subdomain.

An empty subdomain can never be selected. Segment selection takes the
first bound strictly greater than the input, so an input below the
plateau lands before it and an input at or above it falls through to the
last segment. The subfunction between two equal bounds is unreachable by
construction, which is also why the degenerate interval cannot divide by
zero. Rejecting the function discarded a whole page over a subfunction
that would never run.

Only a decreasing bound is disorder now. The ISO exception letting the
last bound equal Domain[1] is untouched, and the multi-segment
empty-domain guard still fires first, so the domain [1, 1] case keeps
rejecting through that guard.

The validation and the selection exist twice, once in the parser and
once in the renderer-owned evaluator; fixing only the first moved the
error message from one to the other, so both are updated. The evaluator
case joins the existing parity table, so the two are asserted to agree.
In both new cases the unreachable segment is the one whose outputs start
at 10, so selecting it by mistake could not pass unnoticed.

Refs soadzoor#2

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@soadzoor

Copy link
Copy Markdown
Owner

Looks good to me, thank you!

@soadzoor
soadzoor merged commit 1f6e34f into soadzoor:main Sep 17, 2026
1 check passed
soadzoor added a commit that referenced this pull request Sep 17, 2026
- 0.1.30
- Fix: use native ESM paths in Vite config
- Prefer usable PDF output with bounded raster fallback and approximation warnings
- Strengthen CFF BaseFontBlend regression coverage
- Merge pull request #10 from sebgoubier/fix/cff-basefontblend-is-not-mm
- Clarify stitching bounds tolerance and expand regression tests
- Merge pull request #9 from sebgoubier/fix/stitching-bounds-plateau
- Feat(pdf): Add support for ICC color profiles (qcms, lcms, and alternate) with fallback
- Fix(pdf): recover Flate EOL padding without losing decoded output
- Feat(pdf): synthesize cloudy borders for Square annotations
- Feat(pdf): support underline borders on Square annotations
- Feat(pdf): synthesize Square annotations with validated geometry
- Test: reject invalid CIDSystemInfo strings
- Merge pull request #5 from sebgoubier/fix/cid-system-info-nul-padding
- Fix(tests): handle file URLs correctly on Windows
- Merge pull request #4 from sebgoubier/fix/ccitt-b1-strictly-right-of-a0
- Stop rejecting BaseFontBlend as a Multiple Master CFF font
- Treat repeated stitching Bounds as the empty subdomain they describe
- Trim fixed-width NUL padding in /CIDSystemInfo strings
- Fix CCITT b1 selection so vertical modes cannot move backwards

Source-Revision: 5d761c9
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.

2 participants