Commit Graph
504 Commits
Author SHA1 Message Date
aiongg a891dfebb2 refactor(automation): split a11y.rs into a11y/
New modules: a11y/ocr, a11y/documents, a11y/form_tools (JS and form-action tools).
2026-10-26 22:18:50 +00:00
aiongg 3370aebb6c refactor(automation): split lib.rs into src/
New modules: error, dispatch, documents, assemble, pages, paths, json.
lib.rs keeps Automation, its builders, mod lines and pub use lines.
2026-10-26 22:18:50 +00:00
aiongg 21c1c917a5 test(automation): skip use lines in the file access scan 2026-10-26 22:18:50 +00:00
aiongg 701988ecd6 refactor(automation): move lib.rs's tests to tests.rs 2026-10-26 20:51:19 +00:00
aiongg c316328bb0 test: scan source directories recursively 2026-10-26 20:51:18 +00:00
skylar fc578a0b02 chore(deps): bump 11 safe dependency updates 2026-10-25 16:36:22 -06:00
58d368f1f0 M10: Word export keeps an italic run inside its sentence's paragraph (#4158) (#4411)
The reading blocks keep style boundaries, so "Before Chevron after." with an
italic case name reached the Office exporters as three blocks, and each writer
emitted one paragraph per block. A block that continues another on the same
line (the test `continuations` already applies to keep such blocks out of
table cells) is now a run in its own size, bold/italic and colour inside the
paragraph of the block it continues, in Word, HTML, RTF and PowerPoint.

- `continued_from` names the block each one continues (nearest end);
  `continuations` is its `is_some`, so table detection is unchanged.
- `paragraphs` groups blocks left to right, joining only a block that starts
  strictly further left, with the same rotation, and never a table cell or the
  slide title.
