## 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: the printer's own paper sizes in the Paper list, named as the job's media
The work of #2672 on current main as one commit (its earlier four commits are on
MaxFairwinds/pdfcraft `feat/printer-paper-sizes-pre-squash`), moved to the split Print dialog
(workflows/print_ui/{draft,body}.rs) and the single integration-test binaries.
- spool::printer_papers: on CUPS the queue PPD's *PaperDimension entries (named by their labels,
the *DefaultPageSize one marked), read with the same queue checks and capped PPD read as
printer_options; paper_of_size finds one by size, as the driver feeds it.
- The Paper list offers the printer's sizes after the standard ones, read each time the dialog
opens and when the printer changes. It starts on the printer's default size (the standard
entry when that is a standard size as the driver feeds it), unless a paper was chosen, in
the dialog or remembered from the last session (#2156), and the printer lists it.
- The job names the chosen sheet as its media by the printer's own keyword, also for a standard
size the printer has, else by main's media_option.
- printer_papers tool; doc_print takes one of the printer's sizes as paper (paper_choice), and
with none named uses the printer's default, for "default" the default queue's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Print: name the driver's default keyword for a size it offers twice; fix a parity evidence path
A standard size chosen in the Paper list is sent as the printer's own keyword
for it. When the driver offers that size under two keywords (Letter and
Letter.Fullbleed), the job now names the driver's default one rather than the
first. The printer_papers_are_read_from_the_queue_ppd evidence entry named a
module path that xtask parity cannot resolve.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Print: a chosen printer paper keeps its keyword when the sizes are read again
When the chosen paper was one of the printer's own entries and its size is a
standard one (Letter.Fullbleed), reading the printer's sizes again (the dialog
reopened) matched it by size alone: it became the standard Letter and the job
named the driver's default keyword for that size. The entry is now kept by its
keyword when the printer still lists it at that size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Print: test that a PPD keyword that would break a CUPS option is not offered as a paper
A paper's keyword becomes the job's `media` option. parse_ppd_papers already
leaves out keywords with a space, `=` or a quote; the PPD test now has lines
with each, and fails when that check is loosened.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Print: on CUPS a job on a standard size names the PPD keyword of that size
PdfCraft's 4 x 6 in preset goes out as w288h432, which a driver with its
own keyword for that stock (a 102 x 152 mm label) didn't get mapped. A
cups_media name, a w<w>h<h> name or a PWG self-describing name now maps
to the nearest PPD paper within 5 pt a side, the epsilon CUPS's
_ppdCacheGetPageSize uses.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
* 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>
* feat(js): expose document page boxes
* Normalize page boxes for getPageBox
* Correct page box test expectations and formatting
* Use local page helper in box test
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
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.
* 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>
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>
* M10 #5130: resolve Windows printer-default duplex before spooling
* M10 #5130: document printer duplex resolution
* M10 #5130: adopt DocumentDuplex driver fix from craft-print
* M10 #5130: adopt two-sided capability selection fix
* M10 #5130: update documented craft-print revision
* M10 #5130: fall back when Windows duplex defaults cannot be read
* fix(print): satisfy duplex review formatting and refresh shared pin
* Repin craft-print to craft-libs main (#411 and #133 merged); ask only the picked printer's driver for two-sided support
craft-libs b6b8a55b has #411 (DocumentDuplex) and #133 (two_sided(name)). The
Printer default support check now reads that one driver instead of listing
every printer's capabilities at submit time (#2192).
---------
Co-authored-by: robertobulltech <341520377+robertobulltech@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* fix(print): M10 — Windows ya no cambia las letras de las fuentes TrueType incrustadas (#3132)
Un subconjunto TrueType incrustado numera sus glifos de nuevo (en el
subconjunto de Arial de ReportLab el espacio es el glifo 11; en la Arial
instalada, el 3). Al imprimir en Windows, Windows.Data.Pdf → Direct2D →
XPS conservaba esos números de glifo pero los dibujaba con la fuente
instalada del mismo nombre: «Revisión» salía como «Wj{nxn» y la «z» en
negrita como «Ä», tanto en las colas Brother como en la impresora Adobe PDF.
La copia que va a la cola de Windows renombra ahora cada programa
TrueType/OpenType incrustado como PdfCraftPrint<n>, en su tabla name
y en el PDF (/BaseFont, /FontName; el subconjunto conserva su etiqueta),
para que ninguna fuente instalada responda a ese nombre. Glifos,
codificaciones y /ToUnicode no cambian. CUPS y Guardar como PDF reciben
el PDF tal cual.
Tests: extremo a extremo con impose (la página se renderiza idéntica
antes y después, y el test falla sin el arreglo), fuentes que comparten
programa, Type 0, reescritura por plataforma de la tabla name y programas
dañados sin pánico. Falta comprobarlo en una impresora física con Windows.
Fixes#3132
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(print): M10 — solo se renombran en el PDF las fuentes cuyo programa se reescribe (#3132)
Si PdfCraft no puede leer o reescribir un programa incrustado (no es un
sfnt, está dañado o pasa de 64 MiB), ahora conserva también su /BaseFont
y su /FontName. Un visor que tampoco pueda cargarlo seguirá eligiendo el
sustituto por el nombre original («Arial») en vez de por PdfCraftPrint<n>.
Si no queda ningún programa reescrito, el PDF va a la cola tal cual.
Lo señaló CodeRabbit en el PR #4098. Test nuevo:
a_program_that_cannot_be_rewritten_keeps_its_name_in_the_pdf_too (falla
con el comportamiento anterior).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(print): M10 — el renombrado de fuentes para Windows decodifica contra un presupuesto total (#3132)
Cada programa ya tenía un tope de 64 MiB, pero un PDF con muchos
programas distintos podía sumar mucho trabajo de descompresión al
imprimir. Ahora todo el renombrado comparte un DecodeBudget de 512 MiB,
el mecanismo que main acaba de introducir en print. Agotado el
presupuesto, los programas que faltan conservan su nombre, igual que uno
ilegible.
Test nuevo: programs_past_the_shared_decode_budget_keep_their_names
(falla si se decodifica sin presupuesto).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(print): M10 — también se renombran los CIDFont escritos dentro de su fuente Type 0 (#3132)
Si /DescendantFonts lleva el CIDFont como diccionario en línea (en el
propio Type 0 o en un objeto array aparte), el recorrido no llegaba a
él. Cuando compartía programa con otra fuente, el programa se renombraba,
pero el Type 0, el CIDFont y su descriptor seguían diciendo «Arial», y
Windows podía volver a cruzarlos con la Arial instalada. Ahora el Type 0
se trata en el mismo recorrido, sin el segundo que hacía type0_names: un
CIDFont en línea apunta su programa, y al renombrar se cambian el Type 0,
el CIDFont y su descriptor en línea.
Lo señaló CodeRabbit en el PR #4098. Test nuevo:
a_cidfont_written_inside_its_type0_font_is_renamed_with_it (falla sin el
arreglo).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(print): M10 — renamed_table ya no copia cada cadena de la tabla name (#3132)
renamed_table copiaba el texto de cada registro antes de comprobar el
tamaño del resultado: una tabla name de menos de 1 MB con 65.535
registros apuntando a una misma cadena de 65.535 bytes reservaba unos
4 GiB, y un fallo de reserva cierra el proceso al imprimir en Windows.
Ahora la tabla se lee entera sin copiar ningún texto (cada registro
guarda el offset y la longitud de origen) y después cada cadena distinta
se guarda una sola vez: los registros que compartían cadena la siguen
compartiendo y el almacén nunca pasa de lo que alcanzan los offsets de
16 bits. Una tabla que declara más registros de los que caben antes de
sus cadenas se rechaza de entrada.
Lo encontró RomanLis91 en el PR #4098. Tests nuevos:
records_that_share_one_string_keep_sharing_it (falla con el código
anterior) y a_name_table_claiming_more_records_than_fit_before_its_strings_is_refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
After a print to a printer, its paper, page sizing (Fit, Actual size,
Shrink, Custom %) and two-sided choice are remembered for that printer and
brought back when it is chosen. The printer printed to last is preselected
after a restart while it is listed, else the system default. Saved as
print_printers, most recent first, at most 32; malformed entries are skipped.
* M10: grayscale with Print as image prints on a driver that keeps no grayscale (#5806)
With Print as image and grayscale, PdfCraft renders the pages as grey
pictures itself, yet the job still asked the driver for grayscale. A
driver that keeps no grayscale setting, Microsoft Print to PDF among
them, made craft-print refuse the job ("the printer's driver did not
retain the requested grayscale; nothing was sent"), so even the grey
pictures never printed. The job now asks the driver for grayscale only
when the pages aren't grey pictures already
(print::driver_needs_grayscale), in the dialog and in doc_print.
The print.grayscale parity note and the doc_print as_image description
say so, and the note names what is still missing: vector pages to such
a driver are refused rather than printed in colour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M10: the grey-pictures test checks that the pictures are grey too (#5806)
The test checked that grey pictures don't ask the driver for grayscale,
but not that the pictures are grey. A change that dropped grayscale from
the dialog's raster settings, while the job still skipped the driver,
would have printed in colour with the test passing. It now asserts both.
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>
* M10: the Print dialog remembers two-sided, grayscale, collate, sizing and comments & forms (#2001)
Persist the choices that describe how this computer prints and restore
them on the next launch, under the names doc_print takes; copies, pages,
layout, orientation and the printer still start fresh for each job.
Those names now live in one place, pdfcraft_print::names, used by the
tool and the settings file. Settings are untrusted: an unknown name or a
custom scale outside the dialog's 1..=1000 % keeps the default.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M10: the Print dialog also remembers a booklet's binding side (#2707)
Acrobat keeps the booklet binding (left or right) between prints;
PdfCraft started at Left every time. It is now kept with the other
remembered print choices, named as doc_print names it, and an unknown
saved value keeps the default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M10: Print starts each job's copies, pages, layout and orientation afresh (#2001)
CodeRabbit on #2103: the README promised that copies, pages, layout and orientation
start fresh, but open_print carried them over through `..keep` within a session.
ISO 32000-2 Table 147 initialises these per document when the print dialog opens
(NumCopies "typically 1", PrintPageRange), so each opening now resets them to the
defaults (Selected pages still win when thumbnails are picked); two-sided,
grayscale, collate, sizing, Comments & forms, binding, the paper and the printer
picked last carry over as before. Two-sided names now include "printer" (leave it to
the printer, main's default since #2422), so that choice is remembered too.
main's paper-persistence test expected grayscale not to persist; it is one of the
remembered preferences.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M10: a new print job also resets its layout's own options and leaves Label (#2001)
CodeRabbit on #2103: resetting the layout to Size kept pages per sheet, order, borders,
booklet subset and the poster options through `..keep`, so choosing Multiple again
brought back, say, a cut-and-stack order; and a job printed on Label went back to Size
without giving back the paper Label replaced. open_print now leaves Label through
set_handling (#3564) and resets the layout's own options with the rest of the job's
choices; the binding and sizing stay remembered. The README now says what resets on
each opening and what carries over.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M10: the Print dialog also remembers Print as image and its resolution (#6392)
A printer that needs pictures keeps getting them after a restart: as_image and
dpi are saved with the other print preferences and restored, a resolution the
dialog doesn't offer keeping the default 300 dpi. The bad-settings checks move
to their own test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: mtlcl410 <chuanlin@niar.org.tw>
One conflict, in redact lib.rs: the module list, where main added `elsewhere` and
`identification` (#6714) and this branch `globals`. All three stay, in order.
Main's #2448 work also added a sixth step to `apply`, after the verification and the pattern
guard: `metadata::scrub` removes the `/Info` entries and the catalog's and pages' XMP that repeat
the removed text. An image that only such a packet refers to leaves the file then, after
`unshare` had looked for what left, so its shared dictionary stayed. `unshare` now runs after
the scrub, last of what can take something out. Storing images again changes no page's content
and no pattern cell, so the verification and the pattern guard before it check the same pages
they did. The crate's doc says the dictionary goes last. Remove Hidden Information's order is
unchanged: main's changes to `sanitize.rs` only read (`owned_values`).
Test: `an_image_only_the_scrubbed_metadata_refers_to_takes_a_shared_jbig2_dictionary_too`. An
image under the mark is also named by the catalog's XMP, which repeats the text removed beside
it. After the redaction the packet is gone, the image on the other page is stored again, and
the dictionary is gone too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY