## What this changes
Adds `Permissions::extract_pages()`: `assemble()` **and** copying (bit 5), as #5081 asks, since extracted pages carry their text and graphics into a file without the source's protection. It is used for Extract, Split, Split by size and every Combine/Insert/Replace/paste source check (`sources::source_document`). Changes within the document (rotate, delete, move, insert) keep `assemble()`.
Since this PR was opened, #6309 made `assemble()` revision-aware on `main`: bit 11 alone at revision 3 and later, bit 4 at revision 2. That already refuses "Any except extracting pages" at revision 3+. This PR builds on it:
- At revision 2, `assemble()` reads bit 4, which "Any except extracting pages" sets, so such a file could still be extracted and split into unprotected copies. Requiring copying closes that.
- New `Needs::PageExtraction` greys out Extract and Split up front. It reuses the existing security-refusal string, so no catalogue changes.
- `Document::allows_page_extraction()`, and an `extract_pages` key in the automation security JSON.
- `page_extract` with `separate: true` extracts and writes one page at a time (`extract_separate`), so it no longer buffers every page. The first page is extracted before `out_dir` is created, so a refused extraction leaves no empty directory. If a later page fails, the files this call wrote are removed, and `out_dir` too if this call created it (review on this PR).
- Insert Pages checks a PDF through the same path as the insert itself: new `Session::insert_source(dest, name, bytes)`, which the UI's insert now calls (review on this PR).
- The `organize.combine` parity notes now say "page extraction".
Fixes#5081
## How you verified it
`cargo fmt --all` is clean. Regression tests (adopted from Harrison-Booth's review patch, plus CodeRabbit's follow-ups):
- `crates/crypt/src/tests.rs::page_extraction_needs_assembly_and_copying`: unit test for `extract_pages()`.
- `crates/engine/src/tests.rs::extraction_permissions_cover_revision_three_to_six_and_leave_no_partial_output`: AnyExceptExtract on RC4-128, AES-128 and AES-256 refuses Extract, Split, Split by size and Combine/Insert/Replace sources without the permissions password, leaves source and destination untouched, and the owner can do everything. The authorized combine output has 2 pages, and a differently encrypted or unprotected destination is refused.
- `crates/engine/src/tests.rs::page_extraction_needs_the_copy_permission_on_every_revision`: a file that withholds only copying (R2 RC4-40 and AES-256) refuses extraction; the owner password lifts it.
- `crates/engine/src/tests.rs::pages_under_the_same_protection_are_inserted_without_page_extraction`: a separately built file under matching protection is inserted, and the result stays restricted.
- `crates/automation/tests/it/automation.rs::refused_extraction_writes_no_split_or_page_output`, and the `extract_pages` checks in `protecting_through_tools`, which now cover three presets, including a user (not the owner) for whom extraction is allowed.
**Only the crypt unit test has been run locally, and it passes.** The engine and automation tests timed out while compiling on this machine and have not been run, nor seen failing without the fix. The one `pdfcraft-ui-egui` call-site change has not been compiled. CI is the first place these build.
- [x] `cargo fmt --check`
- [ ] clippy with `-D warnings` on the crates I changed (not re-run after the rebase)
- [ ] tests pass on the crates I changed (crypt only)
## Anything a reviewer should look at first
- **Rebased onto #6309.** The tests no longer expect "Any except extracting pages" to allow assembly at revision 3+. The same-protection paste tests now use the "Inserting, deleting and rotating pages" preset without copying.
- **Copy (bit 5) is now required**, as the issue suggests. `crates/ui-egui/tests/it/editing.rs::protected_files_that_can_be_combined_are_flagged` used a copy-withheld file as "protected but combinable"; it now uses a print-withheld file, since a no-copy file is no longer a Combine source without the permissions password.
- **Same-document paste**: Paste re-opens the document's own bytes as an Insert source, which the new check would refuse. `sources::open_source_into` accepts a source that withholds extraction but allows assembly only when the destination will be saved under exactly the same protection (file key and `/Encrypt` values), so cut/copy and paste within one protected document still works. Pasting into an unprotected or differently protected document is refused. One limit: after changing security in the same session without saving, a cut can't be pasted back until you save.
- **Not in this PR:** CodeRabbit's `disabled_reason` suggestion for read-only documents predates this change and covers other commands too, so it belongs in a separate UI change.
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
<!-- coderabbit-ai-generated:v1 -->
## Summary by CodeRabbit
- **New Features**
- Page extraction and splitting now follow a dedicated permission check, distinct from page assembly.
- Pages can be inserted from certain protected PDFs when their protection settings match the destination document.
- **Bug Fixes**
- Separate-page extraction now cleans up files created during a failed operation and avoids creating an output directory when the first page cannot be extracted.
- Security details now report whether page extraction is permitted.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
* AcroForm: integrate persistent timesheet script workflows
* AcroForm: address review caching, worker stacks and live metadata
* M6: address second review of AcroForm script workflows
* M6: handle top-level ASI boundaries and verify cyclic calculation results
* M6: bound script workers and isolate pending completions across edits
* M6: make form resets visible before subsequent script reads
* M6: include calculated dependents in reset comparison baselines
* M6: bound reset writeback work before native completion
* M6: charge expanded reset selections and field name bytes
* M6: bound string conversion bytes and charge reset widgets
* M6: preserve unread defaults and bound prompt text conversion
* M6: bound reset calls early and preserve calculated field identity
* AcroForm: address grouped calculations, field identity and widget ownership
* AcroForm: retire field instances and address native review edge cases
* AcroForm: route comparison Save As and Print to the source document
* M6: dispatch text widget mouse-up scripts
* M6: honor fit bounds for form button icons
* AcroForm: bound calculation work and preserve reset and save integrity
* Harden AcroForm script requests and worker recovery
* Keep form callbacks and focus consistent across edits
* AcroForm: preserve reset ordering and bound edit callbacks
* Fix reset appearance ordering and XFA focus after row removal
* Keep XFA focus indexes and stopped reset appearances consistent
* Document form script execution and reset helpers
* Keep script workers bounded and reject partial callback edits
* Keep form events available after a document script reaches VM limits
* Keep main's first-value Simple calculation and restore the calculateNow helper test
AFSimple_Calculate (JavaScript) and the native Simple calculation count each field's first
value again, as #5304 landed them; summing every selection of a multiple-selection list is
a separate behaviour change. Restores the document-helper calculateNow test and the
AFSimple_Calculate doc comment, and adds native and JavaScript multiple-selection cases.
* Patch the callback field snapshot in place instead of rebuilding it
Each field callback rebuilt all field states (3,129 on the benchmark form), and the
retained realm held the previous snapshot, so stamping instance tokens copied it again.
The runner now keeps its last snapshot and patches only the values and flags that
changed; ScriptSession::stamp_fields stamps it before dispatch, the worker copies only
when a token changes, and the realm drops the job's snapshot when the job ends (field
identity is checked against the cached keys instead).
* An oversized job script is that script's error, not a refused edit
Realm::run refused a script too long or too deep to run as an execution refusal, which
made the engine reject the whole edit (setting a 300 KiB calculation script failed). As on
main it is now a plain script error that keeps the value; the realm is not touched.
* Read a leading #! line as a comment in the chain scan; follow main's refusal and conditional limits in tests
The nesting guard from #5079 now checks brackets, prefixes and conditionals; the document
worker's preflight still bounds arrow-function and right-associative chains and skips a
leading #! line as the guard does. A refused script is an error, not a refused edit, so
the preflight test now checks the edit goes through with the error reported.
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* Print: flatten transparent sheets for GDI (v3) printer drivers
Windows sends a job for a GDI (v3) driver through its XPS-to-GDI
conversion, which fails on soft masks (#6155), prints halos around them
(#5838) or prints transparency as black boxes (#5940). XPS (v4) drivers
print the same pages correctly.
spooler_copy takes a flatten_transparency flag. With it, after greying
and before tiling, each sheet that uses transparency is replaced by an
opaque picture of itself (spooler::flattened): rendered at 300 dpi, lower
for a sheet past 80 MP or 16000 px a side, in tiles of at most
2000 x 2000 px, each an RGB Flate image over white, so no tile reaches
Windows' 2^22 px image cap and tiling leaves them alone. The sheet keeps
its boxes and rotation; its group and annotations go. Sheets without
transparency stay vector.
pdfcraft-preflight gets page_uses_transparency, the PDF/A transparency
search without counting a transparency group on its own, which Word
puts on every page.
Both callers pass false until the GDI-driver check lands.
* Print: flatten transparent sheets only for a GDI (v3) printer driver
spool::xps_driver / spool::gdi_driver read whether the printer's driver renders XPS itself,
through craft-print's new xps_driver (storytold/craft-libs#804, winprint
storytold/winprint.rs#21). The Print dialog and doc_print pass gdi_driver to spooler_copy's
flatten_transparency, so XPS (v4) drivers, PDF printers among them, keep vector sheets.
craft-print pinned to the craft-libs#804 branch, winprint to #21's, until they merge.
* craft-print 60ede99985ef1e5df87a2aca66ed538eb23dec99: craft-libs main with #804 (xps_driver), winprint bc06a7c4 (#21)
* Cargo.lock: the two winprint entries in Cargo's order (by source, after the re-pin)
## Summary
Fixed selected-text comments that were previously shown as separate sticky notes, often covering text and making their target unclear. Comments are now attached directly to highlight annotations, with a small marker and connector beside the highlighted word. Hovering shows only the comment text, while normal highlights and other annotation types keep their existing behavior.
Fixes#3759
## What this changes
Fixes the confusing behavior when adding comments to selected text.
- Selected-text comments are stored as highlight annotations.
- Commented highlights display a small message marker and connector.
- Hovering a commented highlight shows only the comment text.
- Normal highlights and other annotation types retain their existing display.
- Cancelling a selected-text comment clears its pending selection state.
### Screenshots
### Before
<img width="1763" height="293" alt="Screenshot from 2026-10-21 04-18-37" src="https://github.com/user-attachments/assets/b88cb60a-82e9-4dd6-81ae-053b482c1e81" />
### After
<img width="2474" height="930" alt="image" src="https://github.com/user-attachments/assets/ee5c6c31-6a6a-4dd8-a3c4-baa04d9908f5" />
## How you verified it
- [x] `git diff --check`
- [x] `cargo fmt --check`
- [x] clippy with `-D warnings` on the changed crate
- [x] tests pass on the changed crate
- [x] `cargo xtask layers` and `cargo xtask assets` — not applicable
Manual verification was performed with a PDF containing selectable text:
- Added a comment to a selected word.
- Confirmed the word remains highlighted.
- Confirmed the comment marker appears near the highlighted word.
- Confirmed the popup displays the comment text.
- Confirmed ordinary highlights remain visually distinct.
Cargo tests could not be run in the agent environment.
## Never-crash
- [x] No new `unwrap`/`expect`/`panic!`/`unsafe`
- [x] No new input-derived indexing or unsafe arithmetic
- [x] No crash fix is included in this PR
## Provenance
- [x] No assets added
- [x] No fonts added
- [x] No GPL/AGPL code copied
- [x] Behavior was implemented from the existing PDF annotation model and project behavior
- [x] No new asset requires attribution
## Security
- [x] No path or automation behavior changed
- [x] No MCP or UI control-channel behavior changed
- [x] No outbound network access added
## Parity and reach
- [ ] `parity/acrobat-features.toml` updated — not applicable
- [ ] New automation tool — not applicable
- [ ] Documentation update — not required for this UI bug fix
## Anything a reviewer should look at first
The main behavior change is in the selected-text comment path: it now creates a highlighted annotation carrying the comment instead of an independent sticky note. This preserves the selected text as the comment’s visual anchor while keeping normal sticky-note placement unchanged.
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
<!-- coderabbit-ai-generated:v1 -->
## Summary by CodeRabbit
* **New Features**
* Comments on selected text are saved as highlights, preserving the text location.
* Choosing the sticky-note tool and clicking elsewhere replaces the selection-anchored composer and places a comment at the new location.
* Comments with text markup display a marker on the page, including near page edges. Hovering over markers shows the author and comment, and markers remain selectable across page rotations and zoom levels.
* **Bug Fixes**
* Markers take priority when selecting or hovering over overlapping annotations. Markers are not shown for comments without text or outside the page.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What this changes
Batch processing now keeps the caller's "Enable JavaScript" setting. Before, each file was processed in a fresh `Session::new()`, where JavaScript is on, so open-time XFA scripts and "Run a JavaScript" steps ran and their results were saved.
- `actions::run_on` and `ocr::recognize_file` take a `javascript: bool` and call `set_javascript` before opening the file. In `recognize_file` the open now goes through a small private helper, `ocr::open_file`, so the open path can be tested without the recognition models.
- `doc_batch`'s `Recipe` carries the setting into its per-file session and its OCR step.
- Callers pass their own `session.javascript()`: the desktop Action Wizard, desktop multi-file OCR, `action_run`, `ocr_recognize_files` and `doc_batch`.
Also relevant to #5054: an administrator's `DisableJavaScript` policy now reaches these sessions.
Fixes#5080
## How you verified it
Regression tests, adopted from the reviewers' patches, plus one for the OCR open path:
- `crates/engine/src/tests.rs::an_action_runs_a_javascript_step_only_when_javascript_is_on` (croquete1): a "Run a JavaScript" step writes its field with JavaScript on, and `run_on(.., false, ..)` refuses it instead of saving a scripted value. croquete1 reported it failing with the `set_javascript` call in `run_on` removed.
- `crates/automation/tests/it/automation.rs::file_processing_keeps_javascript_off_for_xfa_open_scripts` (Harrison-Booth): with `js_enabled` off, an XFA initialize script that sets `qty` to 42 doesn't reach the output of `action_run`, `doc_batch` or (only when its models are installed) `ocr_recognize_files`. The contributor reported that each propagation removed on its own makes it fail.
- `crates/engine/src/ocr.rs::tests::files_open_for_recognition_with_the_callers_javascript_preference`: opens the scripted XFA fixture through `recognize_file`'s open path with JavaScript off (the initialize script leaves `qty` empty) and on (it sets `qty` to 2). This covers the OCR propagation in runs without the OCR models.
What was run locally, and what wasn't:
- `cargo fmt --check` and `cargo clippy --all-targets -- -D warnings` were clean on `pdfcraft-engine`, `pdfcraft-automation` and `pdfcraft-ui-egui` at the earlier heads. On the latest commit only `cargo fmt --all` was run; clippy was not re-run.
- No tests were run locally, including the new `ocr::tests` one: test builds time out on this machine, and the run was stopped before it compiled. The new test has not been seen failing either. If `set_javascript` is removed, the session keeps its JavaScript-on default, so the off case would read `qty` = 2 like the on case, and the first assertion would fail. Seeing that happen is still owed.
- The tests are left to the maintainers' CI. No CI result is claimed for this PR.
- [x] `cargo fmt --check`
- [ ] clippy with `-D warnings` on the crates I changed (clean at the earlier heads; not re-run on the latest commit)
- [ ] tests pass on the crates I changed (not run locally)
- [x] `cargo xtask layers` and `cargo xtask assets`: N/A. No crate boundaries, dependencies or assets change.
## Never-crash
- [x] No new `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!` outside tests. The new non-test code only passes a `bool` and propagates errors with `?`.
- [x] No indexing or slicing with input-derived positions, no new arithmetic, allocations or recursion.
- [x] No `unsafe`
- [x] Any crash this fixes has a regression test: N/A, this fixes no crash.
## Provenance
- [x] No asset from an Adobe product, and no Adobe icon recreated, traced or imitated (no assets change)
- [x] No font files committed here
- [x] No GPL/AGPL code copied in
- [x] Behaviour comes from the project's own JavaScript preference (`Session::set_javascript`) and issue #5080, not from another implementation's source
- [x] Every new asset has an `ATTRIBUTION.toml` entry: N/A, no new assets
## Security
- [x] `--root` confinement still holds: `action_run` and `ocr_recognize_files` still resolve every input and output through `resolve`, and `doc_batch`'s path handling is unchanged. No new paths are read or written.
- [x] MCP and the UI control channel are untouched.
- [x] No new outbound network access.
- This narrows script execution: with JavaScript off, the per-file sessions of the Action Wizard, multi-file OCR, `action_run`, `ocr_recognize_files` and `doc_batch` no longer run document or step scripts.
## Parity and reach
- [ ] `parity/acrobat-features.toml` not updated: this is a bug fix, and the `misc.batch-processing` entry's status (`partial`) and notes stay true.
- [x] Reachable headlessly: `action_run`, `ocr_recognize_files` and `doc_batch` take the setting from the session, which `js_enabled` sets.
- [x] Docs: N/A. No documented behaviour changes (`docs/cli-batch.md` doesn't describe JavaScript handling).
## Anything a reviewer should look at first
- The existing `run_on` tests pass `true` to keep their behaviour; the tests above cover the JavaScript-off path.
- `recognize_file` now has 7 parameters, the `too_many_arguments` limit.
- The OCR branch of the end-to-end test runs only where the OCR models are installed. The engine unit test covers the OCR open path without them. Provisioning the models in CI is left as a separate decision.
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
<!-- coderabbit-ai-generated:v1 -->
## Summary by CodeRabbit
* **Bug Fixes**
* File actions, batch processing, and OCR now honor the JavaScript setting from your current session for each file.
* When JavaScript is disabled, document scripts—including scripts that run when a document opens—won’t execute during processing. When enabled, JavaScript steps can run as expected.
* This behavior applies across automated and in-app workflows. Per-file results and OCR output handling remain unchanged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
* Fix#5211: persist JavaScript field colours
* Read current document state in field scripts
* Redraw form fields after validation changes
* Format validation redraw regression test
* Keep field list alive in redraw test
* Refresh fields only after same-field scripts
* Format conditional field refresh
* Borrow accepted field value for storage
* Redraw calculated fields from current appearance
* Format JavaScript field state merge
* Restore JavaScript runner document state
* Group JavaScript runner form inputs
* Read current field display state in scripts
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
adding_custom_paper_overrides_pending_driver_default clicked Custom… as soon as the background
paper read started; on hosted macOS the dialog hadn't drawn its paper row by then and the test
failed (no node "Custom…"). It now waits for the button, up to 10 s, before clicking. Found by the
first nightly macOS run.
* Fix#5211: make calculateNow synchronous
* Bound and cache calculation execution
* fix(engine): invalidate field calculation cache
* perf(js): build calculation scripts lazily
* Refresh calculation cache after page insertion
* Load calculations for document script helpers
* Assert imported calculation result
* Initialize formatted field-change state
* Update formatted calculation test expectations
* Include the referenced field in calculation test
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
The menu bar folds titles that don't fit into "⋯", or all of them into one "Menu" when not even
the first fits. On hosted macOS, at MIN_WINDOW_SIZE, the bar is the Menu button (the system font is
wider), so the test's File menu item wasn't there and it failed; the main menu is still reachable.
A real macOS window uses the native menu bar.
Binds Properties dialog drafts to the vendored edit-session crate (byte-identical to PerchAnton/edit-session@033f7c3a, ledger in vendor/README) and adds replayable UI scenarios. Includes a merge of main and a test covering the clean Properties inspection-tab live refresh.
#5861 (#6100) reads the chosen printer's papers and default paper in the background, and the
default replaces the dialog's paper unless it was chosen in the dialog (paper_overridden). The
paper a printer's setup restores (#2001, #6779) didn't count as chosen, so on Windows the driver's
default replaced it each time the dialog opened: an office printer last printed on A3 came back
on A4.
apply_setup now marks the paper it restores as chosen. A test drives the dialog with a printer
remembered on A3 whose driver's default is A4.
On Windows the printer list reports whether a driver supports two-sided printing, but the job still asked for it anyway, so duplex jobs printed single-sided or misaligned on printers such as the reported Brother model.
Leave duplex unset for a printer reported as not two-sided, so that printer keeps its own setting, while printers that support two-sided or report no capability keep the chosen setting.
Test: duplex_is_not_sent_to_a_printer_without_two_sided failed before the change and passes after.
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* Print in grayscale prints on a Windows driver with no grey option (#5806)
Windows refused Print in grayscale ("the printer's driver did not retain the requested
grayscale; nothing was sent") on drivers that list no grey option the ticket can name:
Microsoft Print to PDF, and an HP Photosmart driver whose grey is its own vendor setting.
- On Windows, a grayscale job's copy for the spooler is made grey first with Convert colours
(all pages, comments and form fields, black kept black, pictures re-encoded where they can
be), and the job says so (Job::grey_pages). The driver is still asked for grey where it has
it; one without prints the grey pages instead of refusing them. A copy that can't be read
goes as it is and asks the driver strictly, as before.
- craft-print 970644b7 (storytold/craft-libs#774) adds Job::grey_pages and asks a driver that
lists only psk:Grayscale for that, not psk:Monochrome.
- Both the Print dialog and doc_print go through spooler_copy, which now takes the job.
* craft-print 1146cdc4: craft-libs main with #774 (grey pages) and #773 (the CUPS printer list waits at most 15 s, storytold/pdfcraft#6761)
* M10: a booklet's both sides follow a driver that keeps its own two-sided setting (#5772)
Booklet ▸ Both sides always asked for two-sided printing on the short
edge (#2707). A driver that doesn't take two-sided settings from other
programs (no two-sided printing, manual two-sided only in its
Properties, Microsoft Print to PDF) then made craft-print refuse the
whole job. The reporter's Epson ET-2870 offers only OneSided, and its
manual two-sided setting is what prints their normal two-sided jobs.
print::booklet_job_duplex asks such a driver for nothing on both
sides, so its own setting applies, as for ordinary printing (#2422)
and as Acrobat enables two-sided for a booklet only "if available".
Unknown support still asks for the short edge; one side on its own
stays one-sided. The Print dialog takes the chosen printer's support
from the printer list (PrintDraft::printer_two_sided, also behind its
"its own setting applies" note), and doc_print reads it only for a
booklet's both sides.
The dialog's booklet test used the machine's printers, and on Windows
the default, Microsoft Print to PDF, now gives the other answer: it
sets its own printers and checks the note too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M10: doc_print's lookup of a booklet printer's two-sided support is tested (#5772)
The booklet test fed job_duplex the printer's answer directly, so a
lookup that missed the default printer or matched the wrong one would
have gone unnoticed. The search over the printer list is now its own
function, two_sided_in, tested with a list of named, default and
unknown printers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M10: a booklet printed to "default" goes to the printer whose driver was asked (#5772)
doc_print decides a booklet's both sides from the printer's two-sided
support. For "default", the job named no printer, so the spooler picked
whichever printer was the default when the job was submitted, after the
document was rendered. The printer found in the list is now the job's
printer, by name, and the lookup test covers a one-sided default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #5903: the booklet test lists its printers through a stand-in source
It set the dialog's printer list by hand while the real list was still loading. On a machine with
no printers (CI's Linux runner) the list that arrived replaced them and reset the printer, so the
dialog the test clicked through was not the one it set up. The printers now come from the
dialog's own source, drivers unasked as #3554 lists them, with each driver's two-sided answer in
the map.
---------
Co-authored-by: austin62jake <340374338+austin62jake@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* fix(ui-egui): keep hotbar picker stationary while resizing
* fix(ui-egui): keep hotbar picker pinned during keyboard resize
Pin the Hotbar tools picker while the Toolbar size slider is adjusted with the arrow keys, until the slider loses focus. Widen the regression test to both drag directions and every layout, add a keyboard test, and document the new helpers.
* fix(ui-egui): closing the hotbar picker ends a size adjustment
close_picker() also clears picker_drag_pos and keyboard_sizing. Main's
close_hidden_picker() closes the picker on passes that don't draw the
hotbar, where show() can't clear them, and the next opening would start
at the held position. Adds closing_the_picker_ends_a_size_adjustment, a
doc comment for picker_drag_pos, and says "picker" where the comment
meant it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(print): keep the localized reset label off the properties panel title
The header of Print ▸ Properties… laid the title out first and gave what
was left of the row to Done and Reset. When the translated "Reset to
printer defaults" needs more room than that, egui keeps placing the two
buttons from the right edge of the child leftwards and paints them back
over the title: in Italian the label is 51 characters against 25 in
English, and both strings end up unreadable (#5545).
Laid out with egui::Sides::shrink_left().truncate instead: the buttons
take their room first and the title takes what is left of the row, cut
with an ellipsis. Nothing moves in English, where the title still fits.
* test(print): pin the header against every catalog and the title's tooltip
The geometry tests behind #5545 tried English, Italian and German at four
widths. They now walk every catalogued language at the row the settings column
really gives the header (470 px less the frame's 10 px margins), so the longest
translation of "Reset to printer defaults" is covered by the run instead of by a
guess at which locale is the worst one.
Two more tests: hovering the elided title opens a tooltip with the full string,
while a title that fits whole opens none; and a row too narrow to give the title
even an ellipsis leaves it out instead of painting it back over the buttons.
That last case is why driver_options_header returns Option<Response>. Bulgarian
(370 px of button), Spanish (365) and Ukrainian (361) fill the row with the two
buttons alone at 450 px: the ellipsis is 12.4 px wide and the clearance was
0.1 px, so a pixel less and the label was painted over the buttons again.
---------
Co-authored-by: Jarvis (Hermes) <jarvis@localhost>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Brings back #2381 (by regendorf-ux), which #6706 reverted before v0.20.0 because its page-setup
rows made the Print dialog as tall as Preferences and failed the dialog-height test.
- Custom… beside Paper adds a named size in the preferences' unit (1 to 200 inches a side),
kept with the settings; Remove takes it off the list. doc_print takes sizes such as 8.5x14in.
- #2381's "Remember this paper for <printer>" box and its separate printer-to-paper map are left
out: since #6779 every printer keeps its paper, sizing and two-sided choice on its own (#2001).
That setup now keeps the paper by name, so a custom paper is found again however the list
changes, and one removed since leaves the paper as it is.
- The dialog scrolls within the screen since #6223, so the Custom… row no longer grows it.
- The Print part of the settings restore is its own function (clippy's complexity limit).
Co-authored-by: regendorf-ux <osvaldo.duran09@gmail.com>
Co-authored-by: regendorf-ux <338149330+regendorf-ux@users.noreply.github.com>
* M10: Windows prints a large image whole: drawn as tiles under Windows.Data.Pdf's cap (#5131)
Windows prints each page through Windows.Data.Pdf, which draws any image
of more than 2^22 pixels from a copy shrunk to that size. With the page
at the printer's resolution (craft-libs#396, #5023), a 6000 x 6000 scan
still printed as 2048 x 2048, its one-pixel detail gone.
pdfcraft_optimize::tile_large_images replaces each such image XObject
with a form XObject of the same object number that draws it as tiles of
at most 2000 pixels a side, so whatever drew the image draws the tiles.
Samples are copied as stored (any bit depth and colour space, decode
array, interpolation, colour key); a grey or RGB JPEG is decoded and
stored with Flate; a soft or stencil mask of the image's size is cut
into the same tiles. Tiles overlap by a pixel against drivers that
soften image edges, except under a soft mask, where the overlap would
blend see-through pixels twice. JPEG 2000, CMYK or ambiguous JPEGs,
masks of another size, more than 80 million pixels, short data and
images past the decode allowance are left whole.
pdfcraft_engine::spooler::spooler_copy applies it to the copy that goes
to the spooler on Windows, from the Print dialog's worker and doc_print;
elsewhere, or when the copy can't be read or written, the bytes go as
they are. The saved print-ready PDF is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M10: a test that the spooler gets the tiles on Windows; tiles are up to 2001 px (#5131)
spooler_copy was only tested with prints that pass through unchanged, so
one that skipped tiling on Windows would have passed. A test now checks
that on Windows it sends the tiled copy and elsewhere the bytes as they
are. The optimize README said tiles have at most 2000 pixels a side;
with the overlap they have 2001.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M10: the tiles' samples are StoredSamples, apart from the crate's Samples (#5131)
main has since added a public `Samples` (8-bit grey or RGB, for tools
that repaint an image), so two different things in one crate shared a
name. The tiles keep an image's samples as stored, in its own colour
space and depth, so each tile redraws exactly what it replaces; the
type now says so in its name and doc comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: austin62jake <340374338+austin62jake@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>