Skip to content

Fix several small correctness bugs - #5076

Open
A831ARD0 wants to merge 1 commit into
pymupdf:mainfrom
A831ARD0:fix/small-bug-fixes-batch
Open

Fix several small correctness bugs#5076
A831ARD0 wants to merge 1 commit into
pymupdf:mainfrom
A831ARD0:fix/small-bug-fixes-batch

Conversation

@A831ARD0

@A831ARD0 A831ARD0 commented Aug 3, 2026

Copy link
Copy Markdown

Summary

A batch of small, independent correctness fixes found while reviewing src/__init__.py, src/utils.py, src/_apply_pages.py, and the table-extraction module. Each item below is a separate, self-contained fix; happy to split into individual PRs if preferred.

  • Archive.add(): a missing comma in an assert turned the error message into part of the boolean expression, silently disabling the empty-name check for binary content (Archive().add(b"...", path="") no longer raised).
  • Annot.file_info: UnboundLocalError when a malformed/crafted attachment's /FS dict has neither /UF nor /F.
  • Page.set_language(): passed the bound method pdfpage.obj instead of calling it (pdfpage.obj()), breaking every call that actually sets a language.
  • Widget.on_state(): TypeError when button_states() leaves an entry as None.
  • Pixmap.tobytes(): missing f-string prefix meant the "cannot have alpha" error literally printed '{output}' instead of the format name.
  • JM_matrix_from_py(): copy-paste bug returning mupdf.FzRect() instead of mupdf.FzMatrix() on the float-conversion failure path (latent — every other sibling converter returns its own correct type).
  • utils.get_text(): a bare assert option in formats ran before the following if, making the documented "misspelled option falls back to 'text'" behavior unreachable (and behavior differed under -O).
  • utils.get_label_pno(): raised IndexError instead of returning "" (as documented) when no label rule applies to the given page number.
  • utils.get_sorted_text(): ZeroDivisionError for a zero-width glyph bbox.
  • _apply_pages.py (method='fork'): workers received plain list indices (range(len(pages))) instead of the actual page numbers, so apply_pages(path, fn, pages=[3, 7, 9], method='fork') silently processed pages 0, 1, 2 instead — results were silently attributed to the wrong pages. Fixed by passing (index, page_number) pairs through the queue.
  • table.chars_in_rect(): compared a top-down rect (from page.get_drawings()) against c["y0"]/c["y1"], which are in PDF-native (bottom-up) space, instead of the top-down c["top"]/c["bottom"]. This made box-drawn table borders (filled/connected rectangles instead of ruling lines) fail to register as table candidates.

Testing

I don't have a build of the compiled _fitz/mupdf C extension available, so I could not run the full test suite. Each fix was verified by:

  • Reading the surrounding code and confirming the defect against the function's own documented contract or a directly comparable sibling function.
  • python3 -m py_compile on every modified file to confirm no syntax errors were introduced.

Given that, please review carefully and let me know if you'd like any of these split out, reverted, or backed by a regression test before merging.

- Archive.add(): fix broken assert (missing comma turned the error
  message into part of the boolean expression, disabling the empty-name
  check for binary content).
- Annot.file_info: avoid UnboundLocalError when a crafted/malformed
  attachment's /FS dict has neither /UF nor /F.
- Page.set_language(): call pdfpage.obj() instead of passing the bound
  method itself, which broke every call that actually sets a language.
- Widget.on_state(): skip None entries from button_states() instead of
  raising TypeError.
- Pixmap.tobytes(): add missing f-string prefix so the alpha error
  message actually includes the format name.
- JM_matrix_from_py(): fix copy-paste bug returning FzRect() instead of
  FzMatrix() on the float-conversion failure path.
- utils.get_text(): remove a bare assert that made the documented
  "misspelled option falls back to text" behavior unreachable.
- utils.get_label_pno(): return "" (as documented) instead of raising
  IndexError when no label rule applies to the given page number.
- utils.get_sorted_text(): guard against ZeroDivisionError for
  zero-width glyph bboxes.
- _apply_pages.py (method='fork'): send the actual page numbers to
  workers instead of plain list indices, which silently processed the
  wrong pages for any non-contiguous/non-zero-based `pages` list.
- table.chars_in_rect(): compare against c["top"]/c["bottom"] (top-down,
  matching the rect from page.get_drawings()) instead of c["y0"]/c["y1"]
  (PDF-native bottom-up space), fixing table detection for frames drawn
  as filled rectangles.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@A831ARD0

A831ARD0 commented Aug 3, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

Comment thread src/_apply_pages.py
Comment on lines -140 to +145
page_num = queue_down.get()
item = queue_down.get()
if verbose:
pymupdf.log(f'{os.getpid()=}: {page_num=}.')
if page_num is None:
pymupdf.log(f'{os.getpid()=}: {item=}.')
if item is None:
break
index, page_num = item

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.

I think the current code is ok here. This code is in the worker function def childfn(): so reads page numbers from queue_down and writes (page_num, text) to queue up.

Comment thread src/_apply_pages.py
Comment on lines -175 to +178
pymupdf.log(f'{os.getpid()=}: sending {page_num=} {ret=}')
queue_up.put( (page_num, ret) )
pymupdf.log(f'{os.getpid()=}: sending {index=} {ret=}')

queue_up.put( (index, ret) )

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.

As above, this is worker code.

Comment thread src/utils.py
Comment on lines -500 to 501
assert option in formats
if option not in formats:
option = "text"

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.

I think the intention here is to fail if an unrecognised option is specified, so we should remove the if option not in formats: option = "text" code, rather than the assert.

github-actions Bot added a commit that referenced this pull request Aug 3, 2026
@julian-smith-artifex-com

Copy link
Copy Markdown
Collaborator

Thanks for submitting this PR, looks like some good fixes in the areas i know about. I've enabled the pre-push tests.

You should be able to build/test fairly easily if you run pip in a python venv - all required packages etc will be installed automatically.

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.

2 participants