mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-29 04:28:20 -06:00
33c14e07cfd742a82bf03baa6b103c01008fa68c
244
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
33c14e07cf |
Print: Windows lists its printers and spools jobs (#847)
* Print: Windows lists its printers and spools jobs (#756)
The Print dialog's printer list comes from Win32_Printer through PowerShell's
CIM (wmic is gone from Windows 11), and jobs are spooled by the embedded
windows_print.ps1: the in-box Windows.Data.Pdf renders each sheet at the
printer's own resolution and System.Drawing.Printing carries copies,
collation, duplex and colour as the job's settings. Both run through
powershell, so the Rust code touches no Win32 API. Fixes #756, with the
printer-not-showing duplicates #160, #244, #293, #465 and #644; CIM approach
from #452. On CI a paused Generic / Text Only test printer catches the job in
its queue; a parser test keeps the script valid PowerShell.
* Print: fix the Windows await helper and render the job on CI (dry run)
Real-machine testing found [System.WindowsRuntimeSystemExtensions]::GetMethods()
failing with 'does not contain a method named GetMethods': GetMethods is an
instance method of [Type], so it is called with '.', the form the canonical
WinRT-await snippets use, not '::'. The first CI run missed it because the
end-to-end test skipped silently when the runner refused the test printer, so
the script had never actually run.
The script now honours PDFCRAFT_PRINT_DRYRUN=1: render every sheet with
Windows.Data.Pdf to PNGs in the temporary folder and print nothing. The test
runs that half unconditionally, so CI exercises the whole WinRT path —
projection, await helpers, render — on any machine, while the paused-printer
half stays best-effort and prints why it skipped. Win32_Printer enumeration
asks only for Name and Default, so a machine with nine printers waits less.
* Tests: the find-options wait in the viewer test is a deadline too
|
||
|
|
ecd9d158ea | UI: preserve shared document workspace on current main | ||
|
|
b8ddf71e30 |
Drawn signatures and drawings resize from their handles (#920)
A drawn Fill & Sign signature is an Ink annotation, and Ink couldn't be
resized: the Select tool showed no handles and the engine refused
ResizeAnnotation ("Ink comments can't be resized"). Typed and image
signatures (stamps) already resize (#335, #355), so drawn ones were the
gap left in #331.
- annot: set_rect scales an Ink annotation's strokes into the new
rectangle, keeping each side's margin between the strokes and /Rect
so the line isn't clipped, and regenerates the appearance. A straight
stroke with no extent on one axis is centred on it.
- ui-egui: Ink is resizable with the 8 handles. A drawn signature's
corners keep its shape, as image signatures' do; other drawings resize
freely like shapes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
10965fbf5e |
Select tool picks up a signature placed over a form field (#916) (#917)
With the Select tool, a press on a form field went to the field before comments, so a signature (or any movable comment) sitting on top of a field could not be dragged: the cursor showed Move, but the field took the press on the frame the drag started. A slow, hand-speed drag always started while the pointer was still over the field, so it never moved. The Select tool now checks for a movable comment under the press first (select_grabs, sharing fill_grabs' hit logic) and only lets the form field take it when there is none. Away from comments, fields fill as before. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
82b1093b73 |
M7: select and move mixed PDF objects together (#844) (#907)
Preserve source glyphs and artwork, resolve the group atomically, and expose generation-scoped automation tools with selection and rendering regressions. Co-authored-by: Tevinhead <173904641+Tevinhead@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
892f0b78f0 |
M14: give Windows PDF files a dedicated document icon (#890)
* M14: give Windows PDF files a dedicated document icon Embed the document icon as resource 2 through the existing Windows resource build and select it in both PDF association registrations. Keep the asset reproducible and carry its required licence notices in Windows packages. Formatting, strict Clippy, 1,842 existing workspace tests, all 30 WASM crate checks, assets, layering and parity pass. The Windows x64 release package checks pass, and the contributor confirmed the icon in Explorer. Co-authored-by: Codex <noreply@openai.com> * Refresh catalog hashes after merging main --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
9ccf77bdf6 |
M4: traverse large bookmark trees lazily without dropping later items (#876)
* M4: traverse large bookmark trees lazily without dropping later items The outline walk stopped silently at 100,000 bookmarks, and opening a document built the whole nested outline with the same cap. Both walks also recursed once per level and overflowed the stack on a deep chain. organize walks the outline lazily and iteratively with no item cap, and lists it a page at a time (bookmark_page). render keeps a bounded window of the first 10,000 bookmarks, nested at most 33 levels deep, and reports when more exist. automation's bookmark_list pages with offset and limit, and doc_info reports outline_more. The bookmarks panel says when bookmarks are left out. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * M4: keep the bookmarks panel note in English The note was a tl! label, and the coverage tests require every tl! label to exist in each complete catalogue. This PR adds no translations, so the note is now a plain English label in every locale. The i18n catalogues are restored to origin/main. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Bookmarks: translate the panel's limit notice; a numeric destination names a page in the document --------- Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
+21 |
f74c19de87 |
Combine files: move and remove single pages before combining (3 of 3, closes #611) (#790)
* Combine files: a thumbnail grid, card sizes and card actions (refs #611) Combine files shows each file as a card (the default; Grid/List kept in the settings, --combine-view): the first page it adds, stacked when it adds more, its name shortened in the middle. Cards select (click, Ctrl, Shift, arrows by card and by row), drag to reorder (one undo step; the grid scrolls at its edges) and share the list's toolbar, selection and history. Locked, unreadable, bad-range and unmeasurable files say why. Thumbnails render off the UI thread, only for cards in view: at most three render threads at once, counted until they exit (no watchdog replacements), a 32 MB texture budget, keys that never show another page, password or file, and one heavy step per frame on the web. Cards are sized from 60% to 200% (toolbar slider and buttons, Ctrl +/-/0, Ctrl+wheel or pinch over the grid, kept in the settings, --combine-zoom), keeping the card under the pointer in place. Hovering a card shows its actions over the top of its page: remove that file, and a magnifier that shows it large, flipping through the pages it adds; the same on a right click, and Space by keyboard. The toolbar wraps between groups at narrow widths. RenderPool gains retire(), without_replacements() and threads() so a caller can bound the render threads that exist at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Combine files: expand a file into its pages (refs #611) A file that adds more than one page (up to 2000) expands into a card per page it adds, in its range's order, each labelled with its page and the file's name, on a shared tinted band: from its card's hover bar, a right click, or Expand all / Collapse all in the toolbar. A page card's magnifier opens the preview on that page; Up and Down step rows of cards. Expanding is a view: not an undo step, kept across views and zoom, and the combined file is the same either way. Thumbnails are now kept per file and page, looked up on demand (the cost follows the files, not the pages of an expanded file), and a render's document stays open for two seconds so the next page of the same file isn't read again. Adds the Lucide minimize-2 icon (ISC, recorded in ATTRIBUTION). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Combine files: move and remove single pages before combining (closes #611) With a file expanded, single pages are selected (click, Ctrl, Shift), dragged anywhere and removed (a page card's trash, the toolbar, Delete), each one undo step; the files themselves are never changed. A page moved between another file's pages splits its file into parts that share its bytes: the list shows each part as a row with its own page range, and parts that end up side by side join again. A split file still counts, unlocks and combines as one file. organize gains combine_grouped (and Session::combine_grouped): each file is copied once and its pages put in the order shown, so it keeps one bookmark at its first page, its nested bookmarks, links between its own pages, its fields and one copy of its attachments. A page listed twice now gets its own copies of its annotations, and a form field's widget on it joins the same field, so the field keeps one value. The existing combine is unchanged; doc_combine gains an optional groups array (entries with the same number are one file split into parts), so the same combine is reachable headless. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Progress docs: target-app parity, gaps, alpha gate, readiness by audience (#873) Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and feature, format, PDF-spec, hardware, UI and localization parity docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Measure: snap to the implicit closing edge of filled paths (#803) (#862) * fix(organize): reject rotation angles that are not multiples of 90 (#801) (#833) Page rotation only turns in quarter steps, but the degree value was read with an integer division by 90 that threw the remainder away. Asking for 45 degrees saved an unrotated page, 91 and 135 saved a 90 degree turn, and the command still reported success. Reject the value before touching the document instead, so every caller gets an actionable error and the pages are left alone. The automation tool already rejected it; the single edit command did not. Fixes #801. Co-authored-by: Big Pickle <noreply@opencode.ai> * Autosave: snapshot a new unsaved document before its first edit (#815) (#850) * OCR: Recognize text in multiple files refuses paths outside the root (#795) ocr_recognize_files turned a path outside the automation root into a per-file error and returned success, so no_tool_escapes_the_root reported 9 leaks once the OCR models were installed. Without them, loading the models failed first and hid it, and CI never installs them. Every path is now checked before any work starts, and one outside the root refuses the whole call, like the other tools. A missing file inside the root stays a per-file error. Nothing was ever read from outside the root. ocr_recognize_files_refuses_paths_outside_the_root checks this without the models. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(text): match phrases across repeated whitespace (#814) (#831) The phrase search collapsed runs of whitespace in the query but not in the page text it was matching against, so a document drawn with two space glyphs in a row like the issue example never matched the phrase. Single words still matched, which is why it looked like only some files were affected. Collapse whitespace while the text is flattened, the same way the query is normalised. The regression test fails on the old code because the phrase find returns an empty range, and it also covers tabs and single word queries. Fixes #814. Co-authored-by: Big Pickle <noreply@opencode.ai> * fix(edit): keep a comment whose appearance is missing when flattening (#805) (#838) Viewers draw notes, FreeText, ink and stamps from their dictionary when the file stores no /AP /N. flatten only drew annotations that already had an appearance and then removed the rest, so a supported note disappeared and the saved page came back blank. Build the appearance a viewer would show for every selected, visible, non-widget annotation that has none, then draw it as usual. A subtype this build cannot render still fails to build and is handled exactly as before. The new synthetic test draws the note icon into the page and asserts it is baked in; without the fix flatten reports 0 drawn and the icon is lost. Co-authored-by: Big Pickle <noreply@opencode.ai> * fix(comments): keep a reply to a reply in the comment listing (#806) (#839) comment_list collected only the replies whose /IRT pointed at the root comment, so a reply to a reply was listed nowhere at all. Roots and the root count were already right; the nested reply was simply dropped from its thread. Follow the /IRT chain to any depth, with a bound, so every reply under a comment is listed in document order. A reply to a reply now stays discoverable through the tool that promises every comment and its replies. The new test builds a root note, a direct reply and a reply to that reply, then checks both texts are listed and the two notes stay roots. It listed only the direct reply before the fix. Co-authored-by: Big Pickle <noreply@opencode.ai> * Issue #774: keep the catalog out of object streams in encrypted files (Acrobat) (#836) Acrobat reports an encrypted PDF as "damaged and could not be repaired", before asking for the password, when the document catalog sits in an object stream. Every other reader tried (qpdf, pypdf, poppler, PDFKit, PdfCraft) accepts such files, and qpdf keeps the catalog standalone too. Full saves of encrypted documents now write the catalog as its own object; unencrypted output is unchanged. Verified with Acrobat DC on macOS: the same file fails with the catalog packed and opens with it standalone, in AES-256 and RC4-128. Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu Co-authored-by: Tristan von Kries <tristan@MacBook-Pro-von-Tristan.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * fix(edit): refuse a header start number that would wrap the next page (#804) (#834) Page numbering adds the start number to the position of the page in the run. At the top of the u32 range that addition wrapped, so a release build saved zero for the second page and still reported success, and a debug build hit a caught overflow error instead. Check the whole run before drawing anything. When the last page number would pass the u32 limit the operation returns an actionable error and the document keeps no header, rather than a half applied run. Fixes #804. Co-authored-by: Big Pickle <noreply@opencode.ai> * OCR: a scanned euro sign reads as €, not E (#796) The ocrs crate's default alphabet has an E where its recognition model was trained with € (ocrs-models DEFAULT_ALPHABET), so a scanned "€250" was recognised as "E250". pdfcraft-ocr now gives ocrs the alphabet the model was trained with. the_alphabet_is_the_trained_one checks the alphabet without the models; recognize_text_reads_the_euro_sign reads a scan end to end when they are installed. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(comments): include replies to replies in the comment summary (#820) (#840) The comment summary gathered a comment's direct replies only, matching /IRT against the comment itself. A reply to a reply points at the direct reply, so its text never reached the generated summary even though the stored discussion kept it. Replies are now walked to any depth, with a bound, and indented one level per reply. The existing output for direct replies is unchanged. The new test builds a rectangle, a reply and a reply to that reply, then checks the summary carries both texts with the nested one indented under its parent. It omitted the nested reply before the fix. Co-authored-by: Big Pickle <noreply@opencode.ai> * fix(xfdf): drop the replaced note's popup when re-importing a comment (#807) (#860) * fix(view): check Shift-Command-G before Command-G for find navigation (#810) (#835) * view: check Shift-Command-G before Command-G for find navigation (#810) * Tests: the find shortcut test waits for find by a deadline (cherry picked from commit eaf3180bd8ebb353b3d8c5ca7b9ef7548ed55c37) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * fix(editing): recognize paragraph drafts as unsaved work (#811) (#843) * view: check Shift-Command-G before Command-G for find navigation (#810) * fix: recognize paragraph drafts as unsaved work (Issue #811) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * fix(automation): retain batch results with identical filenames (#877) * fix(automation): retain batch results with identical filenames * Format --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * M13: preserve desktop Action Wizard batch results (#886) * M13: preserve desktop Action Wizard batch results Co-Authored-By: Codex <noreply@openai.com> * ROADMAP: date the desktop action outputs entry today --------- Co-authored-by: Tevinhead <173904641+Tevinhead@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * Fit to width fits the shown page in single-page view (#808) (#859) * fix(links): find URLs when lowercasing changes the text length (#816) (#830) find_urls case-folded a copy of the page text and then sliced the original characters with offsets taken from that copy. Case folding can change a string's length — U+0130 (İ) lowercases to "i" plus a combining dot — so the offsets drifted past the end of the source and `chars[i..j]` panicked. The CLI exited 101 before it could create the link; MCP reported an internal tool error but kept serving. Match the ASCII prefixes case-insensitively against the original characters instead, so every offset stays in the source's own index space, and use get() so no lookup can index past the end. A new test reproduces the reported panic on the old code (range start index 41 out of range for slice of length 39) and passes with the fix. pdfcraft-annot's tests, clippy and rustfmt are green. Fixes #816. Co-authored-by: Big Pickle <noreply@opencode.ai> * organize: deleting the opening page drops the catalog OpenAction (#821) (#855) * fix(text): Turkish dotted and dotless i match in case-insensitive search (#892) Case-insensitive search folded the query and the page with str::to_lowercase, which follows no locale: U+0130 (İ) becomes "i" plus a combining dot above (two characters) and U+0131 (ı) has no case pair. So "istanbul" never found "İSTANBUL", and "ırmak"/"kış" never found "IRMAK"/"KIŞ" (or the reverse). The same PageText::find serves Find, Search & Redact (phrases and word lists) and the automation text search, so Turkish words could also be left unredacted. Fold I, i, İ and ı to "i" and every other character with its own lowercase, one character for one character. Case-sensitive search is unchanged. * XFDF keeps polygon and polyline vertices (#809) (#848) Export wrote polygon and polyline comments without their points and import had nowhere to read them from, so an XFDF round trip left the comments with no /Vertices and no appearance. Export now writes the points as XFDF <vertices>x,y;x,y;…</vertices>, and import reads them back (whole x,y pairs only) before the appearance is drawn. Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * XFDF keeps a callout's leader, text-box inset, intent and arrowhead (#819) (#854) Co-authored-by: Brandon Thomas <echelon@gmail.com> * Web: warn before reloading a document with unsaved changes (#812) (#856) * Issue #813: a cancelled recovery password no longer attaches to the next unlocked document (#858) Recovering an encrypted snapshot parked its metadata in `pending_recovered` until the password prompt was answered. Cancel left it there, and the next encrypted document unlocked in the foreground took the snapshot's original path and recovery key: Save would overwrite the other file and delete its snapshot. The association now travels with the prompt (`PasswordPrompt::recovered`). Cancel puts the entry back on offer and leaves the store alone; a wrong password keeps it on the re-raised prompt; the correct one finishes the recovery of the tab that was just opened. `pending_recovered` is gone. Test: crash with an encrypted snapshot of A, Recover, Cancel, open encrypted B with its password. B keeps its own path and is clean, A's snapshot stays in the store, also after closing B. Fails on main at "B keeps its own path". Co-authored-by: Tristan von Kries <tristan@MacBook-Pro-von-Tristan.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * Viewer: touchpad scrolling tracks the finger 1:1 (#759) (#868) A precision touchpad sends pixel-precise `MouseWheel` events. On Windows they arrive with no `TouchPhase::Start`, so egui never takes its no-smoothing "in touch" path and eases any delta of 8 px or more over several frames, leaving the page a frame behind the finger. The page view now applies precise (Point-unit) wheel input itself, 1:1, through the same un-eased scroll path as keyboard and middle-button auto-scroll, and suppresses egui's eased copy for a short window so its internal easing tail neither doubles nor trails the motion. A coarse mouse wheel (Line/Page units) keeps egui's smoothing, which is pleasant for chunky notches, and single-page paging still owns the vertical axis. Regression test: one 200 px precise swipe scrolls the full 200 px at once (an eased path would ramp there over ~0.1 s) and leaves no drift. Co-authored-by: Justin John <babyjjbabyjj549@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * Keep every part when bookmark titles repeat in doc_split (#800) (#863) * Windows: add reproducible security diagnostics for storytold/pdfcraft#747 (#769) Add packaging/windows/security-audit.ps1, a read-only maintainer helper that records a release file's SHA-256, Authenticode status and, with an external YARA binary and rule, every matched string identifier and offset. Document the procedure in docs/releasing.md ("Scanner detections"). No application, dependency, installer or network behaviour changes: the diff touches only the helper and the release documentation. Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Notice bar: the message wraps beside its buttons and lines up with them (#785) (#832) The notice bar above the pages (security, forms, XFA, repaired file, warnings, signatures) laid its message out before its buttons. In a narrow document area (a side panel open, a longer translation) the message ran under "Security settings" and on into the side panel, and it wasn't centred on the buttons. A shared notice_bar helper now lays the buttons out first, right to left, centred on a 28 px row, and gives the message the width that's left: it wraps there (cut short with the full text on hover when less than 120 px is left) and its first line is centred on the buttons' row. Regression test: editing::the_security_notice_wraps_beside_its_buttons_and_lines_up_with_them (one line, wrapped and cut-short widths; fails on main). Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * M10.2: scan JPEG metadata beyond frame headers (#764) Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * Issue #878: Combine stores an image once when its colour space is an indirect name (#883) ImageMagick writes an image's colour space as its own object (`/ColorSpace 10 0 R`, `10 0 obj /DeviceRGB`). Each source's copy of that object was never merged, since it isn't a resource, so identical images compared unequal and every copy was kept: 8 copies of an 80 MB ImageMagick PDF combined into 648 MB instead of 81 MB. Dedupe now compares a reference to a plain value (name, number, boolean or string) as that value. The value objects themselves are still never merged. Combine, Insert Pages and Reduce File Size share this code. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * organize: drop named destinations and their links when a page is deleted (#817) (#851) * JavaScript: limit util.printf widths and output, and array arguments (#770) util.printf accumulated a conversion's width and precision with unchecked arithmetic and no maximum, and its output had no bound; the array arguments of resetForm and the field value/colour setters were read for as many items as the array's length says, which a sparse array makes free. Saturate the numbers and refuse widths and precisions above 4096, output above 1 MiB and arrays longer than 2^20 with a script error, before anything is allocated from them. Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> * Linux: "Use system setting" asks the desktop, not winit (#881) winit reports no system theme on Linux — `ActiveEventLoop::system_theme` is `None` there on every desktop — so `ThemePreference::System` never saw the desktop's choice and silently fell back to whatever was on screen: PdfCraft came up light on a dark desktop, and picking "Use system setting" after Dark kept it dark. The default setting did not follow the system. `pdfcraft-platform` now reads the XDG Desktop Portal's Settings interface off the UI thread and the app follows the answer, including a change made while it runs. The colour scheme is asked for under its GNOME schema name first, which GNOME's and XFCE's portals both answer, then under the older freedesktop one, which KDE answers. A desktop that answers neither leaves the user's own choice alone, exactly as before. Co-authored-by: GyuriX <gyurix@users.noreply.github.com> * Extract/Combine: a page repeated in one selection gets its own annotations (#818) (#852) * M5.7: Fill & Sign tools pick up placed signatures to move and resize them (#822) A signature placed with Fill & Sign could only be moved with the Select tool. While a Fill & Sign tool was active (Sign, Initials, Add text, Check, Date...), hovering the signature showed the placement crosshair, a drag did nothing and a click stamped another mark on top of it. Now, as in Acrobat, a Fill & Sign tool picks up the Fill & Sign marks already on the page: hovering one shows the move cursor, a click selects it, a drag moves it and the selected mark's handles resize it. Away from them the tool places as before, including on other comments. Image signatures keep their live move/resize preview. Comments now carry a fill_sign flag (pdfcraft_annot::is_fill_sign for the engine's list, the same rules in the viewer's inspector), so this also works on reopened files. Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Web: opt-in embedding bridge for host pages (?host=parent) (#842) A page that frames PdfCraft on the same origin can now pass it PDFs and receive saves through postMessage, instead of Save becoming a browser download. Built for a Nextcloud integration, but nothing in it is host-specific. - ui-egui: HostSaveFn/HostDirtyFn hooks (`host_save`, `host_dirty`), None by default. On wasm, Save hands the bytes to `host_save` when set; `host_dirty` hears each frame whether any tab has unsaved work. - pdfcraft-web: `host::Bridge`, enabled only by `?host=parent` inside a frame. Messages: pdfcraft:ready / open / save / dirty / failed; only the parent window on our own origin is heard or answered. `?author=` names comment authors unless the user chose a name. - Test: host_dirty_hears_when_unsaved_work_appears_and_goes. Without `?host=parent` nothing changes. Claude-Session: https://claude.ai/code/session_01G6GtUCaPiCMEQCN4QcdQnp Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * Print: a scaled annotation appearance no longer prints larger than its rectangle (#802) (#861) * Single instance: enable tab handoff in Windows portable mode (#889) (#891) * Single instance: enable tab handoff in Windows portable mode (#889) * Single instance: a portable copy's socket stays in %LOCALAPPDATA%, under its own name (cherry picked from commit c7db1e80a374e80a11504efefbaac680dfa5e872) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * Issue #390: build the DMG with hdiutil create so the app passes codesign --strict (#823) * Issue #390: build the DMG with hdiutil create so the app passes codesign --strict makehybrid -hfs wrote a com.apple.FinderInfo (Finder location -1,-1) on every file in the image, and codesign --verify --deep --strict rejects that as "Finder information, or similar detritus", on the app in the DMG and on a copy installed from it. hdiutil create -srcfolder writes the files as they are; it attaches a device while copying, so it gets up to five tries. A gate mounts the finished image read-only and verifies the app inside with --strict. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu * Issue #390: retry hdiutil attach and detach in the DMG gate too As suggested in review: the gate mounts the signed image under set -e, so a "Resource busy" there would stop a release after the DMG was built. The same five tries with growing pauses now cover create, attach and detach. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu --------- Co-authored-by: Tristan von Kries <tristan@MacBook-Pro-von-Tristan.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * M4: make page-label generation lazy across large documents (#845) * M4: make page-label generation lazy across large documents Page labels were expanded into one String per page, and the whole sequence was refused once the labels passed 4 MiB in total. A valid document with long labels could therefore neither be read nor edited. Add a PageLabels view over the label ranges: label(page) builds one label, and iter() builds the labels one at a time, so no string is kept per page. Edits validate each range from its largest number (Roman numerals by their thousands) instead of building every label, so the check no longer depends on the page count. The per-label length limit, the number-tree work and depth limits and the overflow checks are unchanged. page_labels() keeps its signature and returns the same labels as before for ordinary documents. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * M4: drop the aggregate page-label cap from document inspection Document inspection refused every custom label once their total passed 4 MiB, so a valid document with long labels showed physical page numbers and a warning. Labels are now applied to every page. The per-label length limit, the number-tree work, depth and cycle limits and the prefix bounds are unchanged, and labels are still applied only after every range is accepted. MAX_LABEL_TOTAL_BYTES is removed. The inspector keeps one label per page (PageInfo.label), so its label memory grows with the page count times the label length. The page count is bounded by the objects in the file, and each label by 1,024 bytes. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * M4: keep the inspector's 4 MiB page-label cap until its labels are lazy Commit |
||
|
+21 |
26dec1030b |
Combine files: expand a file into its pages (2 of 3, refs #611) (#788)
* Combine files: a thumbnail grid, card sizes and card actions (refs #611) Combine files shows each file as a card (the default; Grid/List kept in the settings, --combine-view): the first page it adds, stacked when it adds more, its name shortened in the middle. Cards select (click, Ctrl, Shift, arrows by card and by row), drag to reorder (one undo step; the grid scrolls at its edges) and share the list's toolbar, selection and history. Locked, unreadable, bad-range and unmeasurable files say why. Thumbnails render off the UI thread, only for cards in view: at most three render threads at once, counted until they exit (no watchdog replacements), a 32 MB texture budget, keys that never show another page, password or file, and one heavy step per frame on the web. Cards are sized from 60% to 200% (toolbar slider and buttons, Ctrl +/-/0, Ctrl+wheel or pinch over the grid, kept in the settings, --combine-zoom), keeping the card under the pointer in place. Hovering a card shows its actions over the top of its page: remove that file, and a magnifier that shows it large, flipping through the pages it adds; the same on a right click, and Space by keyboard. The toolbar wraps between groups at narrow widths. RenderPool gains retire(), without_replacements() and threads() so a caller can bound the render threads that exist at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Combine files: expand a file into its pages (refs #611) A file that adds more than one page (up to 2000) expands into a card per page it adds, in its range's order, each labelled with its page and the file's name, on a shared tinted band: from its card's hover bar, a right click, or Expand all / Collapse all in the toolbar. A page card's magnifier opens the preview on that page; Up and Down step rows of cards. Expanding is a view: not an undo step, kept across views and zoom, and the combined file is the same either way. Thumbnails are now kept per file and page, looked up on demand (the cost follows the files, not the pages of an expanded file), and a render's document stays open for two seconds so the next page of the same file isn't read again. Adds the Lucide minimize-2 icon (ISC, recorded in ATTRIBUTION). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Progress docs: target-app parity, gaps, alpha gate, readiness by audience (#873) Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and feature, format, PDF-spec, hardware, UI and localization parity docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Measure: snap to the implicit closing edge of filled paths (#803) (#862) * fix(organize): reject rotation angles that are not multiples of 90 (#801) (#833) Page rotation only turns in quarter steps, but the degree value was read with an integer division by 90 that threw the remainder away. Asking for 45 degrees saved an unrotated page, 91 and 135 saved a 90 degree turn, and the command still reported success. Reject the value before touching the document instead, so every caller gets an actionable error and the pages are left alone. The automation tool already rejected it; the single edit command did not. Fixes #801. Co-authored-by: Big Pickle <noreply@opencode.ai> * Autosave: snapshot a new unsaved document before its first edit (#815) (#850) * OCR: Recognize text in multiple files refuses paths outside the root (#795) ocr_recognize_files turned a path outside the automation root into a per-file error and returned success, so no_tool_escapes_the_root reported 9 leaks once the OCR models were installed. Without them, loading the models failed first and hid it, and CI never installs them. Every path is now checked before any work starts, and one outside the root refuses the whole call, like the other tools. A missing file inside the root stays a per-file error. Nothing was ever read from outside the root. ocr_recognize_files_refuses_paths_outside_the_root checks this without the models. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(text): match phrases across repeated whitespace (#814) (#831) The phrase search collapsed runs of whitespace in the query but not in the page text it was matching against, so a document drawn with two space glyphs in a row like the issue example never matched the phrase. Single words still matched, which is why it looked like only some files were affected. Collapse whitespace while the text is flattened, the same way the query is normalised. The regression test fails on the old code because the phrase find returns an empty range, and it also covers tabs and single word queries. Fixes #814. Co-authored-by: Big Pickle <noreply@opencode.ai> * fix(edit): keep a comment whose appearance is missing when flattening (#805) (#838) Viewers draw notes, FreeText, ink and stamps from their dictionary when the file stores no /AP /N. flatten only drew annotations that already had an appearance and then removed the rest, so a supported note disappeared and the saved page came back blank. Build the appearance a viewer would show for every selected, visible, non-widget annotation that has none, then draw it as usual. A subtype this build cannot render still fails to build and is handled exactly as before. The new synthetic test draws the note icon into the page and asserts it is baked in; without the fix flatten reports 0 drawn and the icon is lost. Co-authored-by: Big Pickle <noreply@opencode.ai> * fix(comments): keep a reply to a reply in the comment listing (#806) (#839) comment_list collected only the replies whose /IRT pointed at the root comment, so a reply to a reply was listed nowhere at all. Roots and the root count were already right; the nested reply was simply dropped from its thread. Follow the /IRT chain to any depth, with a bound, so every reply under a comment is listed in document order. A reply to a reply now stays discoverable through the tool that promises every comment and its replies. The new test builds a root note, a direct reply and a reply to that reply, then checks both texts are listed and the two notes stay roots. It listed only the direct reply before the fix. Co-authored-by: Big Pickle <noreply@opencode.ai> * Issue #774: keep the catalog out of object streams in encrypted files (Acrobat) (#836) Acrobat reports an encrypted PDF as "damaged and could not be repaired", before asking for the password, when the document catalog sits in an object stream. Every other reader tried (qpdf, pypdf, poppler, PDFKit, PdfCraft) accepts such files, and qpdf keeps the catalog standalone too. Full saves of encrypted documents now write the catalog as its own object; unencrypted output is unchanged. Verified with Acrobat DC on macOS: the same file fails with the catalog packed and opens with it standalone, in AES-256 and RC4-128. Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu Co-authored-by: Tristan von Kries <tristan@MacBook-Pro-von-Tristan.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * fix(edit): refuse a header start number that would wrap the next page (#804) (#834) Page numbering adds the start number to the position of the page in the run. At the top of the u32 range that addition wrapped, so a release build saved zero for the second page and still reported success, and a debug build hit a caught overflow error instead. Check the whole run before drawing anything. When the last page number would pass the u32 limit the operation returns an actionable error and the document keeps no header, rather than a half applied run. Fixes #804. Co-authored-by: Big Pickle <noreply@opencode.ai> * OCR: a scanned euro sign reads as €, not E (#796) The ocrs crate's default alphabet has an E where its recognition model was trained with € (ocrs-models DEFAULT_ALPHABET), so a scanned "€250" was recognised as "E250". pdfcraft-ocr now gives ocrs the alphabet the model was trained with. the_alphabet_is_the_trained_one checks the alphabet without the models; recognize_text_reads_the_euro_sign reads a scan end to end when they are installed. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(comments): include replies to replies in the comment summary (#820) (#840) The comment summary gathered a comment's direct replies only, matching /IRT against the comment itself. A reply to a reply points at the direct reply, so its text never reached the generated summary even though the stored discussion kept it. Replies are now walked to any depth, with a bound, and indented one level per reply. The existing output for direct replies is unchanged. The new test builds a rectangle, a reply and a reply to that reply, then checks the summary carries both texts with the nested one indented under its parent. It omitted the nested reply before the fix. Co-authored-by: Big Pickle <noreply@opencode.ai> * fix(xfdf): drop the replaced note's popup when re-importing a comment (#807) (#860) * fix(view): check Shift-Command-G before Command-G for find navigation (#810) (#835) * view: check Shift-Command-G before Command-G for find navigation (#810) * Tests: the find shortcut test waits for find by a deadline (cherry picked from commit eaf3180bd8ebb353b3d8c5ca7b9ef7548ed55c37) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * fix(editing): recognize paragraph drafts as unsaved work (#811) (#843) * view: check Shift-Command-G before Command-G for find navigation (#810) * fix: recognize paragraph drafts as unsaved work (Issue #811) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * fix(automation): retain batch results with identical filenames (#877) * fix(automation): retain batch results with identical filenames * Format --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * M13: preserve desktop Action Wizard batch results (#886) * M13: preserve desktop Action Wizard batch results Co-Authored-By: Codex <noreply@openai.com> * ROADMAP: date the desktop action outputs entry today --------- Co-authored-by: Tevinhead <173904641+Tevinhead@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * Fit to width fits the shown page in single-page view (#808) (#859) * fix(links): find URLs when lowercasing changes the text length (#816) (#830) find_urls case-folded a copy of the page text and then sliced the original characters with offsets taken from that copy. Case folding can change a string's length — U+0130 (İ) lowercases to "i" plus a combining dot — so the offsets drifted past the end of the source and `chars[i..j]` panicked. The CLI exited 101 before it could create the link; MCP reported an internal tool error but kept serving. Match the ASCII prefixes case-insensitively against the original characters instead, so every offset stays in the source's own index space, and use get() so no lookup can index past the end. A new test reproduces the reported panic on the old code (range start index 41 out of range for slice of length 39) and passes with the fix. pdfcraft-annot's tests, clippy and rustfmt are green. Fixes #816. Co-authored-by: Big Pickle <noreply@opencode.ai> * organize: deleting the opening page drops the catalog OpenAction (#821) (#855) * fix(text): Turkish dotted and dotless i match in case-insensitive search (#892) Case-insensitive search folded the query and the page with str::to_lowercase, which follows no locale: U+0130 (İ) becomes "i" plus a combining dot above (two characters) and U+0131 (ı) has no case pair. So "istanbul" never found "İSTANBUL", and "ırmak"/"kış" never found "IRMAK"/"KIŞ" (or the reverse). The same PageText::find serves Find, Search & Redact (phrases and word lists) and the automation text search, so Turkish words could also be left unredacted. Fold I, i, İ and ı to "i" and every other character with its own lowercase, one character for one character. Case-sensitive search is unchanged. * XFDF keeps polygon and polyline vertices (#809) (#848) Export wrote polygon and polyline comments without their points and import had nowhere to read them from, so an XFDF round trip left the comments with no /Vertices and no appearance. Export now writes the points as XFDF <vertices>x,y;x,y;…</vertices>, and import reads them back (whole x,y pairs only) before the appearance is drawn. Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * XFDF keeps a callout's leader, text-box inset, intent and arrowhead (#819) (#854) Co-authored-by: Brandon Thomas <echelon@gmail.com> * Web: warn before reloading a document with unsaved changes (#812) (#856) * Issue #813: a cancelled recovery password no longer attaches to the next unlocked document (#858) Recovering an encrypted snapshot parked its metadata in `pending_recovered` until the password prompt was answered. Cancel left it there, and the next encrypted document unlocked in the foreground took the snapshot's original path and recovery key: Save would overwrite the other file and delete its snapshot. The association now travels with the prompt (`PasswordPrompt::recovered`). Cancel puts the entry back on offer and leaves the store alone; a wrong password keeps it on the re-raised prompt; the correct one finishes the recovery of the tab that was just opened. `pending_recovered` is gone. Test: crash with an encrypted snapshot of A, Recover, Cancel, open encrypted B with its password. B keeps its own path and is clean, A's snapshot stays in the store, also after closing B. Fails on main at "B keeps its own path". Co-authored-by: Tristan von Kries <tristan@MacBook-Pro-von-Tristan.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * Viewer: touchpad scrolling tracks the finger 1:1 (#759) (#868) A precision touchpad sends pixel-precise `MouseWheel` events. On Windows they arrive with no `TouchPhase::Start`, so egui never takes its no-smoothing "in touch" path and eases any delta of 8 px or more over several frames, leaving the page a frame behind the finger. The page view now applies precise (Point-unit) wheel input itself, 1:1, through the same un-eased scroll path as keyboard and middle-button auto-scroll, and suppresses egui's eased copy for a short window so its internal easing tail neither doubles nor trails the motion. A coarse mouse wheel (Line/Page units) keeps egui's smoothing, which is pleasant for chunky notches, and single-page paging still owns the vertical axis. Regression test: one 200 px precise swipe scrolls the full 200 px at once (an eased path would ramp there over ~0.1 s) and leaves no drift. Co-authored-by: Justin John <babyjjbabyjj549@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * Keep every part when bookmark titles repeat in doc_split (#800) (#863) * Windows: add reproducible security diagnostics for storytold/pdfcraft#747 (#769) Add packaging/windows/security-audit.ps1, a read-only maintainer helper that records a release file's SHA-256, Authenticode status and, with an external YARA binary and rule, every matched string identifier and offset. Document the procedure in docs/releasing.md ("Scanner detections"). No application, dependency, installer or network behaviour changes: the diff touches only the helper and the release documentation. Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Notice bar: the message wraps beside its buttons and lines up with them (#785) (#832) The notice bar above the pages (security, forms, XFA, repaired file, warnings, signatures) laid its message out before its buttons. In a narrow document area (a side panel open, a longer translation) the message ran under "Security settings" and on into the side panel, and it wasn't centred on the buttons. A shared notice_bar helper now lays the buttons out first, right to left, centred on a 28 px row, and gives the message the width that's left: it wraps there (cut short with the full text on hover when less than 120 px is left) and its first line is centred on the buttons' row. Regression test: editing::the_security_notice_wraps_beside_its_buttons_and_lines_up_with_them (one line, wrapped and cut-short widths; fails on main). Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * M10.2: scan JPEG metadata beyond frame headers (#764) Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * Issue #878: Combine stores an image once when its colour space is an indirect name (#883) ImageMagick writes an image's colour space as its own object (`/ColorSpace 10 0 R`, `10 0 obj /DeviceRGB`). Each source's copy of that object was never merged, since it isn't a resource, so identical images compared unequal and every copy was kept: 8 copies of an 80 MB ImageMagick PDF combined into 648 MB instead of 81 MB. Dedupe now compares a reference to a plain value (name, number, boolean or string) as that value. The value objects themselves are still never merged. Combine, Insert Pages and Reduce File Size share this code. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * organize: drop named destinations and their links when a page is deleted (#817) (#851) * JavaScript: limit util.printf widths and output, and array arguments (#770) util.printf accumulated a conversion's width and precision with unchecked arithmetic and no maximum, and its output had no bound; the array arguments of resetForm and the field value/colour setters were read for as many items as the array's length says, which a sparse array makes free. Saturate the numbers and refuse widths and precisions above 4096, output above 1 MiB and arrays longer than 2^20 with a script error, before anything is allocated from them. Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> * Linux: "Use system setting" asks the desktop, not winit (#881) winit reports no system theme on Linux — `ActiveEventLoop::system_theme` is `None` there on every desktop — so `ThemePreference::System` never saw the desktop's choice and silently fell back to whatever was on screen: PdfCraft came up light on a dark desktop, and picking "Use system setting" after Dark kept it dark. The default setting did not follow the system. `pdfcraft-platform` now reads the XDG Desktop Portal's Settings interface off the UI thread and the app follows the answer, including a change made while it runs. The colour scheme is asked for under its GNOME schema name first, which GNOME's and XFCE's portals both answer, then under the older freedesktop one, which KDE answers. A desktop that answers neither leaves the user's own choice alone, exactly as before. Co-authored-by: GyuriX <gyurix@users.noreply.github.com> * Extract/Combine: a page repeated in one selection gets its own annotations (#818) (#852) * M5.7: Fill & Sign tools pick up placed signatures to move and resize them (#822) A signature placed with Fill & Sign could only be moved with the Select tool. While a Fill & Sign tool was active (Sign, Initials, Add text, Check, Date...), hovering the signature showed the placement crosshair, a drag did nothing and a click stamped another mark on top of it. Now, as in Acrobat, a Fill & Sign tool picks up the Fill & Sign marks already on the page: hovering one shows the move cursor, a click selects it, a drag moves it and the selected mark's handles resize it. Away from them the tool places as before, including on other comments. Image signatures keep their live move/resize preview. Comments now carry a fill_sign flag (pdfcraft_annot::is_fill_sign for the engine's list, the same rules in the viewer's inspector), so this also works on reopened files. Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Web: opt-in embedding bridge for host pages (?host=parent) (#842) A page that frames PdfCraft on the same origin can now pass it PDFs and receive saves through postMessage, instead of Save becoming a browser download. Built for a Nextcloud integration, but nothing in it is host-specific. - ui-egui: HostSaveFn/HostDirtyFn hooks (`host_save`, `host_dirty`), None by default. On wasm, Save hands the bytes to `host_save` when set; `host_dirty` hears each frame whether any tab has unsaved work. - pdfcraft-web: `host::Bridge`, enabled only by `?host=parent` inside a frame. Messages: pdfcraft:ready / open / save / dirty / failed; only the parent window on our own origin is heard or answered. `?author=` names comment authors unless the user chose a name. - Test: host_dirty_hears_when_unsaved_work_appears_and_goes. Without `?host=parent` nothing changes. Claude-Session: https://claude.ai/code/session_01G6GtUCaPiCMEQCN4QcdQnp Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * Print: a scaled annotation appearance no longer prints larger than its rectangle (#802) (#861) * Single instance: enable tab handoff in Windows portable mode (#889) (#891) * Single instance: enable tab handoff in Windows portable mode (#889) * Single instance: a portable copy's socket stays in %LOCALAPPDATA%, under its own name (cherry picked from commit c7db1e80a374e80a11504efefbaac680dfa5e872) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * Issue #390: build the DMG with hdiutil create so the app passes codesign --strict (#823) * Issue #390: build the DMG with hdiutil create so the app passes codesign --strict makehybrid -hfs wrote a com.apple.FinderInfo (Finder location -1,-1) on every file in the image, and codesign --verify --deep --strict rejects that as "Finder information, or similar detritus", on the app in the DMG and on a copy installed from it. hdiutil create -srcfolder writes the files as they are; it attaches a device while copying, so it gets up to five tries. A gate mounts the finished image read-only and verifies the app inside with --strict. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu * Issue #390: retry hdiutil attach and detach in the DMG gate too As suggested in review: the gate mounts the signed image under set -e, so a "Resource busy" there would stop a release after the DMG was built. The same five tries with growing pauses now cover create, attach and detach. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu --------- Co-authored-by: Tristan von Kries <tristan@MacBook-Pro-von-Tristan.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * M4: make page-label generation lazy across large documents (#845) * M4: make page-label generation lazy across large documents Page labels were expanded into one String per page, and the whole sequence was refused once the labels passed 4 MiB in total. A valid document with long labels could therefore neither be read nor edited. Add a PageLabels view over the label ranges: label(page) builds one label, and iter() builds the labels one at a time, so no string is kept per page. Edits validate each range from its largest number (Roman numerals by their thousands) instead of building every label, so the check no longer depends on the page count. The per-label length limit, the number-tree work and depth limits and the overflow checks are unchanged. page_labels() keeps its signature and returns the same labels as before for ordinary documents. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * M4: drop the aggregate page-label cap from document inspection Document inspection refused every custom label once their total passed 4 MiB, so a valid document with long labels showed physical page numbers and a warning. Labels are now applied to every page. The per-label length limit, the number-tree work, depth and cycle limits and the prefix bounds are unchanged, and labels are still applied only after every range is accepted. MAX_LABEL_TOTAL_BYTES is removed. The inspector keeps one label per page (PageInfo.label), so its label memory grows with the page count times the label length. The page count is bounded by the objects in the file, and each label by 1,024 bytes. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * M4: keep the inspector's 4 MiB page-label cap until its labels are lazy Commit |
||
|
|
5268047a33 |
Combine files: a thumbnail grid, card sizes and card actions (refs #611) (#777)
Combine files shows each file as a card (the default; Grid/List kept in the settings, --combine-view): the first page it adds, stacked when it adds more, its name shortened in the middle. Cards select (click, Ctrl, Shift, arrows by card and by row), drag to reorder (one undo step; the grid scrolls at its edges) and share the list's toolbar, selection and history. Locked, unreadable, bad-range and unmeasurable files say why. Thumbnails render off the UI thread, only for cards in view: at most three render threads at once, counted until they exit (no watchdog replacements), a 32 MB texture budget, keys that never show another page, password or file, and one heavy step per frame on the web. Cards are sized from 60% to 200% (toolbar slider and buttons, Ctrl +/-/0, Ctrl+wheel or pinch over the grid, kept in the settings, --combine-zoom), keeping the card under the pointer in place. Hovering a card shows its actions over the top of its page: remove that file, and a magnifier that shows it large, flipping through the pages it adds; the same on a right click, and Space by keyboard. The toolbar wraps between groups at narrow widths. RenderPool gains retire(), without_replacements() and threads() so a caller can bound the render threads that exist at once. Co-authored-by: Andrew Taylor <andrew.taylor@tayeng.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
65e89aefb2 |
M3, #740: column select (Alt/Option-drag, or Edit ▸ Column select) (#765)
As in Acrobat, holding Alt (Option on macOS) when a drag starts selects only the text inside the rectangle drawn, instead of everything between two points in reading order. It works with the Select tool, the text markup tools and Redact, so a column can be copied, highlighted or redacted. - render: PageText::glyphs_in (glyph centre inside the rectangle), glyph_rects (one box per run, never covering unselected text) and column_text: rows top to bottom, side-by-side pieces separated by a tab, right to left in right-to-left rows, reading order for vertical text. - ui-egui: a selection is a reading-order run or a column rectangle; the rectangle is outlined while dragging and the cursor is a crosshair. ⇧-click clears a column selection (it has no anchor glyph). - Edit ▸ Column select (edit.column_select, quick tool "column-select") does it with a plain drag. It is the way in on Linux desktops whose window manager takes Alt-drag for moving windows (Xfce, Cinnamon, MATE, KDE Plasma 5 by default), where the app never sees the drag. - automation: text_extract takes rect for the same extraction headless. - "Column select" translated in all 15 catalogs (Arabic included); their ATTRIBUTION.toml hashes updated. - parity: new view.column-select entry with platform notes and gaps (no selection across pages, no ⇧-click extension). Windows: winit hands an Alt release to the system menu loop only when the window has a native menu, which PdfCraft's does not, so Alt-drag cannot leave the window in menu mode. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
2a02d5ddab |
M14: localize displayed shortcut key names (#496) (#884)
Co-authored-by: Tevinhead <173904641+Tevinhead@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
86dd8d8283 |
Fill colour for rectangle, oval and polygon comments (#686) (#793)
* M5: fill colour for rectangle, oval and polygon comments (#686) Rectangles, ovals, polygons and clouds could only be given a border colour. They can now be filled, or have their fill removed: - the comment quick bar has a Fill colour control for those tools ("No fill" or a swatch), kept with the other per-tool styles; - Comment Properties > Appearance has a Fill colour row, applied in the same undo step as the other changes and carried over by Make Current Properties Default; - comment_edit takes `fill` (a colour, or "none"). pdfcraft-annot gains `set_fill` (writes or removes /IC and redraws the appearance; lines, ink and locked comments refuse it) and reports `fillable`/`fill` in the comment properties. The engine adds Edit::FillAnnotation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019XAJjko29kzKj7ULXoXSnN * M5: update ATTRIBUTION.toml hashes for the edited i18n catalogs (#686) The fill-colour strings changed 13 translation catalogs, so xtask's the_real_manifest_passes failed on ubuntu and windows CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
3906dcdff5 |
Exported images keep their transparency, JPEG cut-outs included (#645) (#763)
* M10: exported images keep their transparency, JPEG cut-outs included (#645) Export all images and Save image as wrote a JPEG with a soft mask as the bare JPEG and dropped soft masks of another size, JPEG-compressed soft masks, stencil /Mask images and colour-key /Mask arrays, so cut-out images came out on a solid white or black background. - An image with transparency (/SMask, stencil /Mask, colour-key /Mask) is written as a PNG with it as alpha, JPEG images included (decoded with the image crate's JPEG decoder, already in the tree). JPEGs without transparency are still written byte for byte. - Soft and stencil masks of another size are scaled to the image's size; /Decode inversion is honoured and /Matte pre-blending is undone. - Image sizes are checked (positive, at most 100 Mpx) before use, so a negative /Width no longer overflows in Save image as. - Regression tests: JPEG + soft mask, scaled soft mask, stencil mask, colour key, matte, and damaged masks/sizes (all fail or panic on main). Fixes #645 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M10: use as_chunks for colour-key pairs (clippy 1.99 chunks_exact_to_as_chunks) (#645) Rust 1.99's clippy denies chunks_exact with a constant size, which failed the Clippy step on all CI targets. as_chunks::<2> also drops the indexing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
8309c84a59 |
M12: XFA rows in areas get their instances, and areas stay out of data paths (#853)
A Designer form whose table (a top-to-bottom subform inside an area of a positioned page) asks for several rows with `<occur min="10"/>` was laid out with one row, and a FormCalc total looping over the rows stayed empty. - layout: a repeating subform inside a top-to-bottom container in positioned content is drawn and measured once per instance (occur and data), with its own SOM index, as flowed content already was. One `instances` helper now serves flowing, table rows and this case. - data: areas are named in SOM expressions but are not data scopes. `data_path(tpl, som)` leaves them out (unknown segments are kept), and layout, the script tree, reading and writing the datasets and the engine's value and row operations use it. Before, values Adobe's viewers saved were not read, what was filled here went under a new element named after the area (which Adobe's viewers would most likely not read), and scripts counted the rows there: one, so a loop over the table's rows failed on the missing ones. - write_datasets_with / read_values_with take the parsed template when the caller has it (the engine's cached one); the old functions parse the document's. - Tests: layout regression, data_path rules, and an engine round trip shaped like such a form (rows, Adobe-written values, a FormCalc total over every row, the data written back, reopening). Signed-off-by: Hadi Chokr <hadichokr@icloud.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
6116b0034c |
M9: name the Windows store certificates that can't sign (#179) (#761)
A store certificate PdfCraft can't use (no private key, a P-521 / Brainpool / Ed25519 key, a key CNG can't open) was dropped from the Sign dialog silently, so a user whose CA-issued identity shows in Acrobat but not here had nothing to go on. `windows::list` now returns those under `unusable` with the subject, fingerprint and a reason that names the Windows error code; the Sign dialog shows them greyed and unselectable with the reason, and `sign_windows_ids` returns them. A key that needs a prompt to open (NTE_SILENT_CONTEXT, NTE_UI_REQUIRED while listing) is listed and prompts at signing instead of being skipped. Certificates without a private key stay out of the dialog but are reported by the tool. Tests: the explained-error table, the store listing's invariants, a key-less certificate in the ignored store test, the automation output, and a kittest dialog test for a greyed entry. Verified by hand that a .p12 imported into the CNG and legacy Microsoft providers lists, and that a P-521 certificate is explained. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
4dafbfc325 |
M11: Reduce file size and Optimize PDF drop images nothing draws, such as a deleted image (#894)
Deleting an image with Edit a PDF removes its Do operation, but the image stays in the page's /Resources /XObject, so every full rewrite (Reduce file size, Optimize PDF) carried its data along: a page left blank still weighed the whole picture. Clean Up now drops image and form XObjects from a page's own resources when their name is written in no stream the document still uses. Shared and inherited resources are left alone, and if any stream can't be read the way a viewer would read it, nothing is removed: viewers look names up through parent resources, so only a name written nowhere is safe to drop. doc_optimize reports unused_xobjects. Co-authored-by: Andrew Taylor <andrew.taylor@tayeng.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4fd2c2b3a3 |
M11: Reduce file size shrinks office scans, and never saves a bigger copy (refs #490) (#849)
Office scanners write the default decode array (/Decode [0 1 0 1 0 1]) on their page images. Reduce skipped every image with a decode array, so a scan stayed the same size. The default array is now treated as absent; any other array still skips the image. Reduce also saved its copy even when it wasn't smaller, reported as "-N% smaller". It now saves nothing and says the file is already as small as it can be made; doc_reduce returns "written": false. Optimize PDF still saves (what it discards may matter more than the size) but says "not smaller". Co-authored-by: Andrew Taylor <andrew.taylor@tayeng.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
5ad2ecfef9 |
M5.7: Fill & Sign tools pick up placed signatures to move and resize them (#822)
A signature placed with Fill & Sign could only be moved with the Select tool. While a Fill & Sign tool was active (Sign, Initials, Add text, Check, Date...), hovering the signature showed the placement crosshair, a drag did nothing and a click stamped another mark on top of it. Now, as in Acrobat, a Fill & Sign tool picks up the Fill & Sign marks already on the page: hovering one shows the move cursor, a click selects it, a drag moves it and the selected mark's handles resize it. Away from them the tool places as before, including on other comments. Image signatures keep their live move/resize preview. Comments now carry a fill_sign flag (pdfcraft_annot::is_fill_sign for the engine's list, the same rules in the viewer's inspector), so this also works on reopened files. Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
40a57f0d19 |
M13: preserve desktop Action Wizard batch results (#886)
* M13: preserve desktop Action Wizard batch results Co-Authored-By: Codex <noreply@openai.com> * ROADMAP: date the desktop action outputs entry today --------- Co-authored-by: Tevinhead <173904641+Tevinhead@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
9cd07e4793 |
M8: redact lists of words or phrases (protect.redact-word-lists) (#757)
* M8: redact lists of words or phrases Find text and redact gains "Multiple words or phrases": a list with one word or phrase per line, typed or imported from a .txt file (Import list…), whose every match is marked in one undo step. Lists are trimmed, deduplicated and bounded (1,000 entries of 256 characters; files up to 1 MB are read). redact_mark takes words and reports matched_words, the marks each entry made. protect.redact-word-lists is shipped; whole-word and case-sensitive matching and exporting the list are still missing. * M8: translate word-list labels into Arabic The Arabic catalog arrived on main after this branch, so the new Find text and redact strings were still English and failed arabic_covers_ui_literals. * Tests: drop sharp grid renders when the thumbnail will cover the cell Zooming the organize grid back out left the sharp page raster in place until a thumbnail texture existed, so the zoom test failed when that texture had not arrived yet. * M8: Czech rows for the word-list redaction labels Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
aa6242d0c6 |
M4: bookmarks from tagged headings (organize.bookmarks-from-structure) (#754)
* Parity: merge the duplicate form.field-list evidence and notes #649 and #650 each added `evidence` and `notes` to the same entry, so the TOML has duplicate keys and `cargo xtask parity` fails on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: bookmarks from tagged headings (organize.bookmarks-from-structure) Bookmark options > New bookmarks from structure (command bookmark.from_structure, tool bookmark_from_structure) turns a tagged document's headings into bookmarks, as Acrobat's New Bookmarks From Structure does for headings: H1-H6 through the role map, a plain H by its Sect/Part/Art nesting, nested by level under a new first "Untitled" bookmark, each linked to its structure element (/SE, ISO 32000-2 §12.3.3). Titles come from /ActualText, the marked text drawn on the page (decoded through the font's ToUnicode or encoding), /Alt or /T. The structure walk is bounded (depth, visits, heading and title caps, seen-set) and survives self-referencing elements; an untagged document gets an actionable error; the edit is one undo step under the assemble permission. Not yet: choosing other element types, and destinations at the heading's position rather than its page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: size the shared raster allowance from thumbnail demand The canvas test planted a 132×171 thumbnail whose tag did not match the request finish_render_frame computes. On FreeBSD that real render finished in the same frame, so the retained raster exceeded 132×171×4. Plant a thumbnail that already satisfies demand and compare against that size. * M4: a heading's text stops gathering once it is far past the title length A heading whose marked-content list repeats one mark up to the visit cap could otherwise collect about a gigabyte before the title is cut to 256 characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
0bc619cd02 |
Extract pages: a File name field names the extracted files (#737) (#751)
* Extract pages: a File name field names the extracted files (#737) The Extract pages dialog had no way to name the result: separate files were always "<document> (page N).pdf" in the chosen folder, and a new tab was "<document> (extract).pdf". The dialog now has a File name field, filled in with the document's name when it opens. Separate files are "<name> (page N).pdf"; a single renamed page is "<name>.pdf"; extracting into a new tab names the tab (and so the Save As suggestion) "<name>.pdf". The name is made safe for every platform (separators, reserved and control characters, trailing dots and spaces, any-case ".pdf"), and falls back to the document's name when nothing usable is left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Extract pages: Windows device names get a prefix, and the name is capped at 200 bytes "NUL.pdf" writes nowhere on Windows, and 200 CJK characters overflow the 255-byte name limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
49a8d04f24 |
M9: sign and timestamp encrypted documents (#753)
* M9: sign and timestamp encrypted documents Signing a password-protected PDF failed with "not supported: signing encrypted documents". The writer encrypted every string, including the signature's /Contents placeholder, which the signer patches in place after hashing. - cos: a signature dictionary's /Contents is never encrypted or decrypted (ISO 32000-2 §7.6.2), identified by /Type /Sig|/DocTimeStamp or by /Contents + /ByteRange. Existing signatures in encrypted files now read their real CMS and survive rewrites of their dictionary. - sign: the blanket refusal is replaced by the document's permissions (Table 22) when opened with the user password: existing fields and document timestamps need fill-forms, new fields need annotate + modify, certifying needs modify. Refusals name the permissions password. - Tests for all four algorithms, counter-signing, tampering, permissions, document timestamps and the sign_document tool; parity entry sign.encrypted-documents; ROADMAP log. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Hadi Chokr <hadichokr@icloud.com> * M9: signing an encrypted document waits for a pending security change to be saved Otherwise the incremental save becomes a full rewrite that applies or removes protection while signing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Signed-off-by: Hadi Chokr <hadichokr@icloud.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
7a33d4bf25 |
M9: the signer's certificate must allow document signing (key usage, EKU, critical extensions) (#749)
* Parity: merge the duplicate form.field-list evidence and notes #649 and #650 each added `evidence` and `notes` to the same entry, so the TOML has duplicate keys and `cargo xtask parity` fails on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M9: the signer's certificate must allow document signing (key usage, EKU, critical extensions) A trusted signer whose certificate may not sign documents was reported Valid. Validation now checks the signer certificate (RFC 5280 §4.2.1.3, §4.2.1.12, §4.2): a keyUsage needs digitalSignature or nonRepudiation, an extended key usage needs a document-signing purpose (any, emailProtection, codeSigning, RFC 9336 documentSigning, Adobe Authentic Documents, Microsoft document signing), and no critical extension may be one PdfCraft doesn't process. Otherwise the verdict is Unknown and the details say why. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M9: a critical qcStatements extension doesn't make a qualified certificate unusable Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
1c33e6f851 |
M14: Arabic interface language (#692)
* M14: Arabic interface language A 2,136-entry Arabic catalog (six CLDR plural forms, Arabic month and weekday names), selected by `ar` and every `ar-*` locale. egui joins Arabic letters but places words left to right, so the catalog is put into display order when it loads (bidi::display_rtl); placeholders, keyboard shortcuts and caption colons stay in place. The command search matches a multi-word Arabic query word by word. The craft-fonts pin moves to the revision with Noto Sans Arabic. crates/fonts/build.rs now refuses to embed Adobe type designs, so the Noto Sans CJK SC in that revision is never embedded (AGENTS.md §1.1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M14: Arabic catalog follows the {key} shortcut labels and has Redaction code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
bfa65a9bfc |
M8: redaction code sets (U.S. FOIA, Privacy Act) as overlay text (#741)
* M8: redaction code sets (U.S. FOIA, Privacy Act) as overlay text Redaction properties offers "Custom text" or "Redaction code" under "Use overlay text". Picking the U.S. FOIA (5 U.S.C. 552(b)) or U.S. Privacy Act (552a) set and ticking codes makes new marks carry them as overlay text, in the set's order. redact_mark takes code_set and codes and rejects unknown sets or codes with the allowed list. Also merges a duplicated evidence/notes pair in form.field-list that made parity/acrobat-features.toml fail to parse on main. * M8: say why the default redaction code set can't fail Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: pin the clock in form_bulk (saves a second apart differed in /ModDate on Windows CI) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e4cd9e2557 |
PDF/A: no sRGB output intent for CMYK documents, and pdfa_verify checks the intent's colour space (#667) (#732)
* PDF/A: no sRGB output intent for CMYK documents, and verify checks the intent's colour space (#667) pdfa_convert always added an sRGB output intent when there was none, even for documents whose only content is DeviceCMYK, and pdfa_verify then called them compliant: it only checked that some PDF/A output intent existed. ISO 19005-2 6.2.4.3 allows DeviceCMYK only with a CMYK output intent, and DeviceRGB only with an RGB one. - verify reads the PDF/A output intent's destination profile (/N, else the ICC header's data colour space) and reports DeviceCMYK under a non-CMYK intent and DeviceRGB under a non-RGB one (6.2.4.3, not fixable). - convert adds the generated sRGB intent only when the document has no DeviceCMYK; a CMYK document keeps the existing "needs a CMYK output intent (not added automatically)" issue instead of a wrong intent. - Device colour detection tells gray, RGB and CMYK apart (gray works under any intent). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * PDF/A: DefaultRGB/DefaultCMYK resources satisfy the output-intent colour check ISO 19005-2 6.2.4.3 accepts device colour drawn through a default colour space under any output intent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
d94da2038a |
M2: pages with /UserUnit are shown at their size in points, as in Acrobat (#722)
* M2: pages with /UserUnit are shown at their size in points, as in Acrobat /UserUnit gives the size of a page's user-space unit in points; PdfCraft ignored it everywhere except the measuring tools, so a /UserUnit 2 page was shown, extracted, printed and edited at half its size, and the measuring tools disagreed with the rest of the app. The value is now read as Acrobat reads it (the page's own entry, a number from 1 up, at most 75,000; anything else counts as 1) in two places: - vendored hayro-syntax Page (patch 13): render_dimensions and initial_transform give the page in points, so rendering, tiles, PageInfo and the text layer follow; a page without it keeps exactly the transform it had. - pdfcraft-model Page: user_unit(), and display_size/view_matrix in points, so print, header/footer, watermark, background, added content and measuring agree with the screen. The measure crate drops its own arithmetic. Added items record the /UserUnit they were written for; items without the record (written before) read back converted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M2: an added item's recorded /UserUnit is clamped like the page's An infinite or huge recorded /UserUnit made the scale factor 0 and collapsed the item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
99c7dd9311 |
Sign: smart cards and tokens in the Windows store ask for their PIN (#694) (#742)
* Sign: decide Windows store keys in code every platform builds #646 fixed the Windows build after #386 with `_` arms in the two matches in windows.rs. That module is cfg(windows), so Linux and macOS CI never compile it, and the next PublicKey variant will either break the Windows build again or, now, fall into `_` without anyone choosing. The store's two decisions move into keys.rs, which every platform builds, with every variant named and no `_` arm: - PublicKey::store_signing_key: RSA, or ECDSA with 256/384 bits, or None for the kinds PdfCraft only verifies with (P-521, brainpool, Ed25519, Unsupported). - PublicKey::store_signature_der: CNG's raw r||s as DER (RSA as is); the verify-only kinds return SignError::Unsupported, as since #646. windows.rs calls both, with no `_` arms left. Behaviour is unchanged. tests/store_keys.rs runs everywhere: every variant's answer (with an exhaustive match, so a new variant must be added to it), the P-256 and P-384 conversion round-tripped through PublicKey::verify (code that used to compile only on Windows), and malformed CNG signatures erroring rather than panicking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Sign: smart cards and tokens in the Windows store ask for their PIN (#694) Signing with a Windows store identity whose key lives on a smart card or USB token failed with NTE_SILENT_CONTEXT (0x80090022) and no PIN dialog. `identities()` acquires each key with CRYPT_ACQUIRE_SILENT_FLAG so that listing never prompts, and signing reused that handle after `set_silent(false)`. That only drops NCRYPT_SILENT_FLAG from NCryptSignHash: a provider handle that was opened silently stays silent, so a card that needs its PIN refuses instead of asking. `WindowsKey` now keeps the certificate context and acquires a fresh, non-silent key handle when it signs, so the provider can show its own dialog. Listing is unchanged and still silent. Reproduced and checked by hand with a qualified certificate on a SafeNet token (SafeNet Smart Card Key Storage Provider, RSA-2048): 0.5.0 fails with 80090022, this build shows the SafeNet PIN dialog and signs. The software-key round trip (`signing_with_windows_store_identities`, --ignored) still passes on Windows 11. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Sign: a Windows store identity signs with its issuers A digital ID file brings its certificate chain and the signature embeds it; a Windows store identity signed with its own certificate only. A certificate issued by an intermediate CA (the usual case on a token) then validates only where the reader already has that CA. `windows::find`, which fetches the identity to sign with, now fills `chain` with the certificates Windows chains it to, without the root. `identities()` still builds no chains, so listing stays fast and local. A chain that can't be built leaves `chain` empty, as before. Test `a_windows_store_identity_signs_with_its_issuers` (--ignored, like the other store test): a temporary root, issuer and signer; the signature validates against the root alone. The cleanup guard now also removes the public copy New-SelfSignedCertificate leaves in Intermediate Certification Authorities, which the existing test left behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: James Locke <336218001+jcl-bit@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: mmmaarian03-crypto <253881360+mmmaarian03-crypto@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
06a7b9a7ca |
Issue #666: images made into a PDF keep their ICC profile (#729)
Create from images (doc_create, and image_xobject for stamps, signatures and inserted images) dropped a JPEG's or PNG's embedded ICC profile, so a CMYK JPEG became untagged /DeviceCMYK. - JPEG: the APP2 ICC_PROFILE chunks are joined by sequence number; a profile with a missing or repeated chunk is ignored. - PNG: the iCCP profile is read. - A profile whose header is sound (acsp) and whose colour space matches the image (GRAY/RGB/CMYK for 1/3/4 components) becomes [/ICCBased <stream>] with /N and the device space as /Alternate. Anything else keeps the device space, as before. JPEG data and an Adobe CMYK /Decode are unchanged. - Images in one document that carry the same profile share one profile object. TIFF profiles are not read yet. Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
abe0366d26 |
M3: document why fixed-zoom sizes differ from Acrobat (no runtime change) (#736)
* Parity: document the Page Display resolution gap at fixed zoom view.zoom-to-percent and view.zoom-actual-size are shipped with empty notes. Record that PdfCraft's 100% is its fixed 96-dpi basis, that Acrobat's fixed-percentage display also depends on its Page Display Resolution preference, and that PdfCraft does not offer that preference yet (tracked with misc.prefs-page-display). Status, commands, tools and evidence are unchanged. No runtime behavior changed. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Parity: state the Acrobat fixed-zoom formula in the zoom notes The zoom percentage and Actual Size notes now give the measured rule (logical px per PDF point = ppi / 72 × zoom), name the Custom resolutions checked black-box, and say that Use System Setting is not reproduced. Status, commands, tools and evidence are unchanged. No runtime behavior changed. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> |
||
|
|
ade5dc4dcb |
Print: list driverless CUPS destinations, not only permanent queues (#658)
* Print: list driverless CUPS destinations, not only permanent queues The Print dialog's printer list came from `lpstat -p -d`, which lists only permanent queues. A driverless IPP printer discovered on the network — the standard setup on modern Linux desktops — has no permanent queue: only `lpstat -e` (CUPS >= 1.7) names it, and CUPS builds a temporary queue when `lp` sends it a job. Until now such printers were invisible and the dialog showed only Save as PDF. Enumerate `lpstat -e` alongside `lpstat -p -d` and merge the answers: queues first, deduplicated, default still from `-d`; a spooler without `-e` keeps today's behaviour and loses only the driverless names. The .deb/.rpm now recommend cups-client so a machine without a print stack is not silently printerless. Fixes #652 Co-authored-by: ZCode (GLM-5.3) <noreply@z.ai> * Print: fix the off-unix build and a driverless default's marker `lpstat_e_command` is called only from `printers`' unix block, so off unix it was dead code and CI's Windows clippy gate (`-D warnings`) failed to build the crate. Gate it like its caller. The default destination can itself be driverless — `lpoptions -d` names a destination no queue exists for — and the merge appended it from `lpstat -e` with `default: false`, so the dialog preselected the first queue instead of the user's default. The `-d` default now marks the appended entry too; the test covers it, and CRLF in `-e` output. Co-authored-by: ZCode (GLM-5.3) <noreply@z.ai> --------- Co-authored-by: ZCode (GLM-5.3) <noreply@z.ai> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
a99b39396f |
M6: bulk form-field properties and selection (#95) (#650)
* M6: bulk-edit shared form field properties (#95) Add opt-in shared properties, partial appearance patches and atomic CLI/MCP batches. Complete current-page, rectangle and Fields-panel selection, with 20 synthetic regressions for preservation, rollback and dialog lifecycle. Co-Authored-By: Codex <noreply@openai.com> * Bulk field properties: translated messages, a catalog check, and in-place /DA patches - The bulk dialog's flag label, errors and notices are in all 13 full catalogs; the missing-field error is a template (fmt + tl!) instead of format!. New test bulk_field_properties_labels_are_in_every_full_catalog. - patch_da rewrites the last Tf and fill colour in place (appending only when there is none), so /DA doesn't grow per edit; sizes clamp to 300 like parse_da. - A size- or font-only edit leaves widgets untouched; an indirect /MK or /BS is updated where it lives instead of inlined into each widget. - Draft::new says why: a widgetless field gets its own message, not 'could not be found'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: pin the session clock in the bulk-fields harness (save_bytes stamps /ModDate) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Tevinhead <173904641+Tevinhead@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a2f0fb7363 |
Forms: fields listed only as page widgets join the form (#649)
A PDF whose AcroForm /Fields array is empty or missing listed no fields, so none were fillable even though every widget sat on a page with its name and value: Acrobat and the browsers fill those. The page widgets are now adopted as fields — after the tree-listed ones, through each widget's topmost unlisted /Parent so a field split across several widgets stays one — and the leniency is recorded, never silent. - forms: fields() walks /Fields as before, then adopts the unlisted page widgets; adopted_page_fields() counts them for the note. - render: the inspector's field list adopts them the same way, so the Fields panel, the field-count notice and menu gating follow. - engine: opening such a file notes "the form's /Fields list omits N fields; they were read from the page annotations" in the repair log (Document Properties ▸ Advanced). Co-authored-by: GLM-5.3 <noreply@z.ai> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
b8b6fb2f45 |
Never crash & persist: Make Current Properties Default and page-display choices survive a restart (#340, #266) (#365)
* Never crash & persist: Make Current Properties Default and page display choices survive a restart #340: - Expose per-tool comment styles via CommentPrefs::styles(). - persist() writes a `comment_styles` array (tool, color, opacity, width, fill). - restore() reads each entry through the clamping setters; a new rgb_of helper at the read boundary refuses malformed colors whole (string, wrong length, null, non-finite) and clamps finite out-of-range values into 0..=1, so a hostile settings file can't smuggle a NaN or saturated color. #266: - The page display chosen in a document (view.layout.*, view.fit_one_page, view.fit_width_scrolling) now mirrors into view_defaults, which the existing default-layout/default-zoom keys already persist. New documents and the next launch open in the persisted display; transient zooms do not rewrite the default (matches Acrobat). Tests: - make_current_properties_default: persist + fresh app reuses the style. - the_page_display_you_choose_becomes_the_default: choice mirrors, persists, new document opens in the persisted display; Acrobat fit modes persist too. - comment_style_settings_clamp_and_refuse_hostile_values: hostile JSON is clamped or refused. cargo fmt --check and cargo clippy -p pdfcraft-ui-egui --all-targets are clean. * Fix clippy: chunks_exact(4) -> as_chunks::<4>() in signature_image.rs * 365: keep the comment-style persistence (#340) only; a saved no-fill clears a default fill The #266 half (every View choice rewriting the default page display) is dropped: Preferences already sets that default explicitly. rgb_triple moves out of the module declarations with one doc comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fill & Sign dates: Italian names, since Italian is now an interface language (#494) date_languages_are_the_interface_languages requires every interface language to have date names; the unknown-language example in the session test moves from it to xx. --------- Co-authored-by: Mawfyy <mawfyy@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
94f9ce69c6 |
i18n: add Italian (it) interface translation (#494)
* i18n: add Italian (it) interface translation - Complete it catalog (about 2,000 entries); it and it_* locales use it - Fonts test covers the Italian catalog; ▸ written as > like fr/es/ru, since Inter has no ▸ - Docs, attribution and ROADMAP Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * it: add the labels main gained since this catalog (83), drop stale OCR keys, and coverage tests --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
c110c579a2 |
M4: delete pages from the Pages panel (#607)
Right-clicking a thumbnail offers Delete pages, and Delete or Backspace with the pointer over the panel deletes too, as in the organize grid: the selection, or the current page. A document keeps at least one page, so with every page picked the item is off and the keys do nothing. The selection goes and the current page moves to the page after the deleted ones; one undoable step. Parity: view.thumbnail-context-ops notes and evidence include Delete pages. Co-authored-by: William Max & Co. <info@williammax.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
caeec49f5e |
M3: keep late startup URLs from changing the active document (#583)
Co-authored-by: Tevinhead <173904641+Tevinhead@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
56f85f76df |
M9: more signature validation algorithms (SHA-3, RIPEMD-160, Ed25519, P-521, brainpool, PSS, rsa_sha1) (#386)
* M9: more signature validation algorithms Validation now reads what real signers write, and says when it is weak: - Digests SHA-224, SHA-512/224, SHA-512/256, SHA3-224/256/384/512 and RIPEMD-160, beside SHA-1 and SHA-2. - RSA PKCS#1 v1.5 for every digest, in `rsa_pad`: the one expected encoding is built and compared byte for byte, with or without the DigestInfo's NULL (RFC 8017 App. B.1). A signature over the bare digest, which doesn't say which hash it uses, is refused. The signature value must be below the modulus (RFC 8017 §5.2.2); s and s + n both verified before. - RSA-PSS with the hash, MGF1 hash and salt length the signature declares. - ECDSA as DER, non-minimal DER or raw r||s on P-256, P-384, P-521 and brainpoolP256r1/P384r1/P512r1 (BSI "plain" ECDSA OIDs too); brainpoolP512r1 is hand-written on crypto-bigint because no RustCrypto crate has it. - Ed25519 (RFC 8419), and the legacy adbe.x509.rsa_sha1 format. - An algorithm PdfCraft can't check is Unknown, never Invalid. - SHA-1 and RIPEMD-160 signatures validate, but the signature's details say the algorithm is weak (wording pinned by tests). - Encoding irregularities that were tolerated are listed in the details (`verify_noting`; `verify` keeps its signature). Found while testing, each with a regression test that failed before the fix: a brainpoolP512r1 public key with a coordinate >= p panicked inside crypto-bigint's `sub_mod` (the certificate comes from the PDF); it is refused now, and the point at infinity is an invalid signature, not an error. Vectors were made with OpenSSL (tests/data/README.md has the commands). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Update comment to clarify MAC digest restrictions Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * M9: a legacy x509.rsa_sha1 signature whose key the verifier rejects is Invalid; only unsupported keys are "can't check yet" Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
c957288d3b |
M9: read BER signatures (DocuSign, Documenso); classify later changes narrowly (#144)
* M9: read BER signatures; later changes are classified narrowly
Real-world signatures (DocuSign, Telekom QES, Documenso) are BER: indefinite
lengths and segmented OCTET STRINGs, which the strict DER reader turned away
("DER signature wrong"). Behind that, later revisions that add DSS data or
rewrite the XMP were classified as tampering.
- cms: the CMS structure is read with `Tlv::parse_ber` (the reader #453 added
for PKCS#12); certificates and signed attributes are still verified over
their own exact bytes, signed attributes with an indefinite length are
re-encoded as DER first (RFC 5652 §5.4), and the details say the signature
was BER. High tag numbers are not read.
- pdf: validation data added after signing is a permitted change, but only
objects reached through /DSS /Certs|CRLs|OCSPs|VRI by the keys the standard
defines, with the shape of their role, and new or only grown since signing.
A /DSS entry naming other objects (a page's contents) excuses nothing.
- pdf: only the signed catalog's /Metadata is the document's XMP. A
/Type /Metadata or /Type /DSS label on any other dictionary or stream, even
one it already had when signed (a page's Form XObject), makes nothing
permitted. Negative tests at every DocMDP level.
Six of the new tests fail on the previous code: the BER signature, nested
segments, the store-growth rules, and a dictionary or stream carrying a
/Type /Metadata or /Type /DSS label. The last one let a drawn Form XObject be
rewritten under a Valid signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* M9: comparing a grown document security store is linear for references and bounded otherwise
only_grew compared arrays element by element, so a crafted /Certs of ~100k entries could hang
validation. Reference arrays are compared as sets; other arrays only up to 10,000 elements.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
d90b3f897f |
Refs #103: ship the OCR models in the desktop release packages (#630)
Release builds said "Text recognition isn't installed: its models are missing" because no packaging script or release job fetched the OCR models: only `cargo xtask models` does, into the source tree. - packaging/env.sh gains stage_models DEST, which runs `cargo xtask models DEST` (every ATTRIBUTION.toml kind = "model" file, SHA-256 verified, with its licence text). No model file is named, so the models PRs #433/#572 add ship without packaging changes. - `cargo xtask models` also writes ATTRIBUTION.txt crediting each model (CC-BY-SA-4.0 requires attribution when redistributed). - macOS: PdfCraft.app/Contents/Resources/models, before signing. - Windows: models\ beside pdfcraft.exe in the MSI (WiX v5 <Files> harvest) and the portable zip; test-msi.ps1 checks the folder. - Linux deb/rpm/tar.gz/AppImage, Flatpak bundle and FreeBSD tarball: share/pdfcraft/models beside bin/. Models::search_dirs now also looks in <exe dir>/../share/pdfcraft/models (Models::dirs_beside_exe); a test builds every package layout on disk and finds the models in each. release.yml and freebsd.yml check the packages contain them. - The missing-models messages no longer tell end users to run cargo, and the 11 catalogs that keyed an old PRINTCRAFT_MODELS wording (or the previous one) now key the current English text; ATTRIBUTION hashes updated. A test checks the translations match. The web (wasm) build, which the issue reports, still has no models; this is the desktop half only. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
72d640f9f0 |
Fix #155: edit text across content stream pieces (#628)
A page's /Contents array is one content stream in pieces that writers may split between any two tokens (ISO 32000-2 §7.8.2). Edit text parsed each piece on its own, so an operator whose operands ended one piece and whose keyword started the next arrived with no operands: a `[(After) -20 (wards)]` / `TJ` split made the line vanish from text_lines, text_paragraphs and text_edit, and from the Edit text tool. The joined-parse logic the redaction interpreter used (#153) moves into pdfcraft-content as `Pieces`: join the pieces, parse them as one stream, map positions back to pieces, and splice joined byte ranges back into per-piece bytes (an edit replacing or removing an operator that spans pieces rewrites every piece it touches; untouched pieces keep their objects). text_lines now interprets the joined operators, and replace_line and rewrite_block splice through `Pieces`. Redaction uses the same helper. Lines and paragraphs are grouped by the piece their first operator starts in, as before, so pages whose pieces are separate drawings don't merge. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
efb3439cb9 |
Parity: cite the MCP resources test by its merged name (#608)
#451 renamed conventions_resources_and_modern_results to conventions_resources before merging, but the misc.mcp-resources evidence kept the old name, so `cargo xtask parity` failed on main. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
+2 |
db1b3d065d |
MCP: common command tools, session resources and client hints (#451)
* XFA: choice lists, signature, password, image and barcode fields; PNG and GIF pictures The xfa crate lays out the field kinds that were left blank or shown as text fields: - Choice lists become combo boxes (editable with textEntry) or list boxes (open="always"; multi-select with open="multiSelect"), with the shown items and the saved values of <items save="1"> as /Opt pairs, /V, /I and /DV as saved values, several on separate lines in the data. Field::saved_value maps shown text to saved values for the layout and the script tree alike, so scripts read what is saved. - Password fields are password text fields; signature fields are /Sig fields the app can sign. - Image fields show the picture the data (base64) or the template holds; barcode fields show their value as text, with a warning that no bars are drawn. - PNG and GIF pictures (fields and draws) are decoded to Flate image XObjects with a soft mask for transparency; JPEGs stay as they were. Sizes come from the headers, so layout never decodes; decoding is capped at 40 million pixels and failures are warnings, not errors. Engine: multi-select list values from scripts and data are one per line. README and parity notes updated (form.xfa-dynamic). Hardened after an adversarial review pass: picture sizes refused from the header past 20 million pixels or 65,535 a side, the GIF decoder given limits (a frame far larger than its screen is refused before allocation), template pictures read whole up to 32 MB, pictures decoded once and remembered in the catalog by content hash so re-layouts reuse them, data pictures that aren't images reported by field; choice values matched against saved values first, several values deduplicated and capped, /I ascending and unique, /DV through the same mapping, <value> children read, the item cap reported; the reopen path takes another viewer's choice values (several per line); CMYK JPEGs inverted only with Adobe APP14. * D5: README for cos and engine, and an honest cos status line Thirteen crates have no README, although the session protocol tells every session to read the README of the crate it is touching. These are the two biggest: cos is where every hostile file lands first, and engine is the whole API surface a frontend gets. Each states its layer and the rules that bind it, sketches the API against the real signatures, and ends with what is deliberately not done. The cos README spells out the clone-is-a-snapshot property and why both writers return bytes rather than taking a sink; the engine README spells out the four-step editing model that makes what you see what Save writes. Also corrects the module doc on cos/src/lib.rs, which still said encryption and object-stream output for full saves were not done. Both shipped: security.rs handles R2-R6 with crypt filters and /EFF, and SaveOptions::object_streams defaults to true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Preserve alternate appearances and indirect push-button values Keep rollover, down and custom appearance entries when normal appearances are regenerated, without modifying shared dictionaries. Resolve indirect button caption and layout values. Cover full and incremental saved output, and wait for raster readiness in the existing Linux scroll test. Co-authored-by: Codex <codex@openai.com> * M13: common MCP tools through the confined automation path Preserve task tools and compact mode, add complete hints and strict argument-key errors, and catch escaped tool panics. Co-Authored-By: OpenAI Codex <noreply@openai.com> * M13: session resources and version-aware MCP result hints Keep per-document resources and legacy response shapes; add document/command resources and private cache hints for modern clients. Co-Authored-By: OpenAI Codex <noreply@openai.com> * M7: select and edit grouped Form XObject figures Co-Authored-By: Codex <noreply@openai.com> * M14: let neutral Windows locales fall through to display language Co-authored-by: Codex <codex@openai.com> * pdfcraft-cli --help lists `mcp` only when the build has it Follow-up to #540: a build without the `mcp` feature (`--no-default-features`) still listed `pdfcraft-cli mcp` in its usage, and `pdfcraft-cli mcp` then said "unknown command". USAGE is now put together with `concat!`, and its `mcp` lines come from a macro that is empty without the feature. In such a build `pdfcraft-cli mcp` says the MCP server was left out instead of calling it an unknown command. - tests/help.rs: usage_lists_mcp_only_when_built_with_it (runs in both feature sets) and mcp_without_the_feature_says_it_was_left_out (without the feature) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * engine README: the execution plan is local-only, so don't link a path that doesn't exist * Merge land/450 into land/414; XFA pictures are found again by a SHA-256 on their XObject The image cache was a /PCXfaImages map in the catalog, keyed by DefaultHasher (not stable across Rust releases) and read back from the file. Each image XObject now records the SHA-256 of its picture's bytes, and a re-layout finds the XObjects on the pages it replaces by that hash; nothing goes in the catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Merge land/414 into land/451; keep main's MCP protocol versions, cap command_batch at 1000 steps The 2026-07-28 revision and its resultType/ttlMs/cacheScope fields are dropped: main doesn't advertise them and they couldn't be checked against the spec. command_batch refuses more than 1000 steps. docs/mcp.md says that unknown argument keys are now refused with -32602 (a behaviour change for clients that sent extra keys). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Yuvraj Singh <singh.yuvraj31@icloud.com> Co-authored-by: Tanish Manku <tmanku.dev@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com> Co-authored-by: Codex <codex@openai.com> Co-authored-by: storybored379 <storybored379@users.noreply.github.com> Co-authored-by: OpenAI Codex <noreply@openai.com> Co-authored-by: Flowershangfromthebranches <152056395+Flowershangfromthebranches@users.noreply.github.com> Co-authored-by: Streetblock <github@streetblock.de> Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
1ba8b194b2 |
XFA: choice lists, signature, password, image and barcode fields; PNG and GIF pictures (#414)
* XFA: choice lists, signature, password, image and barcode fields; PNG and GIF pictures The xfa crate lays out the field kinds that were left blank or shown as text fields: - Choice lists become combo boxes (editable with textEntry) or list boxes (open="always"; multi-select with open="multiSelect"), with the shown items and the saved values of <items save="1"> as /Opt pairs, /V, /I and /DV as saved values, several on separate lines in the data. Field::saved_value maps shown text to saved values for the layout and the script tree alike, so scripts read what is saved. - Password fields are password text fields; signature fields are /Sig fields the app can sign. - Image fields show the picture the data (base64) or the template holds; barcode fields show their value as text, with a warning that no bars are drawn. - PNG and GIF pictures (fields and draws) are decoded to Flate image XObjects with a soft mask for transparency; JPEGs stay as they were. Sizes come from the headers, so layout never decodes; decoding is capped at 40 million pixels and failures are warnings, not errors. Engine: multi-select list values from scripts and data are one per line. README and parity notes updated (form.xfa-dynamic). Hardened after an adversarial review pass: picture sizes refused from the header past 20 million pixels or 65,535 a side, the GIF decoder given limits (a frame far larger than its screen is refused before allocation), template pictures read whole up to 32 MB, pictures decoded once and remembered in the catalog by content hash so re-layouts reuse them, data pictures that aren't images reported by field; choice values matched against saved values first, several values deduplicated and capped, /I ascending and unique, /DV through the same mapping, <value> children read, the item cap reported; the reopen path takes another viewer's choice values (several per line); CMYK JPEGs inverted only with Adobe APP14. * D5: README for cos and engine, and an honest cos status line Thirteen crates have no README, although the session protocol tells every session to read the README of the crate it is touching. These are the two biggest: cos is where every hostile file lands first, and engine is the whole API surface a frontend gets. Each states its layer and the rules that bind it, sketches the API against the real signatures, and ends with what is deliberately not done. The cos README spells out the clone-is-a-snapshot property and why both writers return bytes rather than taking a sink; the engine README spells out the four-step editing model that makes what you see what Save writes. Also corrects the module doc on cos/src/lib.rs, which still said encryption and object-stream output for full saves were not done. Both shipped: security.rs handles R2-R6 with crypt filters and /EFF, and SaveOptions::object_streams defaults to true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Preserve alternate appearances and indirect push-button values Keep rollover, down and custom appearance entries when normal appearances are regenerated, without modifying shared dictionaries. Resolve indirect button caption and layout values. Cover full and incremental saved output, and wait for raster readiness in the existing Linux scroll test. Co-authored-by: Codex <codex@openai.com> * M7: select and edit grouped Form XObject figures Co-Authored-By: Codex <noreply@openai.com> * M14: let neutral Windows locales fall through to display language Co-authored-by: Codex <codex@openai.com> * pdfcraft-cli --help lists `mcp` only when the build has it Follow-up to #540: a build without the `mcp` feature (`--no-default-features`) still listed `pdfcraft-cli mcp` in its usage, and `pdfcraft-cli mcp` then said "unknown command". USAGE is now put together with `concat!`, and its `mcp` lines come from a macro that is empty without the feature. In such a build `pdfcraft-cli mcp` says the MCP server was left out instead of calling it an unknown command. - tests/help.rs: usage_lists_mcp_only_when_built_with_it (runs in both feature sets) and mcp_without_the_feature_says_it_was_left_out (without the feature) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * engine README: the execution plan is local-only, so don't link a path that doesn't exist * Merge land/450 into land/414; XFA pictures are found again by a SHA-256 on their XObject The image cache was a /PCXfaImages map in the catalog, keyed by DefaultHasher (not stable across Rust releases) and read back from the file. Each image XObject now records the SHA-256 of its picture's bytes, and a re-layout finds the XObjects on the pages it replaces by that hash; nothing goes in the catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Tanish Manku <tmanku.dev@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com> Co-authored-by: Codex <codex@openai.com> Co-authored-by: Flowershangfromthebranches <152056395+Flowershangfromthebranches@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Streetblock <github@streetblock.de> Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
37076662a7 |
M7: select and edit grouped PDF figures (Form XObjects) (#450)
* D5: README for cos and engine, and an honest cos status line Thirteen crates have no README, although the session protocol tells every session to read the README of the crate it is touching. These are the two biggest: cos is where every hostile file lands first, and engine is the whole API surface a frontend gets. Each states its layer and the rules that bind it, sketches the API against the real signatures, and ends with what is deliberately not done. The cos README spells out the clone-is-a-snapshot property and why both writers return bytes rather than taking a sink; the engine README spells out the four-step editing model that makes what you see what Save writes. Also corrects the module doc on cos/src/lib.rs, which still said encryption and object-stream output for full saves were not done. Both shipped: security.rs handles R2-R6 with crypt filters and /EFF, and SaveOptions::object_streams defaults to true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Preserve alternate appearances and indirect push-button values Keep rollover, down and custom appearance entries when normal appearances are regenerated, without modifying shared dictionaries. Resolve indirect button caption and layout values. Cover full and incremental saved output, and wait for raster readiness in the existing Linux scroll test. Co-authored-by: Codex <codex@openai.com> * M7: select and edit grouped Form XObject figures Co-Authored-By: Codex <noreply@openai.com> * M14: let neutral Windows locales fall through to display language Co-authored-by: Codex <codex@openai.com> * pdfcraft-cli --help lists `mcp` only when the build has it Follow-up to #540: a build without the `mcp` feature (`--no-default-features`) still listed `pdfcraft-cli mcp` in its usage, and `pdfcraft-cli mcp` then said "unknown command". USAGE is now put together with `concat!`, and its `mcp` lines come from a macro that is empty without the feature. In such a build `pdfcraft-cli mcp` says the MCP server was left out instead of calling it an unknown command. - tests/help.rs: usage_lists_mcp_only_when_built_with_it (runs in both feature sets) and mcp_without_the_feature_says_it_was_left_out (without the feature) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * engine README: the execution plan is local-only, so don't link a path that doesn't exist --------- Co-authored-by: Tanish Manku <tmanku.dev@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com> Co-authored-by: Codex <codex@openai.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Streetblock <github@streetblock.de> Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
025b79b515 |
M14: honor Windows display language with neutral launcher locales (#567)
* M14: let neutral Windows locales fall through to display language Co-authored-by: Codex <codex@openai.com> * pdfcraft-cli --help lists `mcp` only when the build has it Follow-up to #540: a build without the `mcp` feature (`--no-default-features`) still listed `pdfcraft-cli mcp` in its usage, and `pdfcraft-cli mcp` then said "unknown command". USAGE is now put together with `concat!`, and its `mcp` lines come from a macro that is empty without the feature. In such a build `pdfcraft-cli mcp` says the MCP server was left out instead of calling it an unknown command. - tests/help.rs: usage_lists_mcp_only_when_built_with_it (runs in both feature sets) and mcp_without_the_feature_says_it_was_left_out (without the feature) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Codex <codex@openai.com> Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
75b31fb04d |
M10: Print ▸ Properties… opens the printer driver's own settings (#482)
* M5.7: date format and date language for Fill & Sign in the engine * M5: flatten Fill & Sign marks when saving Saving can now bake typed text, marks and signatures into the page, from Preferences or doc_save. The option defaults off, so a normal save still leaves them editable, and other comments and form fields stay. * M4: zoom the Organize page grid The page grid draws its pages at 50% to 300%: toolbar buttons with a percentage that resets to 100%, trackpad pinch or Ctrl/⌘ + wheel about the page under the pointer, and ⌘+ / ⌘− / ⌘0 (which no longer change the hidden page view's zoom while the grid shows). Thumbnails are re-rendered sharper in a few steps as the grid grows, capped so one document's thumbnails stay within 256 MB. The page under the pointer (or the first one in view) stays where it is across a zoom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: zoomed grid pages are rendered at the size drawn A zoomed page grid stretched 132 pt thumbnails, and the stepped detail that was meant to help was capped by a memory budget, so a long document stayed blurry at any zoom. Pages drawn larger than their thumbnails now get a render at exactly the size drawn while they are in view; the thumbnail stands in until it arrives. Only pages in view keep one, so memory no longer depends on the page count, and thumbnails go back to their one fixed size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M5.7: fill_sign_date_format tool; format and language on fill_sign_add * M5.7: Preferences ▸ Date format for Fill & Sign dates (refs #158) * M5.7: refuse Fill & Sign dates the PDF text font can't hold * M5.7: Spanish labels for Preferences ▸ Date format * M10: Print ▸ Properties… opens the printer driver's own settings CUPS: the queue's PPD job options (with lpoptions defaults), grouped in the Print dialog and sent with this job as lp -o key=value; installable options and what the dialog sets itself are left out. Windows: the driver's Printing Preferences window through printui (no unsafe FFI), saved as the printer's per-user defaults. Headless: printer_options tool and doc_print options. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * View: the middle button pans instead of drawing with markup tools egui's drag responses accept every pointer button, so on Windows and macOS a middle-button drag reached the page tools: with Draw (ink) selected it drew a stroke, and other tools cropped, selected or moved. Linux was unaffected because its middle-click auto-scroll already blocks page input. Outside Linux a middle-button drag that starts in the viewport (document or Organize Pages) now pans 1:1 while the button is held, with a Grabbing cursor, and page input is blocked from the press through the release. Linux keeps its latched auto-scroll. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M3: a short page gone to from the Pages panel stays current The current page was whichever page showed most of itself. Going to a short page (a cheque) above a tall one scrolls it to the top, where the tall page below shows more, so the current page and the Pages panel's highlight moved to the tall page instead of the one clicked. The page navigated to now stays current while it is wholly on screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: page operations from the Pages panel: context menu and drag to reorder Right-clicking a thumbnail offers Extract pages, Cut, Copy and Paste after, on the selection or on the page clicked (which becomes the selection), as the organize grid does. Dragging thumbnails moves the selection, or the page grabbed, to the gap it is dropped on: a bar marks the gap, the page count follows the pointer, and the panel scrolls near its edges. The move is one undoable step; the moved pages stay selected and the current page follows its page. Documents that forbid assembly keep click-only thumbnails. Parity: view.thumbnail-context-ops is shipped, with what it still lacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pages panel: only the primary button drags thumbnails; a drag left over after the panel closed or the document changed is dropped * Fill & Sign dates: German, Bulgarian, Hungarian and Ukrainian month and weekday names, matching the interface languages * uk: keep the author's wording for the flatten setting --------- Co-authored-by: KissT <github@kisst.net> Co-authored-by: Mark Bartalos <bartalos86@gmail.com> Co-authored-by: samehelhawary <basio20044@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cody Vogt <Cody.Vogt@caltechsurveys.com> Co-authored-by: William Max & Co. <info@williammax.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
d592dbb9c4 |
Fill & Sign: date format and date language in Preferences (refs #158) (#505)
* M5.7: date format and date language for Fill & Sign in the engine * M5: flatten Fill & Sign marks when saving Saving can now bake typed text, marks and signatures into the page, from Preferences or doc_save. The option defaults off, so a normal save still leaves them editable, and other comments and form fields stay. * M4: zoom the Organize page grid The page grid draws its pages at 50% to 300%: toolbar buttons with a percentage that resets to 100%, trackpad pinch or Ctrl/⌘ + wheel about the page under the pointer, and ⌘+ / ⌘− / ⌘0 (which no longer change the hidden page view's zoom while the grid shows). Thumbnails are re-rendered sharper in a few steps as the grid grows, capped so one document's thumbnails stay within 256 MB. The page under the pointer (or the first one in view) stays where it is across a zoom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: zoomed grid pages are rendered at the size drawn A zoomed page grid stretched 132 pt thumbnails, and the stepped detail that was meant to help was capped by a memory budget, so a long document stayed blurry at any zoom. Pages drawn larger than their thumbnails now get a render at exactly the size drawn while they are in view; the thumbnail stands in until it arrives. Only pages in view keep one, so memory no longer depends on the page count, and thumbnails go back to their one fixed size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M5.7: fill_sign_date_format tool; format and language on fill_sign_add * M5.7: Preferences ▸ Date format for Fill & Sign dates (refs #158) * M5.7: refuse Fill & Sign dates the PDF text font can't hold * M5.7: Spanish labels for Preferences ▸ Date format * View: the middle button pans instead of drawing with markup tools egui's drag responses accept every pointer button, so on Windows and macOS a middle-button drag reached the page tools: with Draw (ink) selected it drew a stroke, and other tools cropped, selected or moved. Linux was unaffected because its middle-click auto-scroll already blocks page input. Outside Linux a middle-button drag that starts in the viewport (document or Organize Pages) now pans 1:1 while the button is held, with a Grabbing cursor, and page input is blocked from the press through the release. Linux keeps its latched auto-scroll. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M3: a short page gone to from the Pages panel stays current The current page was whichever page showed most of itself. Going to a short page (a cheque) above a tall one scrolls it to the top, where the tall page below shows more, so the current page and the Pages panel's highlight moved to the tall page instead of the one clicked. The page navigated to now stays current while it is wholly on screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: page operations from the Pages panel: context menu and drag to reorder Right-clicking a thumbnail offers Extract pages, Cut, Copy and Paste after, on the selection or on the page clicked (which becomes the selection), as the organize grid does. Dragging thumbnails moves the selection, or the page grabbed, to the gap it is dropped on: a bar marks the gap, the page count follows the pointer, and the panel scrolls near its edges. The move is one undoable step; the moved pages stay selected and the current page follows its page. Documents that forbid assembly keep click-only thumbnails. Parity: view.thumbnail-context-ops is shipped, with what it still lacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pages panel: only the primary button drags thumbnails; a drag left over after the panel closed or the document changed is dropped * Fill & Sign dates: German, Bulgarian, Hungarian and Ukrainian month and weekday names, matching the interface languages --------- Co-authored-by: Mark Bartalos <bartalos86@gmail.com> Co-authored-by: samehelhawary <basio20044@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cody Vogt <Cody.Vogt@caltechsurveys.com> Co-authored-by: William Max & Co. <info@williammax.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |