Skip to content

feat(preflight): evaluate optional-content membership for print and screen (#115) - #157

Open
mberrys wants to merge 6 commits into
devfrom
cc/issue-115-ocmd-membership
Open

mberrys wants to merge 6 commits into
devfrom
cc/issue-115-ocmd-membership

Conversation

@mberrys

@mberrys mberrys commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Closes #115

What changed

hidden-layers now evaluates every optional-content governor it meets under both the View and the Print usage (default configuration with its /AS events applied) and reports what it finds:

  • Governors covered: groups and membership dictionaries (/P AnyOn, AllOn, AnyOff, AllOff and /VE expressions) on marked content (BDC, including inline dictionaries the content processor cannot evaluate), Form XObject /OC entries (Do), and annotation /OC entries.
  • Reported: membership hidden in print, and content whose screen and print visibility differ, in either direction, with evidence.view_state, print_state, policy, ocg_names, governor, governor_source, divergence.
  • A governor that cannot be evaluated (unparseable expression, unknown group state) is an error finding with inspection_complete: false, not a pass.
  • Overlay row hidden-layers-ocmd is landed (closed by hidden-layers); hidden-layers measures, limitations and evidence rewritten.

Existing behaviour is kept: a plain group hidden in print from marked content is still reported once by name, and the golden corpus is unchanged.

Proof

Source SHA under test: 2478cd80. Fixture: loop-preflight/testdata/fixtures/ocmd-print-divergence.pdf (generator loop-preflight/tools/generate_ocmd_divergence_fixture.py), committed first (38d7af0). It has two groups with opposite View/Print usage and five membership dictionaries (AllOn and AllOff on Form XObjects; AnyOn, AnyOff and /VE [/Not ...] on marked content), each diverging between screen and print.

Before After
ocmd-print-divergence (test-hidden-layers, warning) pass: true, no errors, no warnings 5 hidden-layers warnings, one per policy; AllOn is on-screen/off-print, the other four off-screen/on-print
golden corpus (156 tests) 156/156 pass; the only snapshot change is the new ocmd-print-divergence.json

The baseline binary was built from the PR #156 head, which does not touch hidden-content code.

Local (Windows, MSVC Release): UnitTestsPreflightEngine 112/112 (includes the divergence test and an unevaluable-expression test that expects an incomplete error), UnitTestsPreflightCorpus 156/156, UnitTestsPreflightVerdict 53/53, UnitTestsPreflightChecks 17/17, UnitTestsEvidenceGraph 15/15, UnitTestsContentProcessorLimits 10/10. python scripts/generate-architecture-catalogs.py --check, check_source_integrity.py, check_preflight_truth_source.py, generate-adapters.py and check-change.py --dry-run pass.

Skipped or unavailable locally: the full check-change.py build/ctest/clang-tidy run (no clang-tidy-18; only the targets above were built), Linux lanes, and the remaining mapped test targets. CI covers those.

Remaining risk

  • Alternate optional-content configurations are not evaluated, only the default.
  • An /OC entry on a Form XObject or annotation that is never painted is not inspected.
  • Annotations are inspected from the page's /Annots only when the annotation dictionary itself carries /OC.

Anti-slop review

No second visibility engine: evaluation reuses PDFOptionalContentMembershipObject and PDFOptionalContentActivity for both usages, and the only new decision logic is when to report. A governor is never silently treated as visible when it cannot be evaluated.

🤖 Generated with Claude Code

mberrys and others added 5 commits September 30, 2026 16:21
Five membership dictionaries over groups with opposite View and Print
usage; hidden-layers currently passes it without a finding.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…creen (#115)

hidden-layers now evaluates every governor it meets (marked content,
Form XObject /OC, annotation /OC, inline OCMDs) under the View and Print
usages and reports divergence, print-hidden membership dictionaries, and
unevaluable ones as incomplete.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
#115)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…evidence (#115)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…evidence (#115)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comment thread LoopLibCore/sources/preflightengine.cpp Outdated
const bool divergent = !governor.incomplete && governor.viewState != governor.printState;
const bool hiddenInPrint = !governor.incomplete && governor.printState == OCState::OFF;
// A plain group hidden in print from marked content is already reported by name.
if (!governor.incomplete && !divergent && !(hiddenInPrint && (governor.kind == QStringLiteral("ocmd") || governor.source != QStringLiteral("marked-content"))))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate finding for marked-content OCMDs that are hidden in print. performMarkedContentBegin already adds a hidden-layers finding ("unnamed optional-content group") when isContentSuppressed() is true. This skip test only exempts plain OCGs, so the same OCMD is reported a second time here. A plain OCG in BDC that diverges between view and print is also reported twice. The fixture's BDC OCMDs are all print-ON, so the tests don't catch this. The marked-content path should defer to the governor report when the governor is an OCMD, or vice versa.

A marked-content OCMD hidden in print was reported both as an unnamed
group and by the governor report; a plain group diverging between view
and print was likewise reported twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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