Commit Graph

146 Commits

Author SHA1 Message Date
Tanapat Eiam-arj 8ee3673bd4
M7: support shaped Thai text in Add Text and UI fallback fonts (#767)
* M7: support shaped Thai text in Add Text and UI fallback fonts

Share the bounded shaping and Type 3 writer with Thai, preserving vowel and tone clusters and original Unicode through save/reopen. Keep the Thai face optional and update UI notices and their translations.

Constraint: Thai font assets remain in the companion craft-fonts repository.

Confidence: high

Scope-risk: moderate

Co-authored-by: Codex <noreply@openai.com>

* M7: show Thai Add Text before and after the fix

Document the rejected input on the base commit and the saved Thai text on the fixed build using genuine UI captures.

Constraint: Synthetic content and licensed fonts only; system font fallback disabled.

Co-authored-by: Codex <noreply@openai.com>

* M7: demonstrate Thai stress cases in the comparison screenshot

Show nine lines of stacked marks, both Sara Am encodings, Thai digits and mixed Latin text. Verify all 198 Unicode code points survive saving and extraction with both PdfCraft and Poppler.

Co-authored-by: Codex <noreply@openai.com>

* CI and releases: craft-fonts pin to 3d9ab3a (adds Noto Sans Thai)

ci.yml, freebsd.yml and the craft-fonts-ref release.yml passes to craft-common-actions move
from 8dcdacd to storytold/craft-fonts 3d9ab3a, the revision with Noto Sans Thai (#18), so
Thai Add Text is built in and tested.

* ATTRIBUTION: drop the screenshot entry again (the image isn't committed)

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 18:07:36 -04:00
Zeyu Li b91d38c073
M14: text the fonts can't draw is kept and reported, never written as ? silently (#1112)
* M14: text the fonts can't draw is kept and reported, never written as ? silently

Form values and captions, text boxes, callouts, Fill & Sign text, measurement labels and stamp by-lines keep text the standard fonts can't draw, draw ? for it, mark the annotation (PCUndrawn) and report the edit (interface notice, tools' warnings); forms set /NeedAppearances (not in PDF/A). Flattening, redaction overlay text, typed and visible signatures and create-from-text refuse instead. Reports escape such characters as [U+XXXX].

* Catalogs: the two new can't-draw messages in Czech, Slovene, Kazakh, Turkish and Greek; refresh hashes

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 17:53:55 -04:00
tzagim e609722b36
M7: Hebrew in Edit text (reading order, writing order, Hebrew glyphs) (#521)
* Hebrew fix

* Tests: the faux-redaction audit gets its own work folder (it shared "audit" with the space audit, so the two raced)

* Arabic golden test: compare bytes only in a build without an Arabic face (CI's Linux job builds with craft-fonts)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 17:41:06 -04:00
Coy Geek 49217aec6c
fix(theme): complete Linux System appearance tracking (#1017)
* fix(theme): complete Linux System appearance tracking

Follow up on #881 for #446 with the standard numeric appearance portal setting
and event-driven updates that wake idle windows. Keep manual modes independent,
cache their desktop updates without repainting, and cancel the worker on close.

Retain inherited native window appearance and populate both egui style branches
with the chosen palette. Preserve saved System, the Light default and unknown
source fallback, and avoid desktop session access in headless UI contexts.

Assisted-by: Codex (gpt-6.1-sol)

* Desktop theme: keep the GNOME colour-scheme fallback XFCE needs

The portal tracker read only org.freedesktop.appearance, which XFCE's portal
doesn't answer (it proxies the GSettings schemas). It now also reads and
subscribes to org.gnome.desktop.interface color-scheme and uses it when the
freedesktop setting states no preference, as main's reader did. KDE answers
only the freedesktop one, which wins when it states a preference.

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>
2026-10-11 15:23:49 -04:00
Ridwan 5df8f3b33a
Issue #995: unlock a restricted document with its permissions password (#1127)
A PDF protected with only a permissions (owner) password opens without
asking, restricted, and nothing let the user enter that password
afterwards: the security notice only led to a read-only Security tab and
the hint said to reopen the file with the password, which the app had no
way to do (#995, #1077).

Now the security notice and Document Properties > Security offer Unlock...,
and Change settings / Remove security on a restricted document ask for the
permissions password first and then carry on, as Acrobat does. The open
password is refused there.

- crypt: SecurityHandler::authenticate_owner accepts only the owner
  password and refuses a different file key.
- cos: Document::unlock upgrades the open document in place; owner and user
  passwords unlock the same file key, so nothing is decoded again.
- engine: Session::unlock (not an edit: no undo step, not marked unsaved)
  also unlocks the undo and redo states and reopens saves as owner;
  Document::can_unlock, EditError::WrongPassword, command protect.unlock.
- ui: the permissions password prompt shares the open prompt's modal; the
  notice and Security tab get Unlock...; the disabled-command hints no
  longer point at reopening the file.
- i18n: the new strings in all 15 full catalogs (catalog hashes updated).

Tests cover crypt, cos and engine (all four algorithms, wrong and open
passwords, undo, redo, save, removing security afterwards) and the UI
(notice flow, Remove security asking first).

Closes #995
Closes #1077

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 15:18:01 -04:00
durtyrobbin f036d3841b
Print: architectural paper presets and PDF scale controls (#599 follow-up) (#1123)
* 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>
2026-10-11 14:49:32 -04:00
cyfung1031 42cfc6a8f2
M14: scan PdfCraft page-content markers once per document refresh (#1016)
* M14: Scan PdfCraft page-content markers once on open

Combine the engine-open marks and added-content discovery passes while keeping the existing public scans and document state unchanged. Add scanner equivalence and engine-open coverage, and retain the local Clippy binding needed for the workspace gate.

Co-authored-by: Codex <noreply@openai.com>

* M14: reuse the combined marker scan on full refresh

Keep full refresh from repeating the separate marks and added-content traversals, with a mark_saved regression against the independent scans.

Co-authored-by: Codex <noreply@openai.com>

* M14: scan marker metadata without cloning content containers

Borrow direct content objects and keep referenced objects alive during marker inspection. This avoids copying /Contents arrays and /PCAdded dictionaries for the combined read-only scan while preserving one-reference resolution and content-object identity.

Co-authored-by: Codex <noreply@openai.com>

* M14: record combined marker scan measurements

Update the #307 progress entry with full-refresh reuse, the borrowed scan implementation, and exact before/after timing and memory measurements.

Co-authored-by: Codex <noreply@openai.com>

* M14: align marker measurements with final benchmark run

Replace the preliminary values with the final exact-revision alternating-run medians and corresponding pair counts.

Co-authored-by: Codex <noreply@openai.com>

* marks_present and list_added read through scan_markers, so the parsing lives in one place

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 14:08:27 -04:00
lukashold 134b98b0a8
M4: open Organize Pages as a selectable thumbnail overview (#1004)
* M4: document the directly accessible page overview

Part of the organize-page-overview change.

Co-authored-by: Codex <noreply@openai.com>

* M4: open Organize in the page grid with selection and per-page actions

Show the overview from the tool catalogue, Home, palette and control channel. Add checkbox selection and local rotate/delete actions; preserve thumbnail aspect ratios.

Co-authored-by: Codex <noreply@openai.com>

* M4: translate page selection and local actions

Co-authored-by: Codex <noreply@openai.com>

* M4: cover overview entry, selection and page actions

Co-authored-by: Codex <noreply@openai.com>

* M4: record the organizer workflow improvement

Co-authored-by: Codex <noreply@openai.com>

* M4: link page overview regression evidence

Co-authored-by: Codex <noreply@openai.com>

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 13:58:58 -04:00
Justin Wilkins 56c034b0b3
M9: visible digital signatures can show a drawn, typed or image graphic (#193) (#934)
* M9: visible digital signatures can show a drawn, typed or image graphic (#193)

A visible certificate signature can show a graphic where the signer's
name is large, as Acrobat's Configure Signature Appearance does: the
saved Fill & Sign signature (drawn, typed or image) or an imported
PNG/JPEG. Sign as previews it and remembers the choice; sign_document
takes `graphic` (image, text or strokes).

pdfcraft-sign gains `Graphic` and `SignOptions::graphic`. Graphics are
checked and size-capped before drawing; images keep alpha as a soft
mask and are encrypted with the rest of the update in encrypted files.

Refs #193

* Refresh catalog hashes

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 13:47:51 -04:00
mitul72 657173fac1
M14: a chosen interface size is absolute, as in PhotoCraft (#1005)
"100%" in Preferences ▸ Interface size multiplied the scale the display
reports, so on a 1080p screen whose system reports 200 % it still drew at
200 % (#456). A chosen size is now absolute, as PhotoCraft's UI Scale is: the
display's scale is divided back out, so "100%" is one screen pixel per point.

Auto is unchanged (the display's scale times GNOME's text scaling), and its
menu entry now shows the size it draws at. The menu offers PhotoCraft's sizes,
75–300 %.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 13:28:30 -04:00
DWAA1660 4582f48ec1
automation: text_find stops reading pages once its limit is reached (#971) (#990)
text_find extracted (and cached) the text of every page before looking
for the first match, so `limit: 1` on a 9,000-page manual meant a whole
document extraction. It now reads pages in batches (8, doubling to 128)
and stops as soon as it has one match past the limit. The result says
`complete`, and a capped result carries `next` ({start_page, skip}),
which the new `start_page` and `skip` arguments accept to continue.

Fixes items 1 and 3 of #971 (and item 4 for pages after the limit).

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>
2026-10-11 13:24:58 -04:00
Ridwan d9660d4e73
Docs: sync the progress docs with issues closed on 2026-10-10 (#1064)
The parity and roadmap docs were last refreshed before a batch of fixes
merged, so they still described closed issues as open problems.

- #774 (encrypted output rejected by Acrobat, fixed by #836): gap 1
  removed from gaps.md; its remaining interop-suite work moved to gap 12.
  Gap numbers are kept so existing "gap N" references stay valid.
- #756 (Windows printing, #847): Printers row, Print area and roadmap
  rank 1 now name web printing as the remaining gap.
- #740 column select (#765), #759 1:1 touchpad scrolling (#868),
  #810 Shift-Cmd-G (#835), #825 (#822).
- #844 multi-object move (#907): marked partial, same page only.
- #799, #800, #807, #809, #811, #815-#819, #821 (incl. the #816 panic):
  gap 6 and the area tables list them as fixed.

Scores, readiness percentages and the stage are not re-judged; only the
rank-0 estimate drops by gap 1's 8-15 h (40-70 -> 32-55).

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 13:14:48 -04:00
MaxMan 2a5e8e2aa2
M14: correct Taiwan Traditional Chinese accessibility and form terms (#1023)
Correct inverted accessibility wording, pass/fail results, row/column terminology and form-choice labels. Add the missing table-row explanation and regressions tied to rule metadata.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 12:27:39 -04:00
cyfung1031 db26eb266d
M14: Reuse form enumeration during document open (#1011)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 12:21:02 -04:00
piero-83 afb586e249
CLI: reject malformed --root options before dispatch (#1048)
* fix(cli): reject malformed root options before dispatch

* test(cli): reject missing empty and repeated roots

* docs: explain strict root arguments and validation

* docs: log malformed root argument regression

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 12:12:23 -04:00
piero-83 ee1ee70463
Forms: keep the accepted value when Tab wraps to the same field (#1045)
* fix(forms): refresh the editor after accepted Tab edits

* test(forms): keep values through same-field Tab and Save

* docs: record accepted-value Tab and Save behavior

* docs: log verified Tab save regression

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 12:10:23 -04:00
piero-83 f117cde040
Forms: encode appearances with the existing simple font (#1044)
* fix(forms): encode appearances with the chosen font

* docs: record form font encoding limits

* docs: log verified form encoding regressions

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 12:07:47 -04:00
aAffliu f1e3a04587
Return renderer initialization errors to the OpenGL startup fallback (#1042)
* M13: report unavailable symbolic-link fixtures in hostile-argument tests

Only include the planted link escape when symlink creation succeeds. On
non-elevated Windows the fixture can fail with OS error 1314; exercising
its nonexistent path otherwise produces false confinement leaks. Keep
all remaining traversal and absolute-path cases and report the skip.

Co-authored-by: Codex (GPT-6) <noreply@openai.com>

* M3: return renderer initialization errors to the OpenGL startup fallback

Guard renderer creation with the existing scoped GPU-error handler and
return CreateRenderer instead of invoking wgpu's default panic handler.
This lets Auto startup reach its existing OpenGL retry before app creation.

Add a real-device invalid/valid pipeline regression and Windows startup
reproduction notes. The host-specific DX12 shader compiler error itself
is not fixed; explicit renderer choices and the non-winit path are unchanged.

Co-authored-by: Codex (GPT-6) <noreply@openai.com>

* Leave the symlink-test fix to #949, which covers the same ground

---------

Co-authored-by: Codex (GPT-6) <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 12:05:45 -04:00
mlkonrad dd065b39bb
M4, #569: Combine files takes images and text files (#1032)
* M10: refuse a PNG whose header claims more than 2 GiB of pixels before allocating

The png crate's limits don't cover the caller's frame buffer, which png_image sized from the
header alone: a few-KB PNG claiming 60000x60000 RGBA made Create allocate and fill 14.4 GB.
Apply the 2 GiB ceiling TIFF pages already have. Reached from Create, Insert pages and (with
#569) Combine files.

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

* M4, #569: Combine files takes images and text files

PNG, JPEG, TIFF, GIF, BMP, JPEG 2000 and .txt files become PDFs as they join the Combine files
list (Add files…, drops, folder scans), so each is an ordinary row or card with its pages,
thumbnails and page range. Session::as_pdf does the conversion; PDFs pass through unread
(Combine reads them itself, with their passwords), as does a file named .pdf without a header,
which the reader may still open. doc_combine accepts the same file types, and a converted
file's bookmark drops its extension.

A file that can't be read (a damaged image or PDF, a type that can't be converted) no longer
is refused at the door: it joins the list with why in its Warnings (a red card in the grid).
Combine then asks "Leave out files that can't be read?", naming them; Combine the rest goes on
without them, Cancel changes nothing. Password-protected files still block Combine until
unlocked.

The picker offers "PDF, images and text", and the empty state, folder tooltips and folder
notices say what is accepted; translated in every full catalog (catalog hashes updated).

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>
2026-10-11 12:04:10 -04:00
Bryan Mark a7e24b9512
M12: XFA script time limits start when the engine is ready (#1081)
* M12: XFA script time limits start when the engine is ready

An XFA script's wall-clock limit (1 s for scripts that run unasked, such
as initialize and calculate) also counted the engine's start-up: the
script thread, a new boa Context, and for the first script in a process
the engine's own one-time set-up (~60-170 ms when idle, shared by every
script starting at the same moment). On a busy machine a trivial
initialize script could pass 1 s, be abandoned, and so turn all of the
form's scripts off, with the error only in the JavaScript console.

That made two ui-egui xfa.rs tests flaky
(xfa_buttons_run_their_scripts_in_the_app,
messages_from_scripts_run_on_open_show_right_away): under full CPU load
25 of 40 runs of the test binary failed, and none of 40 with this
change.

- run_within: the script calls `started` once the engine is ready, and
  the time limit runs from then. Start-up has a cap of its own (10 s),
  so a caller still never waits forever.
- run_here: a once-per-process warm-up script takes the engine's first
  use, so the first form script's timed part drops from ~52 ms to under
  ~11 ms.
- FormCalc (no engine to start) signals at once, as before.
- Tests: a slow start doesn't abandon a script; the script's own time,
  and start-up, are still capped. The UI tests print the script console
  when they fail.

Runaway scripts are still abandoned at the same limits
(runaway_xfa_calculations_at_open_end_quickly_with_a_report).

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

* Add arcanii to the contributor names

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>
2026-10-11 11:58:28 -04:00
Alexis b752827162
M7: watermark offset from the page centre (edit.watermark-appearance) (#1076)
Add Watermark / Update Watermark can now move the mark from the page
centre by a horizontal and a vertical offset in points (right and up).

- edit: add_watermark refuses a non-finite offset instead of writing it
  into the content stream; tests for the placement and the refusal.
- automation: doc_watermark takes offset_x and offset_y (clamped to
  +-20,000 points); a test saves, reopens and measures the text moving.
- ui-egui: an "Offset from centre" row with Horizontal and Vertical
  fields; the preview follows it for text and for picture watermarks.
- i18n: "Offset from centre" in the catalogs that have a coverage test
  (ar, bg, de, fr, hu, it, ja, pt-br, ru, uk, zh-hans); hashes updated in
  ATTRIBUTION.toml.
- parity and roadmap: edit.watermark-appearance stays partial (no offset
  from the page edge, no print/screen-only variants, no repeat); image
  and PDF watermarks no longer listed as missing; progress log line.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:56:06 -04:00
Brandon Thomas bbbe60015c
M10: Windows printing prints at actual size, off the UI thread (follow-up to #847) (#1007)
* M10: Windows printing prints at actual size, off the UI thread (follow-up to #847)

- Sheets print at 100% on the driver's paper closest to their size, landscape
  when wide, instead of being shrunk into .NET's default 1-inch margins
  (about 76% on Letter) and never rotated. Each sheet is capped at ~64 MP so a
  poster-sized sheet can't exhaust memory.
- PowerShell runs with CREATE_NO_WINDOW: the release app has no console, so
  Windows opened one over the app for the whole job.
- The Print dialog lists printers in the background (the desktop app's
  printer_source) and spools jobs on a worker thread, so Ctrl+P and long jobs
  no longer freeze the UI. A list arriving late preselects the default only if
  the user hasn't picked a printer.
- Parity evidence named a test that #847 renamed; xtask parity passes again.
  The raster-only limit is now stated in the parity notes and README.

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

* M10: refresh catalog hashes in ATTRIBUTION.toml for the new print strings

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:15:23 -04:00
Mawfyy 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

4280f83 converted the frame-count waits in this test to deadlines after the
same 12-of-15 race under a full workspace test run, but left the first wait —
the one for the 'page' matches — on the old 60-frame loop, and it flaked the
same way on PR #847's CI. Same fix: wait up to 20 s for the 15 matches, then
assert.
2026-10-11 00:23:35 -04:00
skylar taylor-barrick ecd9d158ea UI: preserve shared document workspace on current main 2026-10-10 19:10:14 -06:00
skylar taylor-barrick 8ff27dc86e UI.9: preserve PDFCraft panels and document lifecycle 2026-10-10 15:46:23 -06:00
mitul72 8196d6f45a
M14: interface size preference; Auto follows GNOME text scaling (#902)
* M14: interface size follows the system text scale

Preferences ▸ Interface size scales the whole interface (75–250 %, setting
`ui_scale`, control option `ui-scale`). Auto, the default, follows the
desktop's text scaling: on Linux and the BSDs the desktop app reads GNOME's
`text-scaling-factor` once at launch (through `gsettings`, with a 500 ms
limit; anything unusable counts as 100 %). With Large Text (1.25) on a 1×
display, the interface no longer draws 13 px labels that show their pixels
next to the system's text.

The size is egui's zoom factor, so `pixels_per_point` grows with it and page
rasters stay one texel per screen pixel (`canvas::render_scale`). It is
applied only when it changes, so a zoom set elsewhere (the test harness's
`pixels_per_point`) isn't undone every frame. egui's own ⌘+/⌘−/⌘0 interface
zoom is turned off so those keys stay page zoom.

Tests and headless screenshots keep 100 %: only the desktop app sets
`system_text_scale`. New labels are translated in every catalog, and the
catalogs' hashes in ATTRIBUTION.toml are updated.

* M14: offer a 50 % interface size

On a display the system reports at 200 %, the smallest size the menu offered
gave a 150 % interface (#456). 50 % brings it back to 100 %; settings and the
control channel already accepted it.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 17:13:52 -04:00
mlkonrad 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>
2026-10-10 16:41:27 -04:00
Tevinhead 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>
2026-10-10 16:39:23 -04:00
DWAA1660 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>
2026-10-10 16:37:04 -04:00
DWAA1660 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>
2026-10-10 16:31:41 -04:00
Silver 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>
2026-10-10 16:28:29 -04:00
Epsilon-LLC 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>
2026-10-10 16:25:24 -04:00
才倾梦华V 7c386fa4ac
M10.2: scan JPEG metadata beyond frame headers (#764)
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 15:46:22 -04:00
Tevinhead 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>
2026-10-10 15:31:06 -04:00
Brandon Thomas 798650e8a0
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>
2026-10-10 14:23:28 -04:00
Márk Bartalos 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>
2026-10-10 08:32:09 -04:00
Márk Bartalos 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>
2026-10-10 08:25:10 -04:00
Silver 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>
2026-10-10 08:19:45 -04:00
Márk Bartalos 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>
2026-10-10 08:14:55 -04:00
Abdelhaq El Amraoui 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>
2026-10-10 06:36:54 -04:00
Todd Pederson ebc5ccd4c0
M14: View menu, tooltips and Keyboard shortcuts show Ctrl, not ⌘, off macOS (#695)
View ▸ Zoom and Page navigation were hand-written with "⌘1", "⌘[" … while
the registered items below them were formatted by Shortcut::label, so
Linux showed both ⌘1 and Ctrl+K in one menu. Ten tooltips and the view-key
rows of Help ▸ Keyboard shortcuts hard-coded ⌘ the same way.

- The keys the document view handles itself are Shortcut constants in
  ui-egui commands.rs, written with the same Shortcut::label.
- Labels follow egui's detected OS (ctx.os()) instead of cfg!, so the web
  build in a Linux or Windows browser shows Ctrl too, and tests can check
  both platforms.
- Translated tooltips take a {key} placeholder (13 catalogs; only the
  shortcut token changed, hashes updated in ATTRIBUTION.toml).
- Fit visible is listed once in View (in Zoom), not also in the generated
  list; Ctrl+3 and the palette still run it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 06:32:06 -04:00
Márk Bartalos 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>
2026-10-10 06:27:26 -04:00
Todd Pederson c5d886488c
M14: side panels keep their width after a resize (#704)
A resizable egui panel stores the width its content used at the end of
every frame and starts the next frame there, so a row wider than the panel
widens it on every repaint (any pointer move) until it reaches 520 px.

- Bookmarks: the search row reserved 32 px for an 8 px gap and a 26 px
  clear button, overflowing by 2 px. The field now reserves the button's
  size plus the theme's item spacing.
- Comments: the reply row reserved 56 px for the gap and "Post", which only
  fits English. The field now fills its line and Post sits right-aligned
  under it, keeping the field first in Tab order.
- Tests read egui's PanelState and hold each panel's width across 30
  repaints (Bookmarks with a bookmark; Comments with a reply open in
  Russian with Post hovered). On main they grow 358 → 418 and to 520 px.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:15:06 -04:00
Tevinhead 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>
2026-10-09 21:56:17 -04:00
Márk Bartalos c5d12ad1d7
View: draw page previews at the resolution they are shown (#529)
* Implemented full resolution pdf rendering

* View: walk nearby print sheets without a range index

Clippy rejects the preview neighbour loop because the index only selects sheets.

* Forms: drop duplicated appearance redraw tests

A merge copied three tests four extra times, so pdfcraft-forms failed to compile.

* 529: keep previews sharp within main's texel-for-texel rule and bounded budgets

Drops whole-page 2x supersampling (SUPERSAMPLE_LIMIT, whole_page_scale)
and the 4096 BASE_SIDE: #260 keeps whole-page rasters texel for texel,
and #419's budgets assume 2048-side backdrops. Print-preview rasters
are capped to min(max_texture_side, 4096) px on a page's long side, and
collected nearest sheet first within 128 MiB (the current sheet always).

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

* 529: struct-update syntax in the preview tests (clippy)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:26:40 -04:00
Will 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>
2026-10-09 20:55:59 -04:00
Reinhard Schuerer dc4b3eee84
Fix #378: draw on the GPU that drives the primary display on Windows (#631)
* Fix #378: draw on the GPU that drives the primary display on Windows

The low-power preference picked the AMD Ryzen integrated GPU on a desktop
whose monitors all hang off an NVIDIA card. Presenting from the headless
iGPU to a window on another GPU took the AMD display driver down, blacking
out every monitor for two minutes before PdfCraft exited.

Windows now gets the display-aware adapter choice Linux already had:
EnumDisplayDevices (through the safe `winsafe` crate, MIT) says which
adapter drives each desktop display and which is primary; `pick_adapter`
matches that to the wgpu adapters by PCI ids. The GPU driving the primary
display wins, as the built-in panel does on Linux (#8). The log names the
GPU chosen, the first question when a window stays black.

Synthetic tests cover the Windows device-id parser and the primary-display
rule; a live test checks the enumeration on the running machine. Verified
on a Ryzen 7000 + RTX 3090 desktop: the app now starts on the 3090.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XBMGuLLMhs1h8orSxcJXER

* 631: the winsafe feature comment says user (and kernel)

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 19:10:06 -04:00
jcl-bit 36468f0ad4
Windows: a GPU that can't set up the window falls back to OpenGL (#519) (#617)
* Windows: vendor egui-wgpu 0.36.2 as published (#577)

The crate as released on crates.io, plus egui's LICENSE-APACHE and
LICENSE-MIT from the 0.36.2 tag (the published crate leaves them out).
Not used yet: the next commit patches it and adds it to [patch.crates-io].

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

* Windows: a window larger than the GPU's texture limit can't close PdfCraft (#577)

egui-wgpu 0.36.2 configures a window's surface at the window's own size.
Beyond the device's max_texture_dimension_2d, Surface::configure fails
validation and wgpu's default error handler panics. eframe configures the
surface before the app is created, so no app code can intercept it
(emilk/egui#8361). #578 raised the device's limit to the adapter's; this
removes the crash for windows larger than even that.

vendor/egui-wgpu: `winit::surface_fit` shrinks both sides by one factor so
neither exceeds the limit (the longer side lands exactly on it), and egui
draws at that factor into the surface. On DX12, PdfCraft's Windows default,
the swapchain is stretched over the window (DXGI_SCALING_STRETCH), so the
window stays complete and lined up with the pointer, only softer; wgpu's GL
backend copies it unscaled into a corner instead. It applies wherever a
surface is sized: install, resize (start-up restore, maximise, DPI change,
fullscreen) and a recreated surface, which starts again from the window's
own size. Depth and MSAA textures follow. A surface logs a warning when
it becomes fitted.

Why only an Outlook launch hit it (read from the code, not observed):
winit creates the window at CW_USEDEFAULT, so Windows places it, and a
launcher that hands ShellExecute its own window gets new windows on its
monitor. winit then converts the restored logical size at that monitor's
scale: 3440 points became 8600 pixels on a 250% panel. eframe's clamp
compares points against the largest monitor, so it let 3440 through.

Tests: surface_fit with the issue's 8600 x 3423 and edge sizes (zero,
u32::MAX, a zero limit, the longer side exactly on the limit); egui-wgpu
resolves to vendor/egui-wgpu and the fit stays wired into the paths that
size a surface.

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

* Windows: a GPU that can't set up the window falls back to OpenGL (#519)

On a 2015 Intel GPU with no D3D12 driver, wgpu picked its GL adapter
over WARP and Surface::configure failed (GpuWaitTimeout). wgpu's default
error handler panics, and eframe configures the root surface before the
app is created, so the OpenGL retry from #530 never ran.

Vendored egui-wgpu (PdfCraft patch #519): winit::catch_errors runs a
closure inside wgpu error scopes (validation, out of memory, internal)
and returns the error. Painter::add_surface configures each new window's
surface inside it and returns a failure as WgpuError::ConfigureSurface,
so eframe returns Error::Wgpu and main retries with glow. Later
reconfigures are unchanged.

Tests: a real device returns a wgpu validation error from catch_errors
instead of panicking (red with the scopes removed), and add_surface
keeps the wiring (red with it removed). Docs: vendor/README.md patch 2,
docs/development.md "Renderer fallback" and PDFCRAFT_RENDERER, ROADMAP.

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

* vendor/egui-wgpu: drop the stray .cargo-ok marker

* Tests: the GPU error-scope test skips on a machine without a GPU adapter

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 18:56:25 -04:00
jcl-bit f7d5ca4a07
Windows: a window larger than the GPU's texture limit can't close PdfCraft (#577) (#609)
* Windows: vendor egui-wgpu 0.36.2 as published (#577)

The crate as released on crates.io, plus egui's LICENSE-APACHE and
LICENSE-MIT from the 0.36.2 tag (the published crate leaves them out).
Not used yet: the next commit patches it and adds it to [patch.crates-io].

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

* Windows: a window larger than the GPU's texture limit can't close PdfCraft (#577)

egui-wgpu 0.36.2 configures a window's surface at the window's own size.
Beyond the device's max_texture_dimension_2d, Surface::configure fails
validation and wgpu's default error handler panics. eframe configures the
surface before the app is created, so no app code can intercept it
(emilk/egui#8361). #578 raised the device's limit to the adapter's; this
removes the crash for windows larger than even that.

vendor/egui-wgpu: `winit::surface_fit` shrinks both sides by one factor so
neither exceeds the limit (the longer side lands exactly on it), and egui
draws at that factor into the surface. On DX12, PdfCraft's Windows default,
the swapchain is stretched over the window (DXGI_SCALING_STRETCH), so the
window stays complete and lined up with the pointer, only softer; wgpu's GL
backend copies it unscaled into a corner instead. It applies wherever a
surface is sized: install, resize (start-up restore, maximise, DPI change,
fullscreen) and a recreated surface, which starts again from the window's
own size. Depth and MSAA textures follow. A surface logs a warning when
it becomes fitted.

Why only an Outlook launch hit it (read from the code, not observed):
winit creates the window at CW_USEDEFAULT, so Windows places it, and a
launcher that hands ShellExecute its own window gets new windows on its
monitor. winit then converts the restored logical size at that monitor's
scale: 3440 points became 8600 pixels on a 250% panel. eframe's clamp
compares points against the largest monitor, so it let 3440 through.

Tests: surface_fit with the issue's 8600 x 3423 and edge sizes (zero,
u32::MAX, a zero limit, the longer side exactly on the limit); egui-wgpu
resolves to vendor/egui-wgpu and the fit stays wired into the paths that
size a surface.

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

* vendor/egui-wgpu: drop the stray .cargo-ok marker

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 18:54:10 -04:00
Streetblock 1f0466fcae
M2: bound image resampling buffers before allocation (#592)
Check source, destination, convolution and filter-plan sizes before allocation. Preserve original image geometry when a resize exceeds the policy, while allowing valid wide sources to shrink.

Co-authored-by: OpenAI Codex <codex@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 18:53:56 -04:00