nigig-org/crates/apps/pdf/pdf-cos/tests
andodeki 674b2be66d
Some checks failed
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
email.yml / feat(pdf): JPEG 2000 decoding — Phase 3 complete, all three codecs (push) Failing after 0s
repo hygiene / hygiene (push) Has been cancelled
feat(pdf): JPEG 2000 decoding — Phase 3 complete, all three codecs
The last codec ADR 0015 deferred. The plan recorded the blocker as a
dependency decision, not an algorithm: openjpeg would add a C dependency
that breaks the Android cross-compile. This is pure Rust and adds no
dependency at all.

It shares the MQ arithmetic decoder with JBIG2 — T.800 and T.88 specify
the same coder — so the previous tranche paid for most of this one.
Context::with_state moved onto the shared type because JPEG 2000 starts
three of its nineteen contexts away from state 0 and JBIG2 starts all of
them at 0.

Implemented: codestream and JP2 container parsing, packet headers with
tag trees and the bit-stuffing rule, EBCOT tier-1 (all three passes,
four zero-coding context tables, run-length mode), both 5/3 reversible
and 9/7 irreversible wavelets, RCT and ICT, arbitrary decomposition
levels, and multiple components.

Refused by name: multiple tiles, custom precinct partitions, code-block
style options, COC/QCC/RGN/POC overrides, subsampled components. Each
error says which feature the file needs. This matters more here than
anywhere else in the stack, because a JPEG 2000 decoder that quietly
skips something does not fail — it returns a slightly soft or banded
image that looks entirely fine.

That property also dictates how this is tested. Fixtures are produced by
OpenJPEG via Pillow and compared **exactly**, sample for sample: the
fixtures are lossless 5/3 so no tolerance is needed, and a tolerance is
where a subtly wrong decoder hides. Four images — grayscale raw
codestream, the same in a JP2 container, a larger one whose tag trees
actually branch, and RGB. A generator script is checked in beside them
so CI can prove the fixtures still match what produced them.

Verified by mutation. The first round was misleading and is worth
recording, because it is the same lesson as ADR 0017:

  DC level shift dropped        3 fail
  5/3 lifting rounding changed  2 fail
  RCT sign flipped              PASSED  <- survived
  RCT components swapped        PASSED  <- survived
  cleanup run-length disabled   PASSED  <- survived
  sign-context XOR dropped      PASSED  <- survived

Four mutations survived because Pillow writes MCT=0 by default, so the
RGB fixture coded its three components independently and never reached
the colour transform at all. The RCT branch was completely untested
while appearing covered — an untested branch that looks tested is worse
than one that looks missing. Added rgb8_mct.j2k with mct=1; all four
now fail. The header bit-stuffing mutation is caught by the unit test
rather than the round-trip.

Two real defects found while writing the tests:

- A corrupt marker length in a tile-part header walked the read cursor
  past the codestream and panicked on a slice. Found by the corruption
  sweep, not by review. The sweep now truncates at every length and
  flips every byte of a real file, and asserts only that nothing panics.
- The 9/7 flat-signal test initially asserted an amplitude I had derived
  from my own arithmetic. That is a test agreeing with the code by
  construction. It now asserts flatness — a ripple means the lifting or
  the edge extension is wrong — and the amplitude is pinned by the
  OpenJPEG round-trips instead, which use pixels this code did not
  produce.

Also removed two dead fields and an unused parameter that clippy found:
Subband::x0/y0 are always zero in the single-tile case this supports,
and dead state implying multi-tile support exists is worse than no
state.

JPX decodes on the image path, like JBIG2, because the codestream
carries its own geometry; it stays in REFUSED_CODECS with a reason
string saying where it is decoded rather than that it is missing.

Engine suite 866 -> 920. Coverage 85.66% -> 86.16%; jpx.rs at 93.72%
with a floor at 88.

Phase 3 is complete: CCITT, JBIG2 and JPX all land, and the plan is
updated to say so and to record how the two gating questions — JBIG2's
CVE record and JPX's C dependency — were actually answered.
2026-08-16 22:17:40 +00:00
..
jpx_roundtrip.rs feat(pdf): JPEG 2000 decoding — Phase 3 complete, all three codecs 2026-08-16 22:17:40 +00:00