Skip to content

fix(import): tolerate legacy text encodings, guard zero-page saves, harden editor load - #137

Merged
nelsonduarte merged 2 commits into
mainfrom
fix/import-encoding-and-editor-doc
Jul 28, 2026
Merged

fix(import): tolerate legacy text encodings, guard zero-page saves, harden editor load#137
nelsonduarte merged 2 commits into
mainfrom
fix/import-encoding-and-editor-doc

Conversation

@nelsonduarte

Copy link
Copy Markdown
Owner

Adversarial bug-hunt fixes. Scope limited to app/tools/import_pdf.py, app/editor/canvas.py, app/translations.json and a new test module. No viewer/requirements changes.

MAJOR #1 — legacy text encodings crash TXT/MD/HTML import

Trigger: import a .txt (or .md/.html) that Notepad/Excel saved as Windows-1252 / Latin-1, e.g. bytes b'Ol\xe1 mundo'.
Symptom: the font pre-scan (~L142) already used errors="replace" and passed, telling the user the file was valid — then the real do_work read (open(src, "r", encoding="utf-8"), no errors=) raised UnicodeDecodeError and the conversion died.
Root cause: strict decoding in a context that must tolerate legacy encodings, inconsistent with the pre-scan.
Fix: aligned all three do_work read sites (TXT, MD, HTML) with the pre-scan using errors="replace". charset-normalizer is not a project dependency, so — per the brief — no new dep was added; errors="replace" is sufficient and consistent.

MINOR #2 — zero-page save raises a cryptic ValueError (and masks skip feedback)

Trigger: all inputs empty/rejected (only non-image files dropped into the image list, gigapixel-rejected images, empty docx, page_count == 0).
Symptom: BasePage._atomic_pdf_writedoc.save() raised ValueError: cannot save with zero pages; in _convert_images this also masked the "N images skipped" status.
Root cause: no guard before writing a page-less document.
Fix: every converter now returns a _NoContent sentinel when doc.page_count == 0, routed through a shared _on_result handler that surfaces the new translated tool.import.no_content message (and preserves the images skip count). New i18n key added with 8-language parity.

MINOR #3 — corrupt PDF leaves a dangling closed Document in the editor

Trigger: PdfEditCanvas.load() on a corrupt PDF (fitz.open raises FileDataError).
Symptom: if self._doc: self._doc.close() then self._doc = fitz.open(path) — the assignment never happens, so self._doc keeps referencing the just-closed Document (use-after-close).
Root cause: the previous reference wasn't cleared before the fallible fitz.open.
Fix: clear self._doc = None before fitz.open, open into a local, and only publish self._doc = doc on success.

Tests

tests/test_import_encoding_and_editor_doc.py — 10 discriminative cases:

  • cp1252 .txt, latin-1 .md, latin-1 .html each produce a valid PDF; an inverse test proves the old strict read would have raised UnicodeDecodeError.
  • all-rejected image list returns _NoContent, writes nothing, and on_done shows tool.import.no_content without calling _done; docx zero-page guard; i18n parity for the new key.
  • corrupt load() leaves self._doc is None (not a closed doc) and a subsequent valid load() works.

Validation

  • Full suite: 484 passed, 3 skipped, 0 failed.
  • ruff check --select F,E9 on the touched files: clean.
  • translations.json: valid JSON, all 8 languages at equal key counts, new key present in all.

🤖 Generated with Claude Code

…arden editor load

MAJOR #1: TXT/MD/HTML import read user files with strict
open(encoding="utf-8") in do_work, crashing with UnicodeDecodeError on
Windows-1252/Latin-1 files (common Notepad/Excel .txt). The font pre-scan
already used errors="replace" and passed, so the user was told the file
was valid before the real conversion blew up. Aligned all three do_work
read sites with the pre-scan (errors="replace"); no new dependency added
(charset-normalizer is not a project dep).

MINOR #2: when every input was empty/rejected/unreadable the fitz doc had
zero pages and doc.save() raised the cryptic "cannot save with zero
pages" (in _convert_images this also masked the "N images skipped"
feedback). Added a _NoContent sentinel returned by every converter when
doc.page_count == 0, routed through a shared handler that shows the new,
translated tool.import.no_content message (8-language parity).

MINOR #3: PdfEditCanvas.load closed the previous doc then assigned
self._doc = fitz.open(path); a corrupt PDF that raised left self._doc
pointing at the already-closed Document (use-after-close). Now clears
self._doc before fitz.open and only publishes on success.

Tests: tests/test_import_encoding_and_editor_doc.py (10 discriminative
cases covering each bug, incl. an inverse test proving the strict read
would have raised, and i18n parity for the new key).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread tests/test_import_encoding_and_editor_doc.py Fixed
Comment thread tests/test_import_encoding_and_editor_doc.py Fixed
Comment thread tests/test_import_encoding_and_editor_doc.py Fixed
- Rename the module-level QApplication holder _app -> _unused_app
  (matches the repo's existing test_encrypted_pdf_tools pattern) so the
  liveness reference no longer trips CodeQL's unused-global note.
- Drop the unused bs4 binding; keep pytest.importorskip("bs4") purely
  for its skip-if-absent side effect.
- Import QMessageBox from app.tools.import_pdf at module top and remove
  the redundant 'import app.tools.import_pdf as mod', unifying on the
  from-import style; the monkeypatch still targets the same class object.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploying pdfapps with  Cloudflare Pages  Cloudflare Pages

Latest commit: b8458f0
Status: ✅  Deploy successful!
Preview URL: https://a3f6a222.pdfapps.pages.dev
Branch Preview URL: https://fix-import-encoding-and-edit.pdfapps.pages.dev

View logs

@nelsonduarte
nelsonduarte merged commit 7aac229 into main Jul 28, 2026
4 checks passed
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