Skip to content

ROB: Tolerate extra operands before Tm/cm in layout mode - #4284

Merged
stefan6419846 merged 2 commits into
py-pdf:mainfrom
rahul05ranjan:fix/tm-cm-extra-operands
Oct 31, 2026
Merged

stefan6419846 merged 2 commits into
py-pdf:mainfrom
rahul05ranjan:fix/tm-cm-extra-operands

Conversation

@rahul05ranjan

Copy link
Copy Markdown
Contributor

Fixes #4283

Bug

Layout mode text extraction crashed with an internal TypeError when a content stream contained a Tm (or cm) operator with extra operands on the operand stack:

# content stream: BT /F1 12 Tf 72 720 1 0 0 1 100 600 Tm (TmText) Tj ET
reader.pages[0].extract_text(extraction_mode="layout")
# TypeError: TextStateManager.new_transform() takes from 0 to 6 positional arguments but 8 ...

Root cause

  • TextStateManager.add_tm/add_trm pass operands through _complete_matrix, which pads a 2-operand Td-style set but does not trim operand sets longer than 6, so new_transform() receives more than its 6 matrix parameters.
  • TextStateManager.add_cm splats *args straight into new_transform().

Per the PDF operand-stack model, a matrix operator consumes the 6 operands nearest to it; the extras are malformed-stream noise and should not overflow the transform API. This is the same class of operand hardening as #4232 (Tz without operand) and #4233 (TD with a missing/non-numeric ty).

Fix

_complete_matrix now returns only the last 6 operands, and add_cm splats only args[-6:], so Tm/cm consume the 6 operands nearest to the operator. Valid operand counts are unaffected.

Verification (stdlib-only harness on the Pi, Python 3.13.5, main @ c7d391c)

Hand-built one-page PDF (correct xref) with the following content streams, layout mode, before → after:

  • BT /F1 12 Tf 72 720 1 0 0 1 100 600 Tm (TmText) Tj ET (8 operands): TypeError crash → 'TmText' ✓ (matrix taken from the nearest 6 operands: 1 0 0 1 100 600)
  • BT /F1 12 Tf 9 1 0 0 1 100 600 Tm (TmText) Tj ET (7 operands): crash → 'TmText' ✓
  • BT /F1 12 Tf 5 5 1 0 0 1 50 50 cm 72 720 Td (CmText) Tj ET (cm with 7 operands): crash → 'CmText' ✓
  • Valid 6-operand Tm: 'TmText' before and after (unchanged) ✓
  • Valid Td translation: 'Second' before and after (unchanged) ✓

Tests: pytest tests/text_extraction/ -q → 6 passed. pytest tests/test_text_extraction.py -q → 108 passed, 1 xpassed, 2 pre-existing failures (test_multi_language, FileNotFoundError: sample-files/015-arabic/habibi-rotated.pdf — the sample-files submodule is absent in a shallow clone, unrelated to this change).

A matrix operator consumes the 6 operands nearest to it; values
left on the operand stack before those (malformed content streams)
overflowed TextStateManager.new_transform() and raised TypeError
in layout mode text extraction.

Complete the matrix with only the last 6 operands in
_complete_matrix and add_cm, mirroring the tolerance already in
place for missing/non-numeric TD and Tz operands (py-pdf#4232, py-pdf#4233).

Fixes py-pdf#4283

Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
@codecov

codecov Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.55%. Comparing base (c7d391c) to head (62df42e).
⚠️ Report is 14 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4284      +/-   ##
==========================================
+ Coverage   98.41%   98.55%   +0.14%     
==========================================
  Files          59       59              
  Lines       11995    11992       -3     
  Branches     2270     2268       -2     
==========================================
+ Hits        11805    11819      +14     
+ Misses        103       93      -10     
+ Partials       87       80       -7     

☔ 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 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests. Why are these changes part of #4286 as well?

Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
@rahul05ranjan

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Tests added in 62df42e — two layout-mode cases covering extra operands before both Tm and cm, asserting the extracted text matches the equivalent clean 6-operand stream. Both reproduce the original TypeError when run against the pre-fix code.

On your question — why these changes are part of #4286 as well: the set_page_label branch (fix/page-label-start-none) was cut from this PR's branch, so it carries this Tm/cm commit (d70431ae) beneath its own commit. I'll rebase #4286 onto main so it contains only the set_page_label change, leaving this PR standalone.

@stefan6419846
stefan6419846 merged commit 694aa59 into py-pdf:main Oct 31, 2026
20 checks passed
@rahul05ranjan

Copy link
Copy Markdown
Contributor Author

Thanks for merging #4284 (ROB: Tolerate extra operands before Tm/cm in layout mode)! 🎉

By the way, I'm currently open to new opportunities (Full Stack (PHP, NodeJs, Python) / Data Science / Applied AI / Devtools, based in India). If you or anyone around is hiring, I'd love to connect:
LinkedIn: https://www.linkedin.com/in/rahulswt7/ · GitHub: https://github.com/rahul05ranjan

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.

Layout mode text extraction crashes with TypeError when Tm/cm has extra operands

2 participants