Skip to content

Read images, color spaces and soft masks through the typed object reader - #593

Open
Velli20 wants to merge 4 commits into
mainfrom
typed-reader-images-color-spaces
Open

Velli20 wants to merge 4 commits into
mainfrom
typed-reader-images-color-spaces

Conversation

@Velli20

@Velli20 Velli20 commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Summary

Image, color space, soft mask and form decoding still dropped back to the raw (dictionary, objects) lookup API. They now go through FromPdfObject and the context APIs.

  • Images: inline images and image XObjects merge into one PdfImage.
    • It is a FromPdfObject decoder for ImageMetadata plus the filtered ImagePayload, with decode(soft_mask) producing the raster. Inline images are read by wrapping their normalized dictionary in an encoded stream.
    • Removed: read_xobject, decode_inline_image, decode_normalized_image, decode_data_with_access and ImagePayload::decode.
    • ImageMetadata builds its value in one pass instead of filling ImageEntries.
    • Resource marks an image unavailable through PdfImage::is_unreadable instead of a raw size check.
    • Filter and image errors keep their type across reads.
  • Color spaces: ColorSpace matches device names in its decoder and reads the Indexed, Separation and DeviceN families with context.read. Colorant names (now Arc<[u8]>) and the Indexed lookup are read with array.at.
  • Soft masks: /S is read as a MaskMode, /G must be a form XObject stream, and /TR is read as MaskTransfer (Identity or a sampled table). XObjectSubtype decodes /Subtype directly.
  • Geometry: Rect and Transform gain decoders, which form and pattern use for /BBox and /Matrix.

Behaviour changes

  • Missing, non-numeric or zero image dimensions give InvalidImageDimensions. Image XObjects with them are still unavailable.
  • An inline JPXDecode image is still rejected. It is now detected after the filters before JPXDecode have run, and the codestream itself is never decoded.
  • An unresolvable form /BBox is treated as absent, so the form is unclipped. /Matrix and a tiling /BBox with extra trailing numbers are accepted.
  • A soft mask /G with an unknown subtype reports UnsupportedXObjectSubtype.

Testing

  • cargo fmt --check and cargo clippy --all --workspace are clean.
  • cargo test passes for pdf-filter, pdf-image, pdf-resources, pdf-canvas, pdf-parser, pdf-content-stream, pdf-content-stream-operators, pdf-object-collection, pdf-color-space, pdf-object-reader and pdf-graphics.
  • Existing tests were updated to the new APIs; no new tests were added.
  • The Skia backend was not built locally, so the full workspace test run and the Skia render check are left to CI.

🤖 Generated with Claude Code

Image, color space, soft mask and form decoding still went back to the raw
(dictionary, objects) lookup API. They are now FromPdfObject impls read
through the context APIs.

Inline images and image XObjects merge into one PdfImage: a FromPdfObject
decoder that reads ImageMetadata and the filtered ImagePayload from a
stream, and decode(soft_mask) for the raster. Inline images are read by
wrapping their normalized dictionary and data in an encoded stream.
read_xobject, decode_inline_image, decode_normalized_image and
decode_data_with_access are removed. ImageMetadata builds its value in one
pass instead of filling ImageEntries, and Resource marks an image
unavailable through PdfImage::is_unreadable instead of a raw size check.
Filter and image errors keep their type across reads.

ColorSpace matches device names in its decoder and reads the Indexed,
Separation and DeviceN families with context.read. Colorant names and the
Indexed lookup are read with array.at.

SoftMask reads /S as a MaskMode, checks that /G is a form XObject stream,
and reads /TR as a MaskTransfer, which is Identity or a sampled table.
XObjectSubtype decodes /Subtype directly.

Rect and Transform gain FromPdfObject decoders, which form and pattern use
for /BBox and /Matrix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Conformance reads

Documents that Safe-PDF reads differently from the base commit, compared with PDFium. A read
regresses when Safe-PDF fails to open a document it opened before, crashes or times out, or
counts a different number of pages than PDFium.

pdf.js corpus

1009 documents compared with 7baaa0e: 0 read worse, 2 read better.

Improvements

Document Base This pull request
issue17190_1 timeout: time limit exceeded (stage safe-read) read, 1 page
issue19517 timeout: time limit exceeded (stage safe-read) read, 1 page

840 documents of the PDFium corpus read as on 7baaa0e.

Details are in the job summaries of the workflow run. Rerun a case locally with
cargo conformance run --corpus <corpus> --read-only --case <id>.

The operator backend traits (twelve per-category traits plus the
PdfOperatorBackend supertrait) had one production implementor, PdfCanvas,
and a test-only recorder that rebuilt a near-copy of PdfOperatorVariant.
Nothing was generic over a single category trait, so the layer only added
indirection over an enum that already describes every operator.

PdfCanvas now dispatches through apply_operator, and its handlers are
inherent methods. Tests check the parsed operators directly instead of
replaying them through the recorder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Velli20 and others added 2 commits October 21, 2026 22:47
A soft mask's /G group can be the form whose resources are being decoded.
Reading its subtype eagerly through the reference pushed that form onto the
active path again and failed with a cyclic reference. Check the kind and
subtype on the resolved value instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dispatch content stream operators by matching on PdfOperatorVariant

This branch has not been deployed

No deployments
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