233 lines
15 KiB
Markdown
233 lines
15 KiB
Markdown
# 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.
|