The print filter read /Subtype with a direct-only lookup, so a standard
annotation whose /Subtype is an indirect reference (ISO 32000-2 §7.3.10)
looked like an unknown type and an Invisible flag dropped it from print
(Table 167 applies only to non-standard types, Table 171). The subtype is now
resolved through the document first.
Also removes a stray cell separator in the hayro-interpret row of
vendor/README.md that pushed the test references for patches (44)-(56) out
of the table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
An annotation whose Invisible flag (bit 1) is set and whose /Subtype is none
of the standard types of ISO 32000-2 Table 171 is no longer drawn (Table 167)
or printed (Table 167: "do not print it even if the Print flag is set"). A
/Popup annotation's /AP is no longer painted as page content: per §12.5.6.14 a
popup has no appearance stream of its own and is shown by the viewer as a
window for its parent. Printing already left popups out.
Link appearances are painted as before (§12.5.5).
hayro-interpret patch (56), recorded in vendor/README.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
dialog hides Two-sided while Booklet is chosen and prints the booklet two-sided itself
(Using Acrobat X Pro, Print dialog box options: "Acrobat automatically enables duplex
printing, if available, for booklet printing"; later manuals and the current help no
longer say so, and on a current Acrobat the Two-sided control is shown for Size and
hidden for Booklet with the same duplex-capable printer). print::booklet_duplex gives a
booklet's setting: both sides flip on the short edge, the fold of a 2-up sheet turned
sideways; front or back only is one pass of a manual duplex and prints one-sided. The
Print dialog hides Two-sided while Booklet is chosen and sends that setting, leaving the
Two-sided choice as it was for the other layouts (and as #2001 remembers it); doc_print
applies the same rule. This replaces the first version, which rewrote Two-sided to the
short edge when Booklet was chosen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
30 x 42 in is `na_wide-format_30x42in` in PWG 5101.1-2023 and the IANA
IPP registry (checked against both). `na_arch-e1_30x42in` was asked for
on the IPP list in 2021 but never registered. CUPS maps the new name to
the PPD's 30 x 42 in size (cupsfilter, synthetic PPD), and the paper
test pins it and fails on the old name.
Doc comments for bad, printers and two paper tests, for the review's
docstring check.
Review: storytold/pdfcraft#4161 (CodeRabbit).
ARCH A-E went out as arch_a_9x12in .. arch_e_36x48in; PWG 5101.1 names
them na_arch-a_9x12in .. na_arch-e_36x48in (ARCH E1's IANA-registered
na_arch-e1_30x42in already had the prefix). CUPS parses either from the
dimensions, which is why the cupsfilter check passed, but a driverless
printer may accept only the names it advertises. Re-checked the corrected
names with cupsfilter against the synthetic PPD.
The paper test now pins every size's exact name, and fails on the old
spelling. Doc comments for doc_print and PrintDraft::job.
Review: storytold/pdfcraft#4161 (CodeRabbit).
A CUPS job that names no paper gets the queue's PPD default (A4 on the
reporter's Xerox queue), whatever the sheets were laid out on. Two gaps
remained after #1583:
- cups_media had no name for 13 of the 21 PAPERS sizes (A6, ARCH A-E1,
ANSI C-E, A0-A2), so the Print dialog sent no media for them. They now
use PWG 5101.1 names (the IANA-registered na_arch-e1_30x42in for ARCH
E1), which carry their dimensions: CUPS feeds the driver's size of those
dimensions whatever the driver calls it, and driverless IPP printers use
them natively. Checked with cupsfilter against a synthetic PPD whose
sizes use vendor keywords (ARCHD, AnsiD, A0...): each name produced its
size's bounding box. The 8 sizes that already worked keep their PPD
keywords.
- doc_print to a printer (CLI run, MCP) sent no paper at all. It now
resolves the paper to its PAPERS index and sends the same media option
as the dialog, after the driver options. The job is built in spool_job
so it can be tested without printing.
print::media_option is the one place both get the option from. Both new
tests were seen failing without the fix. The macOS tray half of #3752
(Auto Select instead of the PPD's default tray) is a craft-print change.
#4114 moves the bodies of Stream::decoded_within and decoded_strict into
decoded_within_typed and decoded_strict_within_typed, which return
FilterError so DecodeBudget can tell a spent limit from corrupt data.
This branch checks /Filter with readable_filters there. Kept #4114's
typed functions and made readable_filters return
FilterError::Unsupported, so a /Filter that is not a name fails both
typed decodes without charging the budget; decoded_first_filter_strict
maps it to CosError as before.
Redaction reads page content and form XObjects strictly (upstream) and
within one page's decode budget (this branch): both now go through
DecodeBudget::decode_strict.
page_form listed every page of the document to take the one it
printed, so imposing P pages walked the page tree and copied its leaf
dictionaries P times over: P^2 page copies. impose passes the list it
already holds. A page index outside it is an error, not a panic.
Pages printed as pictures were drawn with everything on them, so form fields only
printed the page content, and the document with stamps printed every comment. The
pictures are drawn from a copy holding only what the vector print draws.
Printing and the label text-direction check decoded every /Contents
entry with a fresh 1 GiB allowance, and the direction check re-entered
forms at every Do with only a depth limit, so a few hundred bytes of
nested repeated draws meant hundreds of thousands of form walks. Both
charge a per-page DecodeBudget: printing refuses a page whose content
runs past it, and the direction check stops reading there.
4.125 × 9.5 in (297 × 684 pt) in the print dialog's Paper list and
doc_print, fed to CUPS as Env10. Appended after the 4 × 6 label so
LABEL_PAPER keeps its index.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Print: 4 x 6 in label paper and a Label layout for shipping labels
Thermal label printers (e.g. VEVOR Y486) could not be used for eBay/carrier
labels: the paper list had no 4 x 6 in size, the chosen paper was never sent
to CUPS (the queue's default was used), and labels laid out sideways on a
Letter/A4 sheet printed small or rotated.
- PAPERS gains "4 x 6 in label (101.6 x 152.4 mm)"; cups_media() sends the
chosen sheet to the spooler as `-o media=` (Letter, Legal, Tabloid, A3-A5
and w288h432 for the label), so the driver feeds the size the PDF was
laid out on.
- New Layout::Label: each page is cut to its printed area (a low-resolution
render, as Fit Visible does), turned a quarter turn when it is sideways in
the direction that makes its text upright (text_reads_up reads the text
and form matrices), and fitted uniformly with a small margin. Vector
content is kept, so barcodes stay sharp and undistorted. "Turn upside
down" flips it 180 degrees. One label per sheet.
- Choosing the label paper selects the Label layout, and vice versa.
- Strings translated in every bundled catalog.
Tests: label geometry (crop, quarter turns, no stretching, margins),
cups_media, text direction; print, ui-egui and automation unit tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFXSksdMaJ3YnheY7Q6sTx
* Refresh catalog hashes after merging main
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Alessio Boschi <alessio.boschi@dinova.one>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Print: print as image (Advanced), with grayscale pictures
print::Settings.raster (dpi 72-600, grayscale): Session::print_pdf renders each
distinct page used once the way the viewer shows it (comments left out when the
print leaves them out), composes it over white, grey by Rec. 601 luma when
grayscale is on, makes an image-only document of the pictures and imposes it on
the paper like the pages themselves. The Print dialog's new Advanced section
(folded unless on) offers it at 150, 300 or 600 dpi; doc_print takes as_image
and dpi. Rows in every language; parity print.print-as-image shipped,
print.grayscale shipped (the pictures are grey, not only the printer asked).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ATTRIBUTION.toml: the language files' checksums after the new rows
cargo xtask assets checks each catalog's sha256; the rows this branch adds
changed the files without updating it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refresh catalog hashes after merging main
---------
Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The print-ready PDF that `pdfcraft_print::impose` builds starts from a new
catalog with only /Type and /Pages, so the document's /OCProperties was not
carried over. The pages' marked content still refers to its optional-content
groups, but nothing said a group was off, so a layer that is switched off in
the document (and not drawn on screen) was printed anyway.
Keep /OCProperties in the new catalog, so the sheets hide the same layers the
document hides. Test: a one-page document whose red square is in a layer; with
the layer off by default it is not drawn on screen and, now, not on the
printed sheet; with the layer on it is drawn in both.
Printing still follows the layers' default state only; applying the Print
event of /AS (a layer shown only on screen or only in print) is not done here.
Layout::multiple(n) maps counts without a fixed grid to a near-square
grid (3 -> 2x2, 5 -> 3x2), but Layout::Multiple kept only the grid, so
layout() filled every cell: three per sheet put four pages on a sheet.
Layout::Multiple now carries per_sheet, capped by the grid; spare cells
stay blank and cut-stack piles use the same count. The doc_print tool
and the print dialog pass the count through.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A poster scale large enough to make both tile counts saturate at
usize::MAX overflowed when they were multiplied for the 1024-sheet
check: a panic with overflow checks, and a product that wraps to 1
without them, which let the sheet loops run. The headless doc_print
tool accepts any finite positive scale, so a script could reach it.
Multiply with checked_mul and treat an overflow as over the limit, so
the request gets the existing "more than 1024 sheets" error.
Adds tests::huge_posters_are_refused_at_the_sheet_limit, which panics
before this change and passes with and without overflow checks after it.
* i18n: let the layers below hand their sentences over, so they can be translated
The accessibility findings, the PDF/A problems, the signature details, the
page-range mistakes and the edit labels were English in all ten languages,
because matching their wording from the UI with a tl! literal would break
silently whenever the engine changed it.
So each layer now hands the wording over instead, apart from the document's
own words: a WORDINGS list of every sentence it can say, with holes in it, and
said(&words), which looks the wording up through the closure it is given and
fills the holes in a single pass. in_english changes nothing, for the CLI and
the tests; the UI passes &i18n::words, a catalog lookup. A wording with no
entry comes back as it stands, which is the English, so the nine catalogs this
does not touch read exactly as before.
a11y::report_html takes a language tag and a words closure, so a saved report
reads the way the panel read, down to its lang attribute. print::PrintError
gained a variant per thing a person can be wrong about, with the numbers apart
from the words; faults rather than mistakes keep their own text.
Spanish translates all of it (107 entries). spanish_covers_what_the_engine_says
requires an entry per wording in the eight lists and
spanish_keeps_the_holes_in_what_the_engine_says requires the holes to survive
the translation, so the catalog cannot fall behind the engine unnoticed.
No new tl! literal, so the Chinese, French and Russian UI-literal gates are
unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Sign tests from #1099: compare against the &'static str kinds
* Camera tests: undo returns a Label now
---------
Co-authored-by: Fernando Areán <soporte@codebit.es>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M10.5: Restore architectural paper presets and PDF scale controls
Port the missing paper catalog and scale controls from PR #599 onto the merged Windows printing implementation. Keep the spooler and background discovery from #847/#1007. Document the feature-by-feature audit and verification limits.
Assisted-by: OpenAI Codex
* Drop the provenance write-up and the screenshot example (the kittest covers the dialog)
* Refresh catalog hashes
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M10: select pages in the Pages panel and print the selection
The Pages panel could only jump to a page. It now picks several:
Ctrl/Cmd-click toggles a page (the first one keeps the current page
selected too), Shift-click takes a range from the last clicked page, a
plain click goes to the page and starts over, Escape clears. Picking does
not turn the page. It is the selection the Organize grid and the page
commands already use; the click rules moved to DocView::click_page and
both places call it.
Print opens on a new "Selected pages" choice when pages are picked and
prints them in page order. They are passed as positions
(print::select_listed), not as a typed range, which would read numbers as
page labels first. ui.state reports active.selected_pages.
Windows still has no printer output, so there the result is a PDF of the
selected pages. No context menu or drag in the panel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Restore ROADMAP.md, emptied by accident in this PR
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>
The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now
PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the
PrintCraft settings and crash-recovery folders to the new name, and both apps read settings
saved under the old storage key, so upgrades keep recent files, preferences and unsaved work.
The signing test fixtures keep their PrintCraft subject names (they are binary).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
annot, compare, content, cos, export, geom, model, ocr, preflight, print and redact have no
production `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!`/`unimplemented!`. The attribute
keeps it that way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>