Files
docview/tests/PDF-VALIDATION.md
2026-09-21 13:41:40 +09:00

233 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PDF implementation validation
Validated on Linux x64 with Qt 6.11.2 and PDFium 155.0.8057.0, pinned to
`a5a7089234f121990b336b3841008009dca143bf`. The non-V8 build disables PDF
JavaScript and XFA. Exact binary provenance and SHA-256 are in
`cmake/pdfium.lock.json`; `python3 cmake/fetch_pdfium.py` installs that archive.
The current [v2 source-build candidate](results/pdfium-intent-context/README.md)
preserves rendering intents, pattern initial state and uncolored tiling stroke color.
Dedicated Arch and Ubuntu builds each pass 972 color probes and all 24 CTest groups;
the upstream run passes 1,979 tests. It is integrated into `build-context` and the
Ubuntu validation4 deb. Original dependencies and earlier packages remain available
for comparison. The v1 corpus had eight incorrect shading-pattern expectations;
v2 corrects those rows without changing the PDF/profile bytes and retains the old
failures and measurements. See the [current integration record](results/pdfium-intent-context/integration-record.json).
The historical [CMYK CLUT comparison](results/cmyk-lut/README.md) exposes a provider
rendering-intent discrepancy: on both Arch and Ubuntu, 30 of 48 ICC vector/image probes
exceed the unchanged analytic tolerance. The fixed PDFium uses perceptual ICC conversion
despite this PDF's relative-colorimetric intent. Diagnosis does not grant acceptance;
the [additional review page](results/cmyk-lut/index.html) retains the difference.
The [current human review gallery](results/pdfium-intent-context/render-review/index.html) assembles 21 image
pairs with per-page expectations and known differences. Nineteen compare against
Poppler; two compare TTF/TTC within PDFium. Sources, images and the worker used
for that recorded run are bound by hashes. All 83 decoded images match the prior
gallery exactly. All remain pending human review; creating or testing the
gallery does not grant G-RENDER acceptance.
## Validation revision
The older integrated, comparison and performance records remain historical evidence
for the binary hashes recorded with each run. Current checks include the latest
identity, navigation, metadata-omission and translation changes.
The historical completion-final run passed all 20 CTest groups; its [log](results/completion-final/ctest/LastTest.log)
and [build hashes](results/completion-final/build-record.json) are retained. The [six renderer comparisons](results/completion-final/comparison-run.json),
[relocated package checks](results/completion-final/linux-development-package/README.md)
and [watchdog](results/completion-final/worker-deadlines/README.md) have also completed.
The [v2 PDF performance run](results/pdfium-intent-context/performance/README.md)
passed four conditions, 120 cold process starts, 750 operations and 960 scroll inputs,
plus the approximately 1 GB / 5,000-page stress case. No measured provisional budget
was exceeded. These are headless software-rendering reference measurements; physical
display and reference-hardware acceptance remain incomplete. See [validation status](../docs/VALIDATION.md).
## Automated checks
The current `test_pdf` run records 75 passes (the earlier run had 69), including annotation geometry and lifetime checks,
26 tagged-PDF boundary rows, bounded shared-metadata checks, production worker
cases, Japanese broker integration, fatal-font-error rows, and QtTest setup and cleanup: metadata/page labels/CropBox/Rotate; explicit and named outline
destinations; RoleMap and multiple MCIDs in H1/H2; external versus blocked Launch
links and comment contents; text search; saved widget appearance; full-page
versus tile pixels; inverse coordinates; password required/invalid/correct;
corruption and render limits; and real sandboxed PDF worker round trips. A
malformed page tree with a missing first page is rejected before commit, and
its document handle is closed; it cannot trigger repeated page-metadata loads.
The suite also checks 1,200-item outline pagination, encoded-byte limits with
long Unicode labels, explicit 20,000-item truncation, string-limit warnings,
eight capability states/reasons, and the tagged versus untagged declaration.
The latest metadata regression cases are `annotationContentsLengthBoundary`,
`annotationAndLinkCountLimitsAreExplicit` (comments/links at 1,000 and 1,001), and
`annotationResponsePreservesBoundedPrefixThroughWorker`. A Contents value over
4,095 UTF-16 units (8,192-byte buffer including its terminator) keeps a fixed
explanation in the annotation list and reports an omission warning. Page-local
link/annotation count limits and the 512 KiB metadata response budget return an
explicit warning with the retained prefix. The production-worker case uses 100
long Japanese comments and verifies bounded replies and unchanged source bytes.
These cases pass in the current 75-pass suite, without increasing the safety limits.
Controller regressions now distinguish an outline containing only external or
blocked actions from one with usable internal destinations. Only internal targets
are considered for heading fallback and its capability state. The same GUI run
also replaces an open PDF's source file, verifies that its old reading position is
not rebound to the replacement, and checks the unresolved-location notice on
explicit reopen. [Focused and full-GUI evidence](results/completion-regressions/controller-state.md).
The worker test uses the production `WorkerProcess` launcher, completes the
bootstrap ping, parses the fixture only after Linux Landlock and seccomp are
active, renders a tile, and stops cleanly. It must run in an environment
permitting the test harness to create its own local socket. The worker never
disables its own sandbox for testing. The launcher also has a Windows handle
transport, but these results were obtained on Linux and establish no Windows
sandbox result.
The [tagged-PDF boundary corpus](fixtures/pdf/headings/README.md) adds a transformed
Form XObject, a rotated CropBox with structure order different from draw order,
a heading spanning two pages with nested and direct MCR children, and explicit
page-precision fallbacks for vector-only headings and reused stream-local MCIDs.
The regression checks authored PDF-point regions, title/order/page/level, all
four user rotations, and inverse coordinates within 0.5 pt. The Form-only ParentTree fixture now yields an exact H2 using the shared
worker-side qpdf structure adapter; a GUI regression follows the target.
The expanded corpus also checks nested Forms, owner-qualified MCID collisions,
repeated calls, structure cycles and bounded stream decompression.
The corpus exposed and fixed mixed-child ordering and non-text MCID collision
bugs; the ordinary RoleMap/multiple-MCID cases continue to pass.
`test_pdf_canvas` includes 21 passes including setup and cleanup. It distinguishes
the page nearest the viewport centre (ties choose the previous page) from the
leading logical reading location, and preserves the central PDF point on zoom. A separate
`pdf_canvas_hidpi` CTest group repeats the annotation pixel/hit-target case at DPR 2
(three QtTest passes including setup and cleanup). It compares both directions of the canvas coordinate transform
against PDFium for a nonzero CropBox, document Rotate 90 and user rotations
0/90/180/270. It verifies PDF-point restoration after zoom and horizontal pan,
separate jumps to two headings on the same page, invalid/clipped coordinates,
a jump whose page metadata arrives later, high zoom limits and search-result navigation.
The actual sandboxed worker also verifies that prefetch disabled produces no
adjacent-page requests, enabled prefetch warms a bounded adjacent viewport,
and a page jump uses those pixels immediately. An in-flight prefetch test
changes the zoom before completion, rejects the obsolete reply, and confirms
the previous visible resolution remains cached. Prefetch is limited to eight
tiles per adjacent page, runs only after visible work and queued operations,
and cannot insert a tile when doing so would evict existing cache entries.
Real-worker cases also inject a delayed fatal font reply: visible and prefetch
failures stop the poisoned worker even after a zoom revision, while a reply for
a replaced document is ignored. Missing-font warnings remain nonfatal and
reach document information. GUI regressions reopen the same unchanged source
with a new worker after failure in prefetch, search, cancelled search or page
metadata.
Late metadata cannot resurrect a destination cancelled by a newer jump, zoom,
or explicit cancellation. Theme background changes preserve white PDF paper.
The real-worker cache regression checks the 64–512 MiB budget clamp, nonzero
charged image bytes after rendering, cost below budget, cache clearing at
document replacement, actual visible/prefetch request counters, and exactly one
discarded old-revision reply. Repeated viewport readiness checks do not inflate
the request counts. These counters are lifetime samples; budget/cost report
QCache's KiB-rounded image charge and are separate from measured process RSS.
A two-pixel offscreen margin was insufficient to preserve antialiasing for a
glyph crossing a tile boundary. The worker now renders a sixteen-pixel margin,
then copies only the requested tile. The fixture's tile and full-page crop
compare byte for byte, including the red saved form appearance. IPC remains a
512-pixel tile, and the margin is not transmitted.
The base fixtures are generated locally by `tests/fixtures/pdf/generate.py`;
`tests/fixtures/pdf/generate_headings.py` regenerates 26 structure fixtures and
four shared-context boundary fixtures, with a SHA-256/expectation manifest,
without external dependencies.
They contain no third-party document content. The `qpdf` CLI regenerates
the encrypted fixture (reader password: `reader`); libqpdf is also a worker runtime
dependency for supplemental link-border and structure metadata. No password is saved by the
application.
## Independent renderer comparison
Run `python3 tests/compare_pdf_renderers.py` after building `pdf-worker-evidence`.
The exporter uses the production sandboxed worker, font broker and IPC; it does
not link PDFium into the main process. Set
`DOCVIEW_BUILD_DIR=build-pdf` to use another build directory. The comparison
uses system Poppler when available, because the bundled document-tools wrapper
has a different fontconfig environment.
Fixture SHA-256:
`e8b0b037679a3449de5d48f1b13a8562486c9ce19385a036d682cab906815a22`.
PDFium and Poppler 26.08.0 at 144 DPI both produced a 400 × 400 CropBox image.
154,110 of 160,000 pixels matched exactly. 4,024 pixels had a maximum channel
difference greater than 16; mean absolute channel error was 2.26274 / 255.
These descriptive metrics are not a performance or compatibility acceptance
threshold. The record contains the worker and font-broker binary hashes.
Evidence is written to `tests/results/pdf/{pdfium,poppler,difference,comparison}.png`
and `comparison.json`. Inspection confirms all three text strings, the black
rectangle and the saved red widget appearance are present in both images. Text
antialiasing differs. The no-appearance text-comment icon differs by renderer.
The former missing default link borders now match Poppler in the 8,448-pixel
comparison region. Other differences remain visible in the evidence and are
not accepted as golden-image success.
The [link-border corpus](results/link-borders/README.md) adds 20 annotation styles,
four intrinsic and four user rotations, tiled/full output equality, original
rectangle and file preservation, encrypted input, malformed data and bounded
metadata/AP handling. Supplemental metadata is read through libqpdf in the
worker, and numeric viewing-only appearances are generated through PDFium's
public API. Supplied appearances are preserved. NoZoom/NoRotate now use temporary
Rect and page-rotation compensation, with logical zoom separated from DPR.
The [annotation-flag corpus](results/pdf-annotation-flags/README.md) exercises
generated borders, saved link/comment appearances, and native Text icons.
Other missing-AP annotation types retain an explicit NoZoom limitation. Canvas tests compare
actual colored pixels with focus/click bounds at 100% and 200% DPR. These are
scoped rendering results, not a complete G-RENDER gate.
The [expanded synthetic corpus](fixtures/pdf/extended/README.md) adds five
documents and nine pages with embedded Japanese text, authored vertical glyph
positions and ruby, RGB/RGBA images, soft masks, alpha blending, clipping,
shading, CMYK swatches, Standard-14 substitution, varied CropBox/rotation, and
ICCBased RGB vector and raster content.
The [extended comparison evidence](results/pdf-extended/README.md) records
matching dimensions and geometry/alpha probes, Japanese extraction/search,
independent Poppler images and visual inspection. Run
`python3 tests/compare_pdf_extended.py` to regenerate that evidence.
The [ICC comparison](results/pdf-icc/README.md) renders a self-authored linear-RGB
matrix/TRC profile at 50%, 100% and 400%. All 54 probes agree with the analytic
sRGB transfer function within the stated rounding tolerance. A DeviceRGB control
distinguishes actual profile application from ignored ICC data. Dimensions and
the original file hash also match. Run `python3 tests/compare_pdf_icc.py` with
Pillow and pypdf installed. This is not calibrated CMYK/printing acceptance.
The [large-PDF stress record](STRESS-PDF-VALIDATION.md) covers a 5,000-page,
1,009,828,104-byte generated PDF with 1,280 genuinely referenced uncompressed
raster streams. Three opens, 100 keyboard operations and a direct page-5,000
screen capture completed with the sandbox enabled; the large input was removed
after hashes, timings, memory and screenshots were retained. This workload is
separate from rendering compatibility and reference-machine acceptance.
The [real TTC experiment](results/font-collection/README.md) constructs a private
two-face collection from locally installed OFL fonts. The production broker
selects regular face 0 and bold face 1; both pages' 892,800 pixels match the
separate-TTF condition exactly, and regular/bold remain distinct. Font programs,
configuration and cache are temporary and removed after verification. This
checks real Linux TrueType collection selection, not Windows/CJK/CFF collections.
## Outstanding acceptance coverage
G-RENDER is **not complete**: the synthetic comparisons have no human-approved
golden image and do not establish arbitrary CID/CMap compatibility,
calibrated ICC/CMYK, complex transparency
groups, malformed appearances or production-corpus compatibility. The original embedded Japanese
fixture uses explicitly positioned vertical glyphs; the new unembedded fixture
adds native UniJIS-UCS2-V writing mode for two requested Japanese families. Generated link borders, saved link/comment appearances, and native Text icons
now cover NoZoom/NoRotate; other subtype-specific synthesized appearances
still need wider validation.
G-HEADINGS remains scoped to the generated corpus: RoleMap, direct/nested/interleaved
MCIDs, transformed Form text, rotated pages and cross-page fragments are covered.
A shared qpdf context now reads Form-only StructParents and verifies stream owners,
ParentTree membership and actual Form invocation. Public PDFium APIs supply text
and geometry. Repeated or ambiguously matched Forms keep explicit page-precision
fallbacks; malformed/cyclic/bounded-out structures report limitations. The main
process has no added PDF parser or native PDF objects. Annotation-owned marked
content remains explicitly unsupported, and broader production structures require
corpus validation. These results do not establish comprehensive tagged-PDF support.