- Block text is trimmed, so the engine records the source's spaces at its ends
  (`Block::space_around`, from the reading block's editor text); runs keep that
  space, or one where the page shows a gap.
- A paragraph is a heading only if its runs are (a bold lead-in no longer makes
  its line a heading); a smaller footnote marker does not demote a heading.

Style changes that wrap onto the next line still split paragraphs; that needs
the reading-block grouping change and is left for later.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-25 10:11:00 -04:00
mtlcl410andClaude Opus 5.5 e391053658 M4: Document Properties reads a named opening destination's page, so choosing page 1 replaces it (ISO 32000-2 12.3.2.4) (#4574)
* M4: Document Properties reads a named opening destination's page, so choosing page 1 replaces it (#4572)

initial_view resolved only explicit opening destinations, so a named one
(ISO 32000-2 §12.3.2.4) read as page 1 at the default magnification, and
set_initial_view kept it as "unchanged" when page 1 was chosen. Names are
now resolved through the /Names /Dests tree and the catalog /Dests with the
existing bounded resolver, and a bare name or string counts as a destination
when page 1 at the default magnification removes /OpenAction.

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

* M4: parity notes say page 1 at the default magnification removes /OpenAction

Document Properties replaces a named opening destination (ISO 32000-2
§12.3.2.4) with an explicit one when another page or magnification is
chosen; page 1 at the default magnification removes the catalog's
/OpenAction instead (§7.7.2), so the first page opens.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:02:40 -04:00
.CooCoo 0db7d57619 Tests: XFA calculation tests no longer fail when the machine is busy (#4535)
Five XFA tests failed at random on a busy machine. The scripts of an
opening or a change share a 3 s wall-clock budget (a click's share 5 s),
each script has 1 s or 3 s, and the engine has 10 s to start; under load
the tests went past one of them, and the scripts they check were skipped
or did not start.

Session::with_xfa_time_scale (and Automation::with_xfa_time_scale)
multiplies each of these limits for a test; the default stays 1. The
script session counts the engine threads it starts on the calling thread
(xfa_session::engines_started).

The hundreds-of-calculations tests run with the limits raised, and the
engine's test now checks that opening starts one engine and a change one
more. A new test sets the factor to 0 and checks that the budget skips
all 210 calculations and says so. Three more tests that failed the same
way under load run with the limits raised.
2026-10-25 10:02:00 -04:00
Eric Kaden 3f5d8d06fe M9: name a laid-out XFA form's signature fields by their SOM path, so other viewers find the signature (#4507)
* M9: name a laid-out XFA form's signature fields by their SOM path, so other viewers find the signature

* M9: doc comments on the functions the signature naming touches

* M9: say other viewers in the signature naming's comments

* M9: a new layout keeps a signature a generated signature box holds

* M9: the re-layout test checks the signature's picture is kept too

* M9: a signature field named by its SOM path doesn't repeat a name a field already has

* M9: every name a field of the pass takes is known before a signature field is named, in either order

* M9: the signed-box test's doc comment is back on its test

A merge had left it above the next test, which has its own.
2026-10-25 10:01:53 -04:00
b6fedc09f0 M7.5: crop an added image by more than half from one side (#4521)
* M7.5: crop an added image by more than half from one side

Each side of an added image's crop was capped at 45 %, so keeping its right
third (70 % off the left) wasn't possible, and the drawing silently cut a
larger crop down. The limit is now per axis: a side may trim up to 95 % as
long as left and right, and bottom and top, keep at least 5 % together.
AddedImage::crop_valid checks input (content_update refuses the rest with
the rule), clamp_crop brings a crop read from a file into range keeping the
sides' proportion, and the Edit panel lets a side go up to what the opposite
leaves.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Marco Hagspiel <m.hagspiel@lcs-group.com>

* M7.5: accept crops that sum to the limit; keep untouched crop sides exact

`0.05 + 0.9` is just above `0.95` in floating point, so `crop_valid` rejected a crop that
keeps 5 %. Sums now pass within a rounding tolerance, and `clamp_crop` leaves such a crop as
it is. The image panel no longer rounds the stored crop to whole percent or clamps sides the
user doesn't touch: only an edited side is written back.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Marco Hagspiel <m.hagspiel@lcs-group.com>

* M7.5: an excessive crop from a file keeps its proportion

`clamp_crop` capped each side before fitting the pair, so `[1.5, 0, 0.5, 0]` lost its 3:1
proportion. The pair is now scaled first, from its halves so two huge sides can't overflow;
a pair within the limit keeps each side within it.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Marco Hagspiel <m.hagspiel@lcs-group.com>

---------

Signed-off-by: Marco Hagspiel <m.hagspiel@lcs-group.com>
Co-authored-by: Marco Hagspiel <m.hagspiel@lcs-group.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-25 10:01:48 -04:00
af4a307e99 M7: dotted day-first dates in header and footer (#4503)
Header and footer dates had US, ISO and day-first slash forms only. The
dotted forms written in German-speaking and much of Central and Eastern
Europe join them: <<dd.mm.yyyy>>, <<d.m.yyyy>>, <<dd.mm.yy>>, <<d.m.yy>>.
The dialog's date list offers the first three after the existing ones.

Generated with Claude Code

Signed-off-by: Marco Hagspiel <m.hagspiel@lcs-group.com>
Co-authored-by: Marco Hagspiel <m.hagspiel@lcs-group.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-25 10:00:06 -04:00
alehel ad8c2f46dd Merge origin/main into fix/comment-threads-by-irt
Conflicts:
- crates/render/src/inspect.rs: this branch's reply-linking methods and main's NoRotate
  rectangles (#3016) both go in the Annotation impl; the annotation reader takes this
  branch's loop with main's no_rotate field.
- crates/automation/src/comments.rs: this branch's HashMap import, main's import list.
- ATTRIBUTION.toml: each catalogue's source note keeps main's secured-document clause and
  this branch's reply-count clause; ATTRIBUTION.md regenerated (cargo xtask assets --write).
- The 33 catalogues: main's new rows, then this branch's reply-count row.

main's #3016 tests build Annotation field by field; they now set parent and grouped.
2026-10-24 20:42:11 +02:00
cc5fd54868 M6: an editable combo box takes a typed value (ISO 32000-2 Table 233 Edit) (#4451)
A combo box with the Edit flag (bit 19) opened only its list, so a value
that isn't one of its options could not be typed on the page, although
form_fill already took one. ISO 32000-2 12.7.5.4 and Table 233: with Edit
set, "the combo box shall include an editable text box as well as a
drop-down list".

- An editable combo box now opens a single-line text box over the field
  as well as its list. Enter, Tab or a click away commits the text as
  Edit::SetFieldValue with a Choice, the path form_fill and the list use,
  so undo, field scripts and the appearance follow; a pick from the list
  still sets an option.
- The editor starts on the shown text of the picked option, and typing a
  shown text picks that option (the engine maps it to its export value).
- A combo box without Edit is unchanged: a drop-down list only.
- Tests: kittest typing into an editable combo (Enter, list pick, Tab,
  save, undo) and a non-editable one refusing typing; automation
  form_fill takes free text only with Edit, drawn before and after save.
- Parity: form.fill-choice notes and evidence.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-24 10:44:38 -04:00
mtlcl410andClaude Opus 5.5 fc91ab9250 M3: a document opens at its /OpenAction destination, position and named destinations included (ISO 32000-2 Table 149, 12.3.2.4) (#4376)
- render: DocInfo::open_dest resolves /OpenAction (an explicit destination,
  a /GoTo action's /D, or a name or string through /Names /Dests or the
  catalog /Dests) with the existing bounded dest() resolver.
- ui-egui: apply_initial_view goes there with DocView::go_to_dest, so the
  /XYZ point lands at the window's top-left and /FitH top at its top edge.
  With "Always use my default zoom and page layout", only the page.
- organize, ui-egui: /ViewerPreferences /NonFullScreenPageMode (Table 147)
  is read for /PageMode /FullScreen documents and names the panel shown
  behind the full-screen prompt; such a document no longer gets the outline
  panel only because it has an outline.
- organize: set_initial_view keeps an opening action whose page and
  magnification are unchanged, instead of dropping its position or deleting
  a named go-to.
- automation: doc_info reports open_destination.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 10:30:21 -04:00
Gabriel Luís da Silva be0ec70756 fix(automation): roots-only mode refuses other shares and devices by name (#4289)
With only --automation-read-root / --automation-write-root set, a path
went straight to std::fs::canonicalize, so a UNC, WebDAV or device path
(`\host\share\x.pdf`, `\.\pipe\x`) was resolved, contacting the host,
before it was refused. `--root` mode already refuses those by name
(`foreign_share`).

`foreign_share` moves into file_access, and `AutomationRoots::check_path`
applies it to the path made absolute (by name, nothing read) before
following links, with the same refusal as any other path outside the
root. A root on a share still accepts that share. `resolve` is split
into `absolute` (empty path, device names, made absolute) and `resolve`
(links followed), so `out\NUL` keeps its own refusal.

Part of #4003 (the first of its three points).
2026-10-24 10:30:06 -04:00
alehel c16956f539 Merge origin/main into fix/comment-threads-by-irt
main's #4284 stopped hashing translation catalogues in ATTRIBUTION.toml; the
conflict took main's side (no sha256 lines) and kept this branch's source notes.
2026-10-24 11:31:20 +02:00
aiongg f9a179fee0 Merge integration batch B's review fixes 2026-10-24 08:26:10 +00:00
aiongg afa14c3b98 Merge integration batch A's review fixes 2026-10-24 08:26:10 +00:00
aiongg 82eedb6607 Integration: a retained-version optimize reports the later of header and catalog version (#3227)
Object streams raise the header to 1.5 over a lower catalog /Version; pdf_version now reports the version viewers read.
2026-10-24 08:21:14 +00:00
aiongg 1fb1877923 Integration: attachment_add reads its file through the size-capped reader (#1960)
attachment_add used an unbounded std::fs::read; it now uses read_input like the other file-reading tools, and the oversized-input test covers it.
2026-10-24 08:20:39 +00:00
aiongg f053da3d93 Merge PR #1960: Attachments: attach a file and delete one (panel, edit, tools); duplicate names numbered as text
i18n ca, fi, hi, id, ko, ro, sq .tsv: kept both sides' rows (no duplicate keys).
2026-10-24 07:00:24 +00:00
aiongg 464dbd49e0 Integration: #3765 name the case-only comparison in the doc comments (NFC/NFD not compared) 2026-10-24 07:00:10 +00:00
aiongg 23d2e1c823 Merge PR #3765: Refuse an OCR batch whose inputs would write one output file
Cargo.lock: took main's.
crates/automation/src/a11y.rs: kept both sides (PR's helpers, then main's request_json doc comment).
2026-10-24 06:59:56 +00:00
aiongg ce684cd1d3 Merge track r3-t06 2026-10-24 06:50:31 +00:00
aiongg 6a206a0a2e Merge track r3-t05 2026-10-24 06:48:48 +00:00
aiongg 692f34fce2 Merge track r3-t04 2026-10-24 06:40:21 +00:00
aiongg 9705e7a6c1 Merge track r3-t03 2026-10-24 06:39:33 +00:00
aiongg a05dbdcb1e Merge track r3-t02 2026-10-24 06:38:46 +00:00
aiongg aedf339fb8 Merge PR #4181: M11: PDF Optimizer Clean Up stores LZW streams with Flate
Conflicts: ATTRIBUTION.toml: took main's side (hashes recomputed after the merges). crates/optimize/src/lib.rs, crates/automation/src/lib.rs, tools.rs: Settings and tool schema keep object_streams (#3227) and lzw_to_flate (#4181); doc_optimize description names both options. crates/optimize/tests/optimize.rs: both sides' imports and tests kept.
2026-10-24 06:26:33 +00:00
aiongg 1d270206be Merge PR #3227: M11: target PDF version and object streams option in PDF Optimizer
Conflicts: docs/roadmap.md: all status lines kept, M10 row from main, M11 row with #3227's two sentences. parity/acrobat-features.toml: save-with-object-streams entry from #3227 with its test paths under tests/it/.
2026-10-24 06:25:55 +00:00
aiongg 880723e3af Merge PR #4266: M7: added text takes character spacing from Format text
crates/engine/src/lib.rs: union of the added:: re-exports (paste_place/paste_rect from #4230, CHAR_SPACING_LIMIT).
crates/ui-egui/src/content_ui.rs: editor keeps #4178's anchored width and horizontal_align and #4266's spacing layouter; spaced_job takes the alignment so the custom layouter keeps it.
crates/ui-egui/tests/it/content.rs: kept both tests.
2026-10-24 06:24:10 +00:00
aiongg 541c82d59f Merge PR #4318: M12: links in XFA rich text become clickable links
crates/xfa/src/tests.rs: both sides' appended tests kept.
parity/acrobat-features.toml: form.xfa-dynamic evidence and notes: both sides' insertions (word-level merge).
2026-10-24 06:24:04 +00:00
aiongg f017ca3b78 Merge PR #3580: M6: form buttons show the side panels (app.execMenuItem)
crates/js/src/lib.rs: module doc app/util list has execMenuItem and scand; install() doc comment from main.
crates/js/src/tests.rs, crates/engine/src/tests.rs: both sides' appended tests kept.
parity/acrobat-features.toml: js notes: execMenuItem wording plus scand wording.
2026-10-24 06:23:47 +00:00
aiongg f71a305915 Merge PR #4230: M7: paste added text boxes and images in the engine and through content_copy (#2636)
ROADMAP.md, docs/roadmap.md: kept both sides' status and progress-log lines; did not restore the older M14 status line that the branch had already trimmed.
2026-10-24 06:23:24 +00:00
aiongg 581faa876a Merge PR #4349: M5: stamps without an appearance draw the stamp they name (ISO 32000-2 Table 184) 2026-10-24 06:23:15 +00:00
aiongg 8a64b65e4f Merge PR #4330: M8: redact labelled Italian international slash numbers
ROADMAP.md: kept both progress-log lines, newest first by position (HEAD's #4280 line, then this one).
2026-10-24 06:23:03 +00:00
aiongg 8ff6c8af31 Merge PR #2748: M10: a booklet decides two-sided itself, as Acrobat does (#2707)
Conflicts: crates/automation/src/printing.rs: kept job_duplex (#2748) next to bad; #4161's paper_named/spool_job unchanged.
2026-10-24 06:22:34 +00:00
aiongg ee989ab5ca Merge PR #3016: M5: sticky notes stay upright where they are placed on rotated pages (NoRotate, #2583)
Conflicts:
- parity/acrobat-features.toml: comment.note: kept main's icon notes text, added the NoRotate sentence and the PR's three evidence entries.
- vendor/README.md: kept main's rows; added the PR's hayro-interpret patch as (53) with its test (53).
2026-10-24 06:22:25 +00:00
aiongg c24e90b59c Merge PR #3985: M6: a document script that throws no longer stops the form's other scripts (#3940)
crates/automation/src/tools.rs: js_run description: both sides' wording merged.
2026-10-24 06:22:15 +00:00
aiongg 0041a3b10e Merge PR #4161: M10: every paper size reaches the printer, from the dialog and doc_print (#3752)
Conflicts: ROADMAP.md: kept both progress-log lines.
2026-10-24 06:22:15 +00:00
aiongg 19b36de16f Merge PR #3997: fix(automation): guard MCP dispatch and bound line and file reads (#3924) 2026-10-24 06:22:01 +00:00
aiongg 01dc49c13c Merge PR #4053: fix: Issue #4018, form_fill warns when a value does not fit its field (#4018)
crates/forms/src/tests.rs: append conflict, kept both tests.
2026-10-24 06:21:41 +00:00
aiongg 4fedfe7e8c Merge PR #2648: fix(xfa): skip PDF fallback scripts while XFA layout is active
crates/engine/src/js.rs: kept main's DocInfo.xfa field and multi-line layout, added the PR's xfa_active gate on doc_scripts.
2026-10-24 06:21:20 +00:00
aiongg fa3a0221d9 Merge PR #3000: fix(edit): batch styled paragraph fallback glyphs 2026-10-24 06:21:12 +00:00
aiongg ba85d2af66 Merge PR #2957: M6: dotted field names make parent fields (#2696) 2026-10-24 06:21:05 +00:00
a047c6ba09 chore: resolve SonarCloud maintainability issues (#4356)
* chore: resolve SonarCloud maintainability issues

* fix(packaging): use verify_sha256 helper function

* style: cargo fmt

* fix: restore wildcard imports and fix method-reference type errors

Revert the glob-import expansions (they missed SomIndices and left unused
imports), revert char::is_ascii_digit where the iterator yields char by
value, and pass the content slice to prune_doomed_xobjects as &[Vec<u8>].

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

* Keep XObject decodability check without binding content in interp

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
2026-10-23 23:28:18 -04:00
mtlcl410andClaude Opus 5.5 184e9f30ff M10: a booklet decides two-sided itself, as Acrobat does (#2707)
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>
2026-10-24 09:27:26 +08:00
mtlcl410andClaude Opus 5.5 3db28cc4e3 M5: stamps without an appearance draw the stamp they name (ISO 32000-2 Table 184)
A rubber stamp from another program with no normal appearance was not
drawn at all. The display copy now gives it one:

- the fourteen standard names of Table 184 draw that stamp with
  PdfCraft's stamp drawing (eight from the palette, six with labels of
  their own), fitted to /Rect, in /C or the stamp's colour;
- no /Name draws Draft, the table's default, unless /IT says the stamp
  is an image or a snapshot;
- any other name (names match byte for byte, §7.3.5) draws a thin grey
  frame with both diagonals, so the stamp stays visible and selectable.

The drawing is display-only, like the other comments without an
appearance: Save writes the file as it was, and build() still declines
these stamps, so restyling and flattening never write it into the file.
A stamp with its own /AP /N is never touched. stamp() now takes the
label and the sign-here flag instead of a StampKind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
2026-10-24 09:15:12 +08:00
nlevaandOpus 5.5 medium ultracode 5515f321c8 M7: copy added JPEG 2000 images as their file (#2636)
Review: copying an added JPEG 2000 image failed even within its own
document, because the exporter can't write JPEG 2000 yet. Add Image stores
such a picture as the file it was given, so that file is the copy: it
pastes into the same document as the same object and into another as one
embedded image. The notes now also say that a CMYK picture stored
losslessly turns to RGB in another document.

Co-Authored-By: Opus 5.5 medium ultracode <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEgu9NcTcPqKovsLyfPxK
2026-10-24 01:17:34 +02:00