Commit Graph

1189 Commits

Author SHA1 Message Date
Brandon Thomas 5c4988aed6 Merge pull request #13 from storytold/pr/never-crash-js
Never crash: hostile form scripts can't overflow the stack
2026-10-04 14:45:39 -04:00
Brandon Thomas ff8c75f46f Merge pull request #12 from storytold/pr/never-crash-forms
Never crash: forms scripts can't overflow the stack or the date arithmetic
2026-10-04 14:35:21 -04:00
Brandon Thomas ac222e818b Never crash: edit and xfdf have no expect calls
- edit: the substitute font name is a `&str` constant, so there's no `from_utf8(..).expect`.
  Paragraph grouping uses `let … else` / `if let` chains, and the reuse-font path takes the
  metrics with `filter` instead of `expect("checked")`.
- xfdf: a comment key that vanished between listing and lookup is skipped.

Both crates now deny the panic lints. Adds the root clippy.toml (identical to #11) so tests may
still unwrap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:23:06 -04:00
storybored379 e7ba974fe1 Edit text: a rewritten paragraph keeps its place in the text object it shares (#7)
* Edit text: a rewritten paragraph keeps its place in the text object it shares

A rewritten paragraph was drawn again just before the BT of its text object. When earlier
paragraphs shared that BT … ET (as in text-only pages), the paragraph moved ahead of them in
the content: reading order changed (screen readers, copy, `pdftotext -raw`) and text_paragraphs
renumbered, so after text_edit paragraph 2 a following text_edit paragraph 1 rewrote the
already edited paragraph instead of the first one. The text object is now split where the
paragraph was, and the second half resumes from the same line matrix and text state.

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

* Edit text: read the text before a paragraph without slicing by input positions (never-panic rule)

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

* Edit text: test a recoloured paragraph in a shared text object (split + q/Q together)

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

---------

Co-authored-by: storybored379 <storybored379@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-04 14:00:31 -04:00
storybored379 95af23d3a5 Forms: multi-select list values survive XML, CSV and text import (#5)
Export data writes a multi-select list's values into one cell joined by ", " ("A, C"), but
import passed the cell on as a single choice, which the list refused, so the selection was
silently lost on an export → import round trip (the field was only counted as rejected).
A multi-select list now splits such a cell into its options, unless the whole cell is itself
one of them.

Co-authored-by: storybored379 <storybored379@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:00:23 -04:00
storybored379 24f0a8928e Edit text: a paragraph's new colour no longer spills into the text after it (#6)
Rewriting a paragraph (text_edit, Format text) draws it in its own text object, then sets the
text state back for what follows, but not the fill colour. Changing one paragraph's colour
therefore also coloured every paragraph after it in the same content stream (recolour the
first paragraph of a page red and the rest of the page turns red). The rewritten paragraph is
now wrapped in q … Q, so its colour and other graphics state stay with it.

Co-authored-by: storybored379 <storybored379@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:24:17 -04:00
storybored379 096b515719 Forms: CSV import keeps values that span lines (#4)
* Forms: CSV import keeps values that span lines

A quoted CSV value may hold line breaks, and export_data writes multiline text fields that
way. The importer split the file into lines before reading quotes, so such a value was cut
at its first line break and every later column was lost (an export, then an import, of a form
with a multiline field silently dropped data). CSV records now end only at unquoted line
breaks; tab-delimited text is read as before.

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

* Forms: build CSV records without unwrap (never-panic rule)

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

---------

Co-authored-by: storybored379 <storybored379@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-04 13:24:11 -04:00
storybored379 3908a03aa6 Optimizer: Audit space usage counts images PrintCraft added as images (#3)
Content added with Edit ▸ Add content records its image in the content stream's dictionary
(/PCAdded /Image). The audit claimed a page's content streams before its resources and
followed that reference, so an added image's bytes were reported as Content Streams and
Images showed 0 % (a 1.9 MB picture read as 99.9 % content). Resources are now claimed
first, so whatever a content stream's dictionary points at keeps its own category.

Co-authored-by: storybored379 <storybored379@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:24:06 -04:00
Brandon Thomas 1434db626a Never crash: deny the panic lints in crates that are already clean
annot, compare, content, cos, export, geom, model, ocr, preflight, print and redact have no
production `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!`/`unimplemented!`. The attribute
keeps it that way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:21 -04:00
Brandon Thomas 7632d8b179 Never crash: create has no expect calls
`add_page` returns the crate's error when the new document has no page tree, and text pages are
filled with `if let`. The crate now denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:21 -04:00
Brandon Thomas 51027324e1 Never crash: UI and app shell have no expect/unreachable
Each "checked" `expect` becomes `let … else`, `if let` or `?`, with the obvious fallback (the
dialog closes, the command is ignored, the view refresh is skipped). ui-egui and the three apps
now deny the panic lints. The Windows `build.rs` reports a failed resource step with
`cargo::error` instead of panicking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:20 -04:00
Brandon Thomas d54f7c2fa7 Never crash: the automation (MCP/script) tools have no expect/unreachable
- Renderer cache: built with the entry API instead of insert-then-`expect`.
- Page-text cache: filled and read with `get`/`get_mut`.
- Schemas and form JSON: extended through `if let` instead of `as_object_mut().expect`.
- A replace-text mark of the wrong shape is an invalid-argument error.

The crate now denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:20 -04:00
Brandon Thomas f7a0d3a3b9 Never crash: render has no expect/unreachable
The text reading order uses `let … else`, `while let` and `get_mut` instead of `expect`, and
named destinations bind their key with an or-pattern instead of `unreachable!`. The crate now
denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:20 -04:00
Brandon Thomas c74fb89a0d Never crash: organize has no expect/unreachable
The outline import, bookmark lookup and page-label code use `first`/`last`, a final `Err` and a
default range instead of `expect`/`unreachable!`. The crate now denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:20 -04:00
Brandon Thomas 456a42a896 Never crash: sign has no expect/unreachable
PKCS #12 HMAC errors now propagate as `SignError`. Key-bag lookup uses `find_map` and chain
building uses `let … else`. `der::oid` (only ever given this crate's OID literals) keeps the
standard's narrow, documented `expect` exception. The crate now denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:20 -04:00
Brandon Thomas 7f09486004 Never crash: last-resort guard around edits, opens, saves and UI commands
`printcraft_engine::guard` wraps `catch_unwind` and returns an error message.
- `Session::apply`: edits run on a copy of the document, so a panic leaves it unchanged and is
  reported as an error. A panic while refreshing rolls the edit back.
- `Session::open`, `save_bytes`, `save_full_bytes` and `export_data` report a panic as an
  open, write or export error.
- `PrintCraftApp::execute`: a command that panics shows a notice, and the app and its documents
  keep running.
- The desktop app installs a panic hook that logs every panic to stderr, with a backtrace when
  `RUST_BACKTRACE` is set.

Autosave and crash recovery (`RecoveryStore`) were already in place for anything that escapes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:06:24 -04:00
Brandon Thomas 18c1acdb31 Never crash: hostile form scripts can't overflow the stack
boa's parser and compiler recurse once per nesting level and have no limit of their own: about
100 nested parentheses in a field or document script overflowed a 2 MiB stack and aborted the
app, which `catch_unwind` can't stop. `printcraft_js::run` now:
- refuses a script longer than 256 KiB, or one that nests brackets more than 64 deep, chains
  more than 64 prefix operators, or has more than 4096 `?`. Strings and comments are skipped.
  The script fails with an error, as a syntax error would;
- runs the engine on its own thread with a 1 GiB stack, which is address space, committed only
  as it's used. That covers every unbracketed construct a 256 KiB script can nest. A panic
  inside the engine becomes a script error. On wasm the script still runs inline.

Regression tests: `deeply_nested_scripts_fail_instead_of_crashing`, which aborted the test
process before this change, and `ordinary_nesting_still_runs`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:06:24 -04:00
Brandon Thomas 58cd6614ea Never crash: engine denies the panic lints
The one `unreachable!` in the scoped refresh becomes a full refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:06:23 -04:00
Brandon Thomas 9fc3354fe6 Never crash: filters, fonts, js, optimize and a11y deny the panic lints
- js: `host()` returns a script error instead of panicking when the host state is missing.
- optimize, a11y: the graphics-state stack is read with `last`/`last_mut` instead of `expect`.
- fonts: the standard-14 ASCII map is built from `u8`, which can't fail.
- filters: zlib encoding into a `Vec` keeps the standard's narrow, documented `expect` exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:06:23 -04:00
Brandon Thomas 95d8ba3101 Never crash: crypt has no panic paths
AES blocks come from `as_chunks`, the IV from `split_first_chunk`, and the R6 hash takes its
AES key and IV with `first_chunk`, so no `expect` is needed. The crate now denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:06:23 -04:00
Brandon Thomas 15f6fa7d4c Never crash: forms scripts can't overflow the stack or the date arithmetic
Two crashes that a PDF can trigger, each with a regression test that failed before this change:
- Deeply nested arguments in a field's format script (`[[[[…` or `new Array(new Array(…`) and
  deeply nested simplified field notation (`((((…` or `----…1`) recursed until the stack
  overflowed, which aborts the whole app. Nesting is now capped at 64 levels; deeper input is
  rejected like any other unparseable script.
- A date with a huge year (`1/1/2147483647`) overflowed `weekday`, and a `DateTime` with month 0
  indexed before the month names. The weekday math is now done in i64, years beyond i32 don't
  parse, and month and day names are looked up with `get`.

Also removes the crate's remaining `expect`/`unreachable!` calls and denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:06:23 -04:00
Brandon Thomas 48f1495ac2 Tests: take split part names with Path::file_name so the test passes on Windows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:48:43 -04:00
Brandon Thomas ab47bf4b9a CI: as_chunks instead of constant-size chunks_exact (clippy 1.99 chunks_exact_to_as_chunks)
Stable clippy 1.99 denies chunks_exact/chunks_exact_mut with a constant
size (32 sites), which failed the test job on main and every open PR.
Behaviour unchanged; MSRV 1.90 has as_chunks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:48:43 -04:00
storybored379 2336b24746 M4: Deleted pages leave the file even when bookmarks, links or widgets still point at them (#2)
Delete Pages only took the pages out of the page tree, so a bookmark, link or form widget that
referenced a deleted page kept it (and its content) in a full save. Deleted page objects are now
freed; their widgets leave the form (with fields left without widgets), and bookmarks and links
to them lose that destination.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:14:59 -04:00
storybored379 0523ccdfdc M8: Redaction drops /PCAdded from rewritten content streams so redacted added text does not stay in the file (#1)
Text added with Edit ▸ Add content keeps its source string in the stream dictionary's /PCAdded.
Applying a redaction rewrote the stream's glyphs but copied the dictionary unchanged, so the
redacted text survived in the saved PDF. The redacted item now stays as plain drawn content and
is no longer editable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:14:56 -04:00
Brandon Thomas 70280a44d0 M10: Export to Word (.docx), HTML and RTF (new export crate with a small zip writer): paragraphs, headings, bold/italic, images, page breaks; doc_export_office tool
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 09:40:46 -04:00
Brandon Thomas 4d60af8b1e M11: PDF/A (new preflight crate): verify PDF/A-2b/3b structural rules, Save as PDF/A (XMP identification, generated sRGB output intent, forbidden actions, annotation flags, interpolation, encryption), PDF/A dialog; pdfa_verify, pdfa_convert tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 07:19:54 -04:00
Brandon Thomas 632a21bfcf M13: Action Wizard: built-in actions (Prepare for Distribution, Optimize Scanned Documents, page numbers, confidential, sanitize), custom actions with 12 step kinds, background runs over files; action_list, action_run tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 05:48:19 -04:00
Brandon Thomas a4bf717c06 M12: Compare files: visual differences (pages rendered and compared region by region), shaded orange; doc_compare visual option
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 05:37:23 -04:00
Brandon Thomas c12b795ce5 M12: Compare files (new compare crate): word-level text differences, Compare panel with shading on the new file, report PDF, differences as comments; doc_compare, doc_compare_report, doc_compare_mark tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 05:31:18 -04:00
Brandon Thomas aa194686f1 M6: Prepare a form detects fields (underscore blanks, rules, boxes, check squares) and names them from their labels; form.detect, form_detect_fields
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 05:21:01 -04:00
Brandon Thomas 620ef4fcad M6: Field Properties ▸ Actions tab (six triggers, JavaScript/link/reset/menu/page/show-hide/submit actions); form_actions, form_set_actions tools; signing UI tests get their own temp folders
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 05:08:28 -04:00
Brandon Thomas 6ff0bf8f64 M6: Merge data files into spreadsheet (FDF, XFDF, filled PDF forms → CSV); form_merge_data tool
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 04:52:59 -04:00
Brandon Thomas 06f8d73ec7 M6: Acrobat JavaScript: sandboxed boa engine with the forms object model (printcraft-js); custom keystroke/validate/calculate/format scripts, button scripts, document JavaScripts, JavaScript console (⌘J), Preferences ▸ Enable JavaScript; js_run, js_document_scripts, js_set_document_script, form_set_script, js_enabled tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 04:41:47 -04:00
Brandon Thomas a7ffee01b5 M10: Recognize text in multiple files (ocr.recognize_batch, ocr_recognize_files): searchable copies written to a folder in the background
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 03:54:11 -04:00
Brandon Thomas b87cf39168 M10: Scan & OCR ▸ Recognize text: ocrs-based recognition (models fetched by cargo xtask models), invisible searchable text layer, Recognize Text dialog with background progress; ocr_recognize / ocr_status tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 03:47:58 -04:00
Brandon Thomas d7c22d7ef0 M7: Justify, underline, line spacing, character spacing and horizontal scale for existing paragraphs (and justify for added text); Lucide align-justify icon
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 03:16:03 -04:00
Brandon Thomas 385313e0bf M7: Format existing text: font family, bold/italic, size, colour and alignment for a paragraph (Format text panel live while editing; text_edit formatting arguments); centred and right-aligned paragraphs are detected
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 03:07:43 -04:00
Brandon Thomas c2f53d5595 M7: Edit text & images: select existing page images to move, resize, rotate, flip, replace, save or delete; page_images, image_edit, image_save tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 02:55:08 -04:00
Brandon Thomas 695e460bbb M7: Edit text works on paragraphs: detection, in-place editing with rewrap to the box, original spacing, colour and font (or Helvetica); text_paragraphs and text_edit paragraph
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 02:42:18 -04:00
Brandon Thomas fb6208b089 M7: Edit text: click a line of existing text and retype it in place, reusing its font when it can show the text, else Helvetica; text_lines / text_edit tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 02:25:06 -04:00
Brandon Thomas 5a70838884 M7: PDF font tables move to the fonts crate with Unicode decoding/encoding (ToUnicode, WinAnsi/MacRoman/Standard, Differences glyph names, subset glyph checks)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 00:42:57 -04:00
Brandon Thomas 2548228b1d M3: frame-budget tests skip (and say so) when the machine is overcommitted
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 00:42:57 -04:00
Brandon Thomas beb4e14c44 xtask: version test uses gates::root (fixes the xtask test build)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 00:42:57 -04:00
Brandon Thomas fcf3a7efa1 M3: About credits Dancing Script (typed signatures)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:02:18 -04:00
Brandon Thomas 01d0dc67ba M5: Fill & Sign typed signatures and initials (Type or Draw; script-font outlines drawn as filled paths); fill_sign_add signature/initials
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:59:28 -04:00
Brandon Thomas 03fca33d10 M5: Text boxes write rich text (/RC XHTML and /DS) in step with their text and style
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:11:12 -04:00
Brandon Thomas 9372b25d0b M10: Create PDF from JPEG 2000 images (JP2 or codestream, embedded as JPXDecode); M1: looping /Prev chains test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:07:53 -04:00
Brandon Thomas 80a8087ebc M9: Certificate Viewer (chain, Summary, Details, Trust; export, trust) from the Signatures panel; dialog button rows no longer stretch dialogs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:00:56 -04:00
Brandon Thomas f58570699e M9: Sign with macOS Keychain identities (keys stay in the Keychain): listed in Sign with a Digital ID; sign_keychain_ids, sign_document id keychain:…
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 07:55:07 -04:00