Skip to content

BUG: Treat an empty /Filter array as no filter when extracting images - #4026

Merged
stefan6419846 merged 1 commit into
py-pdf:mainfrom
Anai-Guo:fix-empty-filter-array
Aug 27, 2026
Merged

stefan6419846 merged 1 commit into
py-pdf:mainfrom
Anai-Guo:fix-empty-filter-array

Conversation

@Anai-Guo

Copy link
Copy Markdown
Contributor

Closes #4025.

Problem

_xobj_to_image() reads the /Filter entry and then takes its last element:

filters = x_object.get(StreamAttributes.FILTER, NullObject()).get_object()
lfilters = filters[-1] if isinstance(filters, list) else filters

The check only confirms that the value is a list, and then assumes it can be
indexed with -1. For an image whose /Filter is an empty array that raises
IndexError: list index out of range, exactly as reported:

{'/Height': 1, '/Width': 256, '/ColorSpace': '/DeviceRGB', '/BitsPerComponent': 8, '/Filter': []}

Because page.images builds its id list eagerly, the failure is not limited to
the offending image — iterating page.images raises for the whole page.

Fix

An empty array carries the same meaning as a missing /Filter entry: no filter
is applied. The lookup is now skipped when the array is empty, so the image
takes the same unfiltered path it would take with no /Filter key at all.

Testing

tests/generic/test_image_xobject.py::test_xobj_to_image__empty_filter_array
builds the minimal image XObject with /Filter [] and asserts the result is
byte-identical to the same XObject with no /Filter entry.

Verified locally:

  • without the source change the new test fails with the reported
    IndexError: list index out of range at the filters[-1] line;
  • with it, tests/generic/test_image_xobject.py is 27 passed;
  • ruff and mypy (1.17.1, the pinned pre-commit revision) are clean on both
    changed files.

The remaining failures in tests/test_images.py / tests/test_filters.py on my
machine are pre-existing FileNotFoundErrors for network fixtures — identical
counts (37 failed / 130 passed) with and without this patch.

Note on the base

My fork is 68 commits behind and I currently cannot push a branch based on the
newer main, so this branch is built on the fork's own tip. The merge base is
f6287fa and the diff against main is exactly the two files above (+18/-1) —
no unrelated drift is included. I re-ran the tests, ruff and mypy against
both main and the fork tip; both are green. Happy to rebase if you prefer.

🤖 Generated with Claude Code

_xobj_to_image() checked only the type of the /Filter entry and then indexed
it with -1, so an image whose /Filter is an empty array raised
'IndexError: list index out of range', which made page.images unusable for
the whole page.

An empty array carries the same meaning as a missing /Filter entry - no
filter is applied - so the image now takes the same unfiltered path it would
take without the entry.
@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.00%. Comparing base (f6287fa) to head (7c56a40).
⚠️ Report is 68 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4026      +/-   ##
==========================================
+ Coverage   97.89%   98.00%   +0.11%     
==========================================
  Files          57       57              
  Lines       10869    11154     +285     
  Branches     2032     2095      +63     
==========================================
+ Hits        10640    10932     +292     
+ Misses        127      124       -3     
+ Partials      102       98       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@stefan6419846
stefan6419846 merged commit 39565fe into py-pdf:main Aug 27, 2026
20 checks passed
stefan6419846 added a commit that referenced this pull request Sep 4, 2026
## What's new

### Security (SEC)
- Limit value for Roman numerals (#4047) by @stefan6419846

### New Features (ENH)
- _cmap.py: Also parse encoding for embedded CFF Type1 fonts (#4032) by @PJBrs

### Performance Improvements (PI)
- Cache repeated text extraction character lookups (#4036) by @petermik68-sudo

### Bug Fixes (BUG)
- Treat an empty /Filter array as no filter when extracting images (#4026) by @Anai-Guo
- Detect a duplicate dictionary key whose first value is falsy (#4024) by @devYRPauli
- Make is_open=False collapse outline items (#3998) by @SomSamantray

### Robustness (ROB)
- Multiple changes for wrong inputs by @RavSinghChandan
- Skip trailing duplicate %%EOF markers when locating startxref (#4015) by @Anai-Guo
- Do not crash on a non-array destination (#3976) by @eeshsaxena
- Handle annotations without subtype during merge (#3999) by @Nexlu1

### Documentation (DOC)
- Use AnnotationFlag enum instead of plain integers (#3997) by @RavSinghChandan

### Code Style (STY)
- Multiple small changes detected from test runs by @RavSinghChandan

[Full Changelog](6.16.2...6.17.0)
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.

Empty /Filter list breaks image extraction

2 participants