Commit Graph
217 Commits
Author SHA1 Message Date
Eric Kaden 1c4a481ea3 M12: round XFA check buttons draw a round background and border 2026-10-23 16:19:43 -04:00
f46268fbe4 M9: visible signatures read upright on rotated pages (#4067)
* M9: visible signatures read upright on rotated pages

A visible digital signature placed on a page with /Rotate was drawn in
user space, so its graphic and details turned with the page: on scans
stored landscape with /Rotate 270 the signature came out sideways.
Stamps (#1398) and Fill & Sign marks (#2392) were already corrected;
signatures were not.

A new signature widget on a turned page gets /MK /R equal to the page
rotation (inherited rotation included, via pdfcraft_model::pages), and
its appearance is laid out in the box as displayed (sides swapped for a
quarter turn) with a /Matrix from pdfcraft_model::view_matrix_for that
maps it exactly onto /Rect. Signing an existing field follows that
field's own /MK /R, so a field without one is drawn as before. Pages
that are not turned produce the same bytes as before.

Tests: sign crate (layout equals the upright box's, /MK /R, /Matrix,
inherited /Rotate, existing fields with and without /MK /R, malformed
/MK /R) and an engine render test on 0/90/180/270 pages with the
quadrant image. Both fail with the fix reverted.

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

* Keep one evidence/notes pair in the sign.digital parity entry

---------

Co-authored-by: vjeko2404 <vjekoslav@outlook.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 10:43:54 -04:00
Jayesh Prajapati 8af35de464 M5: stamp_custom places a picture fitted inside a rect (#4077) (#4233)
* M5: stamp_custom places a picture fitted inside a rect

stamp_custom only centred a PDF page or image at its natural size, at
most 200 pt, so a vector diagram could not be placed at a text column's
width. It now takes rect [x0, y0, x1, y1] (displayed-page points, like
at) as an alternative to at: the picture is scaled to fit inside the
box, keeping its shape, and centred in it. A PDF page stays a form
XObject, so it remains vector.

- Engine: Edit::AddCustomStamp fits the picture inside a non-point rect
  instead of stretching it (fit_picture). A box already of the
  picture's shape is kept exactly, so signatures and other callers that
  size the box themselves are unchanged. The displayed sides are
  swapped on quarter-turned pages, as for the natural size.
- Automation: rect is validated (four finite numbers, each side
  at least 1 pt and at most 14400 pt, the largest PDF page; not clipped
  to the page, like at); passing both at and rect,
  or neither, is refused. path still goes through the read root.
- Tool schema and description, parity note and evidence.

Fixes #4077

* M14: update ctutils to 0.4.5 for RUSTSEC-2026-0385

The dependency-advisory gate rejects ctutils 0.4.2 (non-constant-time
mask generation and u128 comparisons, fixed in 0.4.4). It comes in
through crypto-bigint for the signing crates. Only its version and
checksum change in Cargo.lock.

(cherry picked from commit 8a687ee2c6012e3d18399633d2fb9fb3742f02ae)
2026-10-23 10:42:20 -04:00
Jayesh Prajapati 6765ae0b5c M11: Convert to PDF/A needs the owner's permission on an encrypted document (#3921) (#4203)
Save as PDF/A removes the encryption (PDF/A forbids it), but it was gated
only by the modify permission, so a document opened with the user password
whose owner withheld printing or copying could be saved unencrypted with
every restriction gone. It now needs the same rights as Protect and Remove
security, also inside a batch.

Fixes #3921
2026-10-23 10:42:14 -04:00
Eric Kaden 8f292c66ae M6: scripts see xfa_version, and xfa_installed in XFA forms (#4142) 2026-10-23 10:19:09 -04:00
aiongg 35b5abd5b3 test(engine): show the truncated-page picture exports within a sufficient budget
The fixture's image stream held six bytes under /Length 4, and the test
only checked that the picture was missing at a zero budget, which a
picture that could not be prepared also passes. The stream is four
bytes, and the picture is first shown to export at a 1 MiB budget.
2026-10-22 21:42:02 +00:00
aiongg aa1c13f71c fix(engine): list a page truncated for both forms and pictures once 2026-10-22 20:56:02 +00:00
aiongg 923e499597 Merge upstream/main into fix/coderabbit-c5-forms-misc
Conflict in crates/content/src/lib.rs: main ends inline images where the
renderer's tokenizer ends them and counts the images whose end is a guess
(Parsed::ambiguous_images); this branch reads operators through the
streaming Ops iterator that parse collects. The iterator's BI branch takes
main's inline_image result and keeps the ambiguous-image count, which parse
copies into Parsed and Ops::ambiguous_images reports.
2026-10-22 20:51:14 +00:00
aiongg b72078871a fix(content): end inline images where the renderer ends them
Filtered inline image data has no length, so its end is an EI chosen
among candidates, and the content parser chose by a different rule
than the renderer's tokenizer (hayro-syntax): the renderer also takes an
EI with no whitespace before it, and checks a candidate by reading what
follows as operators. With image data such as "7FEI 1 0 0 1 200 0 cm",
the renderer ran a cm that redaction read as image data, so text was
drawn 200 pt from where removal and its verify pass looked.

The parser now takes each inline image's end from the renderer's
tokenizer (already in the tree through pdfcraft-cos), so both read the
same operators after the image; a test runs both on the same fixtures.
Only where that tokenizer reads no image (no EI it accepts, or
parameters it can't read) does the parser fall back to its own rules,
counting the image in Parsed::ambiguous_images, and applying redactions
refuses such a page as unreadable.
2026-10-22 15:17:44 -04:00
aiongg 22aa6d5f9a fix(redact): place glyphs with the inherited and ExtGState-set text state
The redaction interpreter started every form XObject with an empty text
state (no font, size 0, no spacing) although a form inherits its caller's
graphics state, and it ignored the /Font entry of an ExtGState set by gs.
Text shown that way was placed at the wrong positions, so removal and
the verify pass could both miss glyphs under a mark. Forms now start
from the caller's state, and gs sets the font and size as Tf does.

The engine's renderer cross-check test used the gs case as its example
of interpreter/renderer disagreement; it now runs the check directly on
an unredacted page, which also covers an input stream labelled as a
redaction overlay.
2026-10-22 15:17:44 -04:00
aiongg 6ca4431b50 fix(redact): check every input content stream, whatever it calls itself
Both checks for text left under applied redaction marks (redaction's own
verify pass and the engine's renderer cross-check) skipped any page
content stream whose dictionary said /PCMark /Redaction, so that the
overlay's label would not count. An input file could put that label on
its own content stream and keep it out of both checks. Applying
redactions now records the overlay streams it adds (stamp returns the
new stream), and only those are skipped.
2026-10-22 15:17:44 -04:00
aiongg 467c8556ec fix(engine): encode each image once for Office export
Office export encoded and kept a separate copy of an image for every place
a page drew it, so a short content stream repeating Do on one large image
multiplied it into gigabytes before the writers' limits applied. Each image
object is now encoded once and its bytes shared (pdfcraft_export::Image
holds an Arc<[u8]>), distinct pictures share a 512 MiB budget whose
overflow lists the page as truncated, and RTF, which leaves pictures out,
prepares none.
2026-10-22 18:02:33 +00:00
aiongg 77480498b2 fix(engine): low-resolution printing prints only the chosen content
Pages printed as pictures were drawn with everything on them, so form fields only
printed the page content, and the document with stamps printed every comment. The
pictures are drawn from a copy holding only what the vector print draws.
2026-10-22 13:18:26 -04:00
aiongg 73f11b2a29 fix(engine): fail an action whose information-removal step fails
A step that applies an edit logged any error and carried on, which is right
for a flatten with nothing to flatten but not for removing hidden
information or sanitizing: on a signed file the removal was refused, and the
unchanged file was written out as the action's successful result. Those
steps now fail the action unless there was nothing to remove.
2026-10-22 13:18:26 -04:00
aiongg 28ab616eb6 fix(engine): print as low-resolution pictures when only that is allowed
Printing checked the print permission but not the separate high-quality one
(Table 22, bit 12), so a document limited to low-resolution printing came out
as its original vector pages, text included. Such a document now always
prints as pictures of at most 150 dpi, whatever the settings ask for.
2026-10-22 13:18:26 -04:00
aiongg 30300ae71b fix(engine): compare only documents that allow copying their text
Compare read both documents' words out with no check of the copying
permission, and its result, report and difference comments carry that text,
so a copy-restricted document's text could be taken out through a comparison.
Both documents must now allow copying.
2026-10-22 13:18:26 -04:00
aiongg 2c13fd9373 Merge r2 track t16: mixed: automation-mcp, crate:misc, crypt-security, export-convert, optimize, preflight-pdfa, save-io
# Conflicts:
#	ATTRIBUTION.toml
#	contributors/people.toml
#	crates/automation/src/comments.rs
#	crates/edit/src/tests.rs
#	crates/engine/src/tests.rs
#	parity/acrobat-features.toml
2026-10-22 02:23:58 +00:00
aiongg dcf2802f96 Merge r2 track t15: mixed: accessibility-tags, dialogs-prefs, javascript, organize-pages
# Conflicts:
#	crates/edit/src/tests.rs
#	crates/engine/src/tests.rs
2026-10-22 02:23:22 +00:00
aiongg 123b214180 Merge r2 track t10: xfa 2026-10-22 02:21:06 +00:00
aiongg 2363b0619b Merge r2 track t09: print
# Conflicts:
#	ATTRIBUTION.toml
#	crates/ui-egui/src/lib.rs
2026-10-22 02:21:06 +00:00
aiongg a2c46a9298 Merge r2 track t06: annotations-comments
# Conflicts:
#	crates/automation/tests/automation.rs
#	crates/render/src/raster.rs
2026-10-22 02:19:23 +00:00
aiongg 8724f6b1a5 Merge r2 track t05: forms
# Conflicts:
#	crates/engine/src/tests.rs
#	crates/ui-egui/src/canvas.rs
#	crates/ui-egui/src/forms_ui.rs
#	docs/ui-parity.md
2026-10-22 02:18:46 +00:00
aiongg 2bd66fbe90 Merge r2 track t03: viewer-ui
# Conflicts:
#	crates/automation/tests/automation.rs
#	crates/engine/src/tests.rs
#	crates/ui-egui/src/content_ui.rs
#	crates/ui-egui/src/edit_text_ui.rs
#	crates/ui-egui/src/fill_sign.rs
#	crates/ui-egui/src/forms_ui.rs
#	crates/ui-egui/tests/editing.rs
2026-10-22 02:18:22 +00:00
aiongg da7f592cd0 Merge PR #1648: XFA: decode BMP and TIFF pictures
crates/xfa/src/tests.rs: union of fixture imports (tiny_bmp, tiny_tiff).
parity/acrobat-features.toml: form.xfa and the XFA image entry: evidence lists unioned, notes take both sides' insertions.
2026-10-22 01:49:05 +00:00
aiongg 4949724ec8 Merge PR #3875: M4: a new bookmark goes to where the reader is (#3865) 2026-10-22 01:48:54 +00:00
aiongg a447dd3db5 Merge PR #2731: Layers panel: Reset to Initial Visibility
Conflicts:
- ATTRIBUTION.toml: ours (sha256 lines only); hashes recomputed at the end of the track
- crates/engine/Cargo.toml: main's workspace chrono entry, plus the PR's pdfcraft-testkit dev-dependency
2026-10-22 01:48:51 +00:00
aiongg be2b7ae411 Merge PR #3452: fix(engine): a reload keeps reading colours and hidden comments (#3443) 2026-10-22 01:48:35 +00:00
aiongg a663f2e9c8 Merge PR #2825: fix(cli): info, text, render and audit take the owner password of R2–R4 files 2026-10-22 01:48:29 +00:00
aiongg 9865f27d8c Merge PR #2619: fix(xfa): run click scripts for every repeated print button
ROADMAP.md, docs/roadmap.md: both status lines and progress-log lines kept; M12 row takes both 'Done since' insertions.
crates/automation/tests/automation.rs, crates/xfa/src/fixtures.rs: union of #2624's and #2619's tests and fixtures.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
2026-10-22 01:48:18 +00:00
aiongg ca2e7f8ffd Merge PR #2840: M6: detected form fields get no border or fill
crates/engine/src/js.rs: auto_detect_fields keeps main's comb and turn handling, builds props with no border and no fill for every field, and pushes SetFieldProps unconditionally (the review's fix)
parity/acrobat-features.toml: form.detect-fields keeps main's comb evidence and notes and adds the no-border test and sentence
2026-10-22 01:47:29 +00:00
aiongg 844e35781a Integration: #2624 doc comment of xfa_scripts_initialize_calculate_validate_toggle_and_add_rows back on its test
The #2624 merge inserted its engine tests between #3782's doc comment and the test it documents.
2026-10-22 01:47:22 +00:00
aiongg a0a9e3370e Merge PR #3408: M12: an XFA script that fails partway keeps what it changed before the error
crates/engine/src/tests.rs: union of #2624's tests and #3408's test.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
2026-10-22 01:47:22 +00:00
aiongg 3ba586fe31 Merge PR #2624: fix(xfa): reflow growable multiline fields after filling
crates/xfa/src/script.rs: template_field and named_in moved to crate::data in #3397; field_grows_with_text calls crate::data::template_field.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
2026-10-22 01:46:50 +00:00
aiongg 8949d34632 Merge PR #2711: Wake idle windows for pending recovery snapshots (#2663) 2026-10-22 01:46:43 +00:00
aiongg 3f9b41246a Merge PR #1829: M10: keep dotted-leader contents entries together
ROADMAP.md: progress-log line added at the top
docs/roadmap.md: status line, M10 row note and revision row applied onto main's
parity/acrobat-features.toml: export-docx evidence and notes insertions applied onto main's
crates/edit/src/tests.rs, crates/engine/src/tests.rs: new tests appended after main's
2026-10-22 01:46:30 +00:00
aiongg 5a13228b57 Merge PR #3520: M5: a stamp grows and shrinks with its handles before release (#3518) 2026-10-22 01:46:28 +00:00
aiongg 38fdeb3f22 Merge PR #2724: fix(print): disable Print on documents whose security forbids printing
ATTRIBUTION.toml: kept main's sha256 lines; hashes recomputed after the track.
crates/ui-egui/tests/print.rs: kept both sides' tests.
2026-10-22 01:46:18 +00:00
aiongg 0b1cce39bc Merge PR #3781: Fill & Sign marks stay upright on rotated pages (#2392) 2026-10-22 01:46:17 +00:00
aiongg dbc2647f48 Merge PR #3397: M12: XFA dates are written and read in the field's data picture
crates/engine/src/tests.rs: union of #3180's new test and #3397's doc comment on the next test.
crates/xfa/src/layout.rs: kept #3782's formatted_value (it calls date_pattern, now built on date_letters) and button_action doc.
crates/xfa/src/parse.rs: parse_field doc comment from #3397 (names the data picture).
crates/xfa/src/script.rs: field() doc from #3582 with #3397's "as scripts read it".
crates/xfa/src/tests.rs: union of fixture imports and of the new tests; one doc comment each for the date-conversion and data-fill tests.
2026-10-22 01:45:58 +00:00
aiongg 8cdfe0f4e8 Merge PR #3475: fix(forms): draw fields the file leaves to the viewer, for display only (#3470) 2026-10-22 01:45:38 +00:00
aiongg 533c59cbe3 Merge PR #2994: M10: Export to Word keeps the page background colour (#2992) 2026-10-22 01:45:26 +00:00
aiongg 54ced90a7e Merge PR #3357: fix(a11y): join and splice page content streams with cross-stream rules (#3217) 2026-10-22 01:45:24 +00:00
aiongg 6e2a449bf7 Merge PR #3318: M6: Optimize AcroForm JavaScript snapshots and diffs
parity/acrobat-features.toml: form calculation order entry keeps main's commands, tools and notes; evidence is the union of both sides.
2026-10-22 01:44:59 +00:00
aiongg 9cb8f52294 Merge PR #3782: M6: XFA formattedValue shows a date in the field's display picture 2026-10-22 01:44:53 +00:00
aiongg 5d7b9a98b3 Merge PR #3192: fix(forms): an XFA form whose config forbids dynamic rendering is static (#3180) 2026-10-22 01:44:51 +00:00
jonathanwxh-cell 895ccb90c3 fix(engine): guard undo and redo refresh and roll back on failure (#3925) 2026-10-22 08:28:37 +08:00
e451934f72 M6: calculation order: read, check, fix and set the order fields calculate in (#3708)
* M6: renaming a field renames it in the calculations that read it

A rename silently broke every formula that used the field: a sum dropped
it, notation read it as 0 and a script kept a stale value. set_props now
rewrites the name in each field's Calculate script, in the same edit:
AFSimple_Calculate field lists, simplified field notation (the BVCALC
comment and its translation) and getField calls naming it in one string
literal. The rest of each script is kept as written; names built at run
time, non-UTF-8 script streams and a name another field still has are
left alone, and a self-check keeps a script whose meaning would change.

Notation is read by one lexer shared by notation_js, eval_notation and
the rename. form_set_props returns formulas_updated; Field Properties
says how many calculations now use the new name.

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

* M6: form_set_props leaves the renamed field out of formulas_updated by object

The renamed field was matched by the name given, but set_props stores it trimmed and under
its parent's prefix. When the same call changed the field's own formula, formulas_updated
listed the field as one the rename had rewritten.

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

* M6: calculation order: read, check, fix and set the order fields calculate in

A form's calculations run in its calculation order (/CO), which a field
joins when it gets a calculation, so a chain defined in the wrong order
shows stale values. calculation_order reads the order against the fields,
with what each formula reads (field lists, notation names, literal getField
names) and what is wrong: a field reading one that calculates later, a loop
(strongly connected components, found without recursion), a field /CO
leaves out, and /CO entries that calculate nothing. Fix order is a stable
topological sort that moves only what must move and leaves loops alone.
set_calculation_order writes /CO afresh; a name two fields share (a
malformed form) stands for the next of them in the current order.

Edit::SetCalculationOrder sets it in one undo step and recalculates, and
form_calculation_order reads, sets, moves one field or fixes the order
headlessly. An edited formula now keeps its field's place in /CO, a field
/CO lists twice calculates once, and recalculation runs each field by
object, so two fields sharing a name both calculate. The /CO code of the
Calculate tab, field scripts and recalculation goes through one place.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
2026-10-21 19:56:27 -04:00
Adrian 946a77cf9b M4: a new bookmark goes to where the reader is (#3865)
A bookmark made in the app always went to the top of the page. It now goes to
the top-left of the selected text, or with nothing selected to the top of the
window, as /XYZ left top null: the reader's zoom is kept, and with only the top
pinned so is the horizontal position.

- render: PageInfo::xyz_for_view, the inverse of dest_fraction. It pins only
  the coordinates given; on a page turned a quarter the displayed y decides the
  user left and the displayed x the user top.
- organize: add_bookmark_at and OutlineError::BadPosition (a coordinate that is
  not finite is refused before anything changes).
- engine: Edit::AddBookmark takes left and top (None, None is the page top, as
  before).
- automation: bookmark_add takes top and left in points from the top-left of the
  displayed page, as bookmark_list reports them, bounded and refused when not a
  usable number.
- ui: DocView::bookmark_position. It follows the window's vertical when the view
  is turned a quarter (View > Rotate View), picks the selection's corner that
  shows at the top-left, and in a spread of unequal pages uses the page that
  crosses the window's top edge.
- parity: organize.bookmark-add evidence and what is still missing.
2026-10-21 12:12:03 -04:00
Adrian a0c106268c Fill & Sign marks stay upright on rotated pages (#2392)
A checkmark, cross, dot or line placed on a page with /Rotate was drawn
in the page's own axes, so on a page turned 180 degrees the checkmark
came out upside down, and on a quarter turn it lay on its side.

Marks now record the page rotation (PCStampRotate, as rubber stamps do)
and their appearance is drawn upright in the displayed box and mapped
into /Rect, sharing the stamps' code for it. The mark's box is sized in
displayed axes (FillMark::rect_at), used by both the Fill & Sign tool and
fill_sign_add, so a line runs across the page as displayed.
2026-10-20 23:43:07 -04:00
Eric Kaden 6a8ea287aa M6: doc comments on the functions the formattedValue change touches 2026-10-20 23:37:21 -04:00