Commit Graph
679 Commits
Author SHA1 Message Date
mtlcl410andClaude Opus 5.5 ecffce69ee fix(engine): Save writes a repaired file in full even without edits (#1937) (#2029)
* fix(engine): Save writes a repaired file in full even without edits (#1937)

A damaged file is repaired on open (its cross-reference index rebuilt from
object headers, as Document Properties reports), but Save wrote it back as it
was: `Session::save_bytes` returned the bytes as opened whenever nothing was
edited, and a repair is not an edit. The "repaired" file on disk was still the
damaged one.

`save_bytes` now writes a reconstructed document in full even with no edits.
`Document::reconstructed()` in cos names the condition (no revision chain:
rebuilt, or built from scratch), and `write_incremental` uses it too, so the
writer and the engine share one definition.

Tests: `engine::tests::a_repaired_file_is_rewritten_by_save_even_without_edits`
(table offsets shifted; no table at all) and
`automation::saving_a_repaired_file_puts_the_repair_on_disk` (doc_open,
doc_info, doc_save over the same path, reopen). Both fail with the condition
removed: the saved bytes equal the damaged file. parity: core.repair-broken-files
cites them and notes that a repair still does not count as an unsaved change.

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

* fix(engine): a signed repaired file keeps its bytes on Save without edits (#1937)

Review follow-up: writing a reconstructed file in full on Save puts the
repair on disk, but for a signed file it would also invalidate its
signatures without warning. Save now skips the early return only when the
document is reconstructed and not signed. A regression test signs a
document, appends a `startxref` that points nowhere (the signed bytes stay
intact, the cross-reference data must be rebuilt) and checks that Save
without edits returns those bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-18 10:47:28 -04:00
aiongg b6b02a1b27 Merge upstream main 1952a8880 into next
ROADMAP.md, docs/roadmap.md: union of both sides' lines, each once.
parity/acrobat-features.toml: the XFA entry has both sides' evidence and notes (#2604 page areas, #2813 CLI layout).
2026-10-18 14:22:30 +00:00
alehel 522c1dcd22 Merge remote-tracking branch 'origin/main' into feat/all-tools-expand-view
# Conflicts:
#	ATTRIBUTION.toml
2026-10-18 16:17:28 +02:00
alehel 911147126c Merge remote-tracking branch 'origin/main' into fix/background-hidden-by-page-backdrop
# Conflicts:
#	ATTRIBUTION.toml
2026-10-18 16:05:19 +02:00
Ian HollowayandBrandon Thomas 1581cd1edd M10: keep the OCR scan fixture at its original page size (#2399)
Assisted-by: GPT-6

Signed-off-by: IanHollow <72767437+IanHollow@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 09:50:16 -04:00
Claude a30a0ab713 G14: Romanian (ro) interface catalog
Add a clean-room Romanian translation of the UI catalog (2,670 entries,
every bundled key), the CLDR plural rule for Romanian (one / few /
other, "20 de pagini"), the ATTRIBUTION.toml entry, coverage and plural
tests, and the localization docs and roadmap entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JadgGo4G2GHaAMNb3qyygk
2026-10-18 16:27:09 +03:00
aiongg 7f718ecc62 Merge upstream main 50f261d70 into next
ROADMAP.md, docs/roadmap.md: union of both sides' log lines and milestone rows, each line once.
parity/acrobat-features.toml, crates/xfdf/README.md: next's text, a superset of main's (#1917, #2686 plus #2688).
crates/edit/src/tests.rs: both sides' new tests (#2558 and #3084).
crates/ui-egui/src/editing.rs: the picked picture is cleared on every edit, and a new drawing commits its raster (#3149).
2026-10-18 13:12:21 +00:00
PerchAnton 52cdb9260c M12: retain product notes while integrating current main 2026-10-18 16:09:57 +03:00
PerchAnton f9b8284819 M12: retain product notes while integrating current main 2026-10-18 16:05:46 +03:00
PerchAnton be6a66dd79 M12: retain product notes while integrating current main 2026-10-18 16:00:08 +03:00
jonathanwxh-cell 0f28c6a902 M11: target PDF version and object streams option in PDF Optimizer (#3090) 2026-10-18 18:02:10 +08:00
Guillem Hermida ec8e30ad80 Merge remote-tracking branch 'origin/main' into fix/2583-note-icon-no-rotate
# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/src/comments_panel.rs
#	vendor/README.md
2026-10-18 10:47:54 +02:00
Marco ArnulfoandBrandon Thomas 50f261d70c M6: detect a row of comb boxes as one comb text field (#2111)
* M6: detect a row of comb boxes as one comb text field (fixes #1887)

* M6: keep empty tables of wide cells out of comb detection (#1887)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:50:00 -04:00
EnricoandClaude Opus 5.5 eeadc79d15 M4: reverse page order (organize.reverse-pages) (#1720)
Organize pages ▸ More ▸ Reverse pages (and the command palette) reverses
the selected pages among their own positions, or the whole document when
fewer than two pages are selected. It is one undoable step, needs the
assemble permission like the other page edits, and is available headless
as the page_reverse automation tool (pages optional, default all).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:48:47 -04:00
94f1a0d157 fix(engine): a new or recovered document stays unsaved through undo (#2346)
* fix(engine): a new or recovered document stays unsaved through undo

#1435 made undo back to the last saved bytes clean. For a document
created by Combine, Extract or Create, or restored from recovery, those
bytes were never written where the document belongs, so edit then Undo
made it clean: Close no longer asked to save and autosave stopped keeping
it. Editor::unsaved_file, set by open_new and mark_recovered and cleared
by mark_saved, keeps such a document unsaved until it is saved; Revert
follows the same rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

* fix(engine): test that doc_close refuses a recovered or new document after undo

Review follow-up on #2346: a recovered document and a new one, rotated
and undone, are refused by doc_close without discard_changes, and close
with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:47:43 -04:00
7c8ca0bde8 M7: keep the text blocks cache after the document is edited (#2504)
The engine caches a page's text blocks for opening a paragraph and
applying its edit, but only while the document is unmodified: it was
keyed by the source identity, which edits keep. After the first edit the
cache never hit again, and the page was grouped again on every call (the
inline editor and the panel each call it after an apply).

pdfcraft_cos::Document::read_state() returns what the document reads as:
its source identity, its trailer and every changed object, compared as
allocations that the state keeps alive. A clone or a snapshot restored by
undo has the same state; any set, add, free or trailer change makes a
different one. The cache is keyed by it, so it hits after edits and never
returns another state's blocks.

On a page of 120 rows, text_blocks after an edit took 0.215 ms per call
and now 0.006 ms (release build, best of three; a throwaway timing test).

Tests: a read state matches a clone and not a document changed by set,
free or a trailer change, nor another document; after an edit the engine
groups the page once and undo and redo get their own blocks. The engine
test fails without this change.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:46:56 -04:00
mtlcl410andClaude Opus 5.5 0f870c7b7d fix(forms): read dynamicRender only at config/acrobat/acrobat7, in UTF-8 or UTF-16 (#3180)
Review follow-up. The config packet is decoded as UTF-16 (byte order mark,
or `<` written as two bytes) or UTF-8 before the scan, as crates/xfa reads
packets. The scan walks tags, skipping comments, CDATA sections, processing
instructions and declarations, and takes the value only at
config/acrobat/acrobat7/dynamicRender, so the same name in form data, in a
comment or elsewhere in the config no longer counts.

The engine test now adds the element to the fixture's own config packet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-18 15:33:57 +08:00
NetterSilverandClaude Haiku 5.5 43f8ddf264 Apply JPEG EXIF orientation when creating PDFs from images (#2951)
Fixes #2944

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:14:25 -04:00
mtlcl410andClaude Opus 5.5 4ff53a8d8e fix(engine): printing keeps layers hidden in the Layers panel hidden (#3046) (#3050)
The print-ready PDF copied the document's /OCProperties as they are, so it
printed the layers in their default state: a layer hidden in the Layers
panel disappeared on screen and came back on paper. Print as image already
used the view's layer choices (it renders with the document's config).

Once the layers have been changed, the copy imposed for printing now has
its default configuration (/D) set to them: /BaseState /ON, /ON the
visible layers, /OFF the hidden ones, its other entries kept. With the
layers untouched, the document's own configuration prints as before.


Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:12:26 -04:00
mtlcl410andClaude Opus 5.5 fe3e07c244 fix(forms): an XFA form whose config forbids dynamic rendering is static (#3180)
An XFA form with no AcroForm fields was always classified as dynamic, so
opening it laid the template out into new pages and appended them as a
revision: the pages the user saw, and any later save, were PdfCraft's
layout rather than the file's own pages, with nothing saying so. Adobe
Designer writes <dynamicRender>forbidden</dynamicRender> in the config
packet of a static PDF form; such a form is now static and its pages are
kept. /NeedsRendering still makes a form dynamic, and with no word from
the config a form without fields stays dynamic as before.

The config packet is found in an /XFA array or a whole-XDP stream, read
with a 16 MiB decode cap, and scanned for the one element.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-18 15:07:38 +08:00
PerchAnton 1e498ec214 chore(pr-2648): integrate main d8b27058 2026-10-18 08:24:36 +03:00
PerchAnton 309834ffef chore(pr-2624): integrate main d8b27058 2026-10-18 08:20:38 +03:00
PerchAnton 226ed13c88 chore(pr-2619): integrate main d8b27058 2026-10-18 08:18:14 +03:00
sanches 78b85482bb Merge remote-tracking branch 'origin/main' into i18n-id-vi 2026-10-17 22:50:13 +03:00
Osvaldo Duran MelendezandClaude Opus 5.5 1edc63907e Merge upstream main bc7a12b9 into pr/attachments: catalogs and ATTRIBUTION hashes by key (ATTRIBUTION.toml )
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:41:50 -04:00
alehel 6c23b7209c Merge remote-tracking branch 'origin/main' into feat/all-tools-expand-view
# Conflicts:
#	ATTRIBUTION.toml
2026-10-17 20:21:31 +02:00
alehel 5e5e699484 Merge branch 'fix/background-hidden-by-page-backdrop' of https://github.com/alehel/pdfcraft into fix/background-hidden-by-page-backdrop 2026-10-17 20:20:54 +02:00
alehel 74f3522478 Merge remote-tracking branch 'origin/main' into fix/background-hidden-by-page-backdrop
# Conflicts:
#	ATTRIBUTION.toml
2026-10-17 20:20:34 +02:00
mtlcl410andClaude Opus 5.5 92990b2e2c M3: layer_set shows or hides a layer, and page_render draws with the document's view settings (#2986)
The Layers panel had no automation tool: doc_info listed the layers, but
nothing could show or hide one, so layer visibility could not be checked
headlessly. layer_set does what the panel's eye icon does, naming a layer
by name (every group with that name, as CAD exporters repeat names) or by
its 1-based position in doc_info's list.

page_render built its own RenderConfig with only the password and the
reading colours, so it ignored layer visibility and comments_hide as
well: hiding comments through the tool changed nothing it drew. The
automation renderer now takes the document's own RenderConfig
(Session::render_config), and both tools drop the cached renderer when
they change a setting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-17 21:09:57 +08:00
Guillem Hermida 32ca91a31f M5: sticky notes stay upright where they are placed on rotated pages (#2583)
Notes and file attachments are saved with the NoRotate flag (/F bit 5),
but nothing honoured it: on a page with /Rotate the icon was fitted to
/Rect and turned with the page, and sat away from where it was placed.

The vendored renderer now honours NoRotate (hayro-interpret patch 37):
the appearance fitted to /Rect is turned against the page's rotation
about the rectangle's upper-left corner, which stays put (ISO 32000-2
12.5.3). Form fields and links stay in /Rect, where their click targets
come from. A counterrotated appearance, as stamps use, would be turned
twice by viewers that honour the flag.

Annotation::shown_rect gives the box the note is drawn in. The UI uses
it for hit-testing, outlines, moving, the hover pop-up, the Comments
panel flash and the edit composer; a resize maps it back to /Rect.
comment_add anchors an icon's displayed top-left at `at`, and
comment_list, doc_info and comment_edit use the drawn box.

This also covers the sticky-note part of #1869.
2026-10-17 11:52:14 +02:00
alehelandClaude Opus 5.5 0b63f5a4ba fix(edit): check page backdrops with one page-tree walk per batch
Review of #2815: `page_of` built every page to return one, and the Add
Background dialog checked 24 pages a frame that way, so a frame cost
24 page-tree walks (93 ms at 5,000 pages, 385 ms at 20,000).

- `page_backdrops(doc, pages)` walks the tree once for a batch; the
  dialog checks one batch a frame and `doc_backdrops` one batch in all.
  A page past the end answers None without hiding the others (the
  viewer's page count can disagree with the page tree's).
- `scan` and `replace` take the page; `add_background` no longer walks
  the tree twice more per page to replace a backdrop.
- Tests: 4,000 pages checked in one batch under a generous bound (17.6 s
  with the old lookup), batch order and missing pages, and a background
  over several pages sharing streams, replaced and restored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:43:46 +02:00
enes yıldızandClaude Opus 5.5 06c4a970a2 fix(cli): info, text, render and audit take the owner password of R2–R4 files
`pdfcraft-cli info`, `text`, `render` and `audit` rejected the owner
password of RC4-40, RC4-128 and AES-128 files with "the password is
incorrect", while `run doc_open` accepted it (#2670, item 8). The renderer
authenticates only the user password of revisions 2–4. `Session::open`
works around that by handing it the user password that owner
authentication recovers, but the CLI passed the typed password straight to
`inspect` and `PageRenderer`.

`pdfcraft_engine::render_password_for` now gives any render path outside a
session the password the renderer takes, sharing the recovery with
`Session::open`. The CLI uses it for the renderer and keeps the typed
password for the engine session and the cos document, so the owner
password still lifts copy restrictions in `text`.

The render test decodes the PNG and checks its dimensions and that it
has non-white pixels, for every algorithm with either password.

Refs #2670 (item 8 only).

Checks: cargo fmt --check; cargo clippy -p pdfcraft-cli -p pdfcraft-engine
--all-targets -- -D warnings; cargo test -p pdfcraft-cli; cargo test -p
pdfcraft-engine (and --lib); cargo xtask layers, assets, parity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:15:22 +03:00
aiongg 265403813b fix(links): resolve remote document paths with the shared path check
GoToR file specifications go through craft-docpath: network, device and
object-manager paths (\??\, /??/) are refused without touching the
filesystem, relative targets resolve against the source document's
folder, and a result that is not absolute is refused rather than read
against the working directory. Files named on the command line open
under their absolute path, so their relative links keep resolving.
2026-10-16 18:16:50 +00:00
sanches 6496dc6f47 Merge remote-tracking branch 'origin/main' into i18n-id-vi
# Conflicts:
#	ATTRIBUTION.md
#	ATTRIBUTION.toml
#	ROADMAP.md
#	crates/engine/src/dates.rs
#	crates/oracle/src/pixels.rs
#	crates/ui-egui/src/i18n/mod.rs
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/system_fonts.rs
#	crates/ui-egui/src/theme.rs
#	crates/ui-egui/tests/fonts.rs
#	docs/gaps.md
#	docs/localization-parity.md
#	docs/localization.md
#	docs/roadmap.md
#	docs/target-app-parity.md
2026-10-16 20:50:35 +03:00
FredApps 66a7667aca Merge current main into idle autosave branch
Preserve the idle repaint and second-edit recovery regression while refreshing already-merged upstream work. Validated exact tree with full local CI and native Linux idle recovery.
2026-10-16 19:50:09 +02:00
aiongg 934a10740d Merge track r1-core (fredapps-r1)
ROADMAP.md, docs/roadmap.md: keep next's status line
2026-10-16 14:17:52 +00:00
aiongg 405ad489c2 Merge PR #2681: Replace Text: give the caret its own /NM (#2666) 2026-10-16 14:02:08 +00:00
aiongg eb403f84ec Merge PR #2702: Remove security also from files with /Encrypt inline in the trailer 2026-10-16 14:02:08 +00:00
mixlnwuu d5bbf60efe i18n: add Thai (th) interface language
A clean-room Thai catalog covering every key the bundled catalogs translate, its LANGUAGES row (one plural form) and Thai month and weekday names in DATE_LANGUAGES.
2026-10-16 19:22:00 +07:00
mtlcl410andClaude Opus 5.5 b97e59190f M5: year-first slash dates (yyyy/mm/dd, yyyy/m/d) for Fill & Sign and headers
Taiwan, Japan and China write dates year first with slashes (Windows'
zh-TW short date is yyyy/M/d), and neither the Fill & Sign presets nor
the header and footer date tokens offered it. Both now have yyyy/mm/dd
and yyyy/m/d. The header and footer list gains them at its end, since
its selection is an index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-16 19:20:48 +08:00
boneylandandClaude Opus 5.5 bf2eb470ed M6: detected form fields get no border or fill
Prepare a form ▸ Detect form fields places each field on a line or box the
page already draws, but every new widget got a grey /MK /BC and an opaque
white /MK /BG, so the fill hid the printed form underneath. Detected fields
now clear both in the same undoable step; fields drawn by hand keep the
defaults.

Refs #294, #2831.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:59:09 +08:00
alehelandClaude Opus 5.5 a9b0db12c6 fix(edit): show backgrounds on pages that paint their own white backdrop
Add Background draws behind the page content, so on a page that first
fills itself with opaque white (browsers' Save as PDF, print drivers) the
background was hidden completely and the tool looked broken.

- pdfcraft-edit: new `backdrop` module. A strictly detected backdrop (one
  axis-aligned `re`, opaque white in a device colour space, painted before
  anything else and covering the crop box) is replaced by turning that one
  fill into `n` in a per-page copy of its stream; the original stream is
  left untouched for other pages. Remove background restores it, unless
  the copy was edited since. Page-sized pictures (scans) are reported only.
- Background gains `replace_backdrop` (default on); the default colour is
  a visible light yellow instead of near-white.
- Dialog: offers to replace the backdrop, warns about scanned pages,
  checks pages a few per frame, preview draws text over the colour.
- Automation: `doc_background` takes `replace_backdrop`; new read-only
  `doc_backdrops` lists pages with a backdrop.

Acrobat's behaviour on such pages is unobserved; recorded as our choice
in parity/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 10:46:02 +02:00
Enrico SitaandClaude Opus 5.5 26051871d0 M11: remove PDF/A or PDF/X identification (optimize.remove-standards-id)
A file edited after it was made PDF/A or PDF/X can keep claiming a
standard it no longer meets. organize::remove_standard_id removes the
claim: the pdfaid: XMP properties for PDF/A, or GTS_PDFXVersion and
GTS_PDFXConformance in the document information and as pdfxid: XMP
properties for PDF/X. Output intents and other claims (PDF/UA) stay.

The XMP is edited with the packet editor Document Properties already
uses (sync_info and the new remove_properties share its read and write),
so the rest of the packet stays byte for byte and unsafe packets are left
alone. It is one undoable edit, in Standards ▸ PDF/A as a button worded
like its undo label, and the standards_remove_id tool (pdfa or pdfx).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:45:31 +02:00
PerchAnton f79ab9ae2d M12: update viewer-warning PR against main (#2589) 2026-10-16 07:48:00 +03:00
PerchAnton d78df1f163 M12: update growable-field PR against main (#2569) 2026-10-16 07:44:37 +03:00
PerchAnton 4d0f2e4dde M12: update print-button PR against main (#2572) 2026-10-16 07:41:57 +03:00
Osvaldo DuranandClaude Opus 5.5 e5c2ea2bff Merge upstream main 5fdd820b into pr/attachments
Conflicts: ATTRIBUTION.toml, crates/ui-egui/src/i18n/es.tsv, crates/ui-egui/src/i18n/vi.tsv. Catalogs keep upstream's rows, then this branch's rows upstream lacks; ATTRIBUTION.toml is upstream's with every sha256 recomputed; progress lines keep both sides, upstream's first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:31:50 -04:00
alehel 8f0b36d66e Merge remote-tracking branch 'origin/main' into feat/all-tools-expand-view
# Conflicts:
#	ATTRIBUTION.toml
#	crates/ui-egui/src/i18n/es.tsv
2026-10-16 03:25:54 +02:00
Chiranjeevi Uandskylar taylor-barrick 5fdd820bdb fix(ui): validated signature display, snapshot tab reuse (#1895)
* fix(ui): validated signature display, snapshot tab reuse

Signature widgets with layered appearances baked the unverified state
into the page, so a valid signature kept reading 'Signature Not Verified'
while the banner and panel said valid. Validated verdicts are now drawn
into the icon and caption layers in the display copy only: the signed
bytes are untouched, saving keeps the original appearance, and files
without those layers or without a valid verdict render exactly as before.

Other valid signatures still get the verdict mark over their widget,
sharing one style with the Signatures panel, and hide it again once the
appearance itself shows the verdict.

View signed version and View revision opened a new tab on every click,
and every tab showed the document title, so repeats piled up identical
tabs. Repeats now reuse the snapshot tab, snapshot tabs read like their
source plus the suffix in the tab strip, window title and Combine picker,
and carry a notice bar naming the revision with a Close that asks about
saving first.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* Address review comments on validated appearances and trust refresh

Count every field when checking shared appearance streams, so a
caption shared with an unvalidated field is never rewritten for the
valid one alone.

Pick the icon slot by drawing instead of length, and only when a
single layer qualifies. Bare rectangles never count as the icon, and
ambiguous or missing slots fall back to the overlay badge.

Revalidate and invalidate together through one helper, so trusting a
certificate always drops rendered pages. Trust alone still marks
nothing dirty.

Tests: mixed-verdict sharing, longer decorative borders, competing
and doubled layers, capture-before preservation, trust invalidation
without zooming, and signed Session round trips with rendering,
saving and passwords.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* Prove the icon slot geometrically and cover real layered signatures

A lone line drawing is still not the icon: frames and borders span the
box while the status mark lives in a corner of it. The icon now has to
sit strictly inside the appearance frame and cover at most half of it;
anything else falls back to the overlay badge. Widgets without a
measurable frame veto the rewrite the same way.

Tests: parsed extents and inhabitant checks as units, both reported
border repros plus a frameless widget, and real layered signatures
built with cms::sign_detached through Session open, trust, rendering,
save and reopen, plain and password-protected.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* Identify the icon by shape instead of geometry

A lone line drawing is still not the icon, but size and position were
the wrong signals: the real status mark is large, and geometry blocked
it. Frames, borders and panels are rectangles by nature while the mark
is not, so a drawing made only of axis-aligned rectangles is decoration
however it is stroked. Singularity, shared-stream and template checks
are unchanged; anything ambiguous still falls back to the overlay badge.

Tests: rectangularity as a unit, nested line-drawn frames, and a
triangular fixture mark in place of the rectangular one.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* Budget the appearance walk and keep the marks with the bytes

The walk that finds a signature's icon and caption capped depth,
layers and fields, but not the work: a dictionary whose entries all
point at the next one is visited once per entry per level, so a small
hostile file never finishes opening. The walk now counts its visits
and gives up, and a field whose tree cannot be read to the end stops
the whole pass, because a layer we never read may be the one two
fields share and a rewritten shared layer puts one field's verdict on
another field's page. The sharing count is exact again with that: two
widgets of one field pointing at the same appearance claim it once,
or the field vetoes its own rewrite.

A mark used to outlive the bytes it described. A failed display write
left the page showing the file's own caption while the verdict still
claimed its state was drawn; a metadata edit dropped the mark while
the page kept drawing the state, so the badge landed on top of it. The
write fallback now drops the marks and logs the loss, and metadata
carries them over.

Tests: a self-referential appearance tree, which never finished before
the budget, two widgets sharing one appearance, a metadata edit that
keeps its mark, a save that goes through the writer instead of
short-circuiting on the opened file, and the snapshot bar's Close
button pressed for real.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* Cite the new regressions in parity, restore main's installer note

The appearance entry now cites the bounded walk and the save through
the writer, and says what a tree that cannot be read to the end does.
The installer entry carried main's older text, which still claims
signed installers are planned; main's newer note is taken verbatim.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: preserve rounded decoration beside raster status glyphs

* M9: place appearance progress beside signature history

* M9: retain appearances with ambiguous curved layers

* M9: keep signature verdicts in viewer overlays

Defer generic appearance rewriting: caption text and a unique drawing do not establish an icon role. Preserve original artwork for saves, image and text exports, and both print modes while retaining snapshot reuse and trust refresh. Cover chamfered borders and mixed signer/caption streams through signed Session round trips.

---------

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
2026-10-15 19:16:48 -06:00
FredApps a5ef6b76e8 Wake idle windows for pending recovery snapshots (#2663) 2026-10-16 02:17:12 +02:00