Commit Graph

24 Commits

Author SHA1 Message Date
Brandon Thomas 01f4937dac Layering: the render-based rotated-field test moves from forms to engine
forms (L3) gained a dev-dependency on render (L3) with #516, which
`xtask layers` refuses, so main CI failed. The one test that renders a
rotated field now lives in engine's tests, which may depend on both;
the parity evidence points there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:08:46 -04:00
Márk Bartalos c9d0619226
Forms: rotate field widgets with /MK /R (#516)
* M6: rotate form field widgets with /MK /R

Filling or restyling a rotated field redrew its text upright. Appearances now follow /MK /R, and Field Properties and form_set_props can set the rotation.

* Forms: a rotation refused as too small is checked before any other property is written; no indexing by widget number

* hu, uk: Rotation:

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 13:13:12 -04:00
Chen Tao 0375bd1e28
M6.2: form fields in Unicode CID fonts show Japanese instead of "?" (#535)
* M6.2: form fields in Unicode CID fonts show Japanese instead of "?"

Japanese forms often give their fields a /DA font that /DR defines as a Type0
font with a predefined Unicode CMap, such as a non-embedded HeiseiMin-W3 with
/UniJIS-UCS2-H. Field appearances accepted only simple fonts, so they fell back
to Helvetica and WinAnsi, and every Japanese character was drawn as "?".

Appearances now use such fonts and encode the text in the CMap's form (UCS-2,
UTF-16, UTF-8 or UTF-32), measuring full-width characters as one em so centring,
auto size, comb cells and multiline wrapping fit. Identity-H and embedded CMaps
still fall back to Helvetica.

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

* M6.2: vertical Unicode CMaps fall back to Helvetica, since field appearances are horizontal

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 12:59:33 -04:00
Chen Tao f78f74164c
M6.2: check boxes with non-UTF-8 state names show their mark when clicked (#536)
Japanese forms often name a check box's on state in Shift-JIS (「はい」 is
/#82#CD#82#A2). State names were read with from_utf8_lossy, so ticking such a
box wrote a different name to /AS and /V, no appearance matched, and the box
was drawn with no mark (or not at all).

Names now map to text losslessly (forms::name_text / name_bytes: UTF-8 as
itself, other bytes and `#` as #XX) wherever states and values are read and
written: /V, /DV and /AS, PdfCraft's own state appearances, and FDF import and
export, so such values also survive an FDF or XFDF round trip.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 12:58:38 -04:00
martinjohuber d9047d4d5b
Forms: radio groups and check boxes accept their /Opt export values (#532)
pdf-lib and other writers give radio groups and check boxes an /Opt array
(PDF 1.4, one export value per widget) and name the on states by position
(/0, /1 ...). Filling such a field by its export value ("Pick-up") failed with
"has no option", and form_fields listed the options as 0, 1.

- Field::export_of / state_for / export_for_state map between a widget, its
  /Opt export value and its on-state name; fields without /Opt are unchanged
- set_value accepts an export value for radio groups and check boxes; the
  "has no option" error lists the export values
- form_fields reports a radio group's options and selected value as export
  values
- Test: opt_export_values_select_button_states

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:19:36 -04:00
pauliusiv123-rgb 22752dc915
Run set-layer-visibility actions from links and push buttons (#292)
* Layers: run set-layer-visibility actions, keeping radio-button groups

The engine can apply a set-layer-visibility action (SetOCGState,
ISO 32000-2 §12.6.4.13) to a document's layers: each ON, OFF or Toggle
in order, naming the layer by its optional content group, so a toggle
sees the state the changes before it left. With /PreserveRB (the
default), a layer turned on turns off the other layers of its
radio-button groups, now read from /OCProperties /D /RBGroups; turning
one off leaves them alone. The renderer is rebuilt once per action, not
once per layer.

Like the Layers panel, this changes the view, not the document.

Tests: engine set_layer_actions_toggle_in_order_and_keep_radio_groups
and set_layer_actions_change_what_renders (pixels before and after),
render radio_button_layer_groups_are_read.

* Links: run set-layer-visibility actions

Clicking a link whose action is SetOCGState showed "SetOCGState actions
run in the JavaScript engine (M6)" and did nothing. Links now read the
action (/State, also when indirect, and /PreserveRB) and clicking runs
it; the Layers panel follows. Hovering shows "Set layer visibility"
(translated in the ja, es, zh-hans and zh-hant catalogs), and doc_info
describes the action in its link list.

Tests: render links_read_set_layer_actions, automation
doc_info_describes_set_layer_links and ui-egui
links_set_layer_visibility.

* Forms: push buttons run set-layer-visibility actions

A push button whose mouse-up action is SetOCGState counted as having no
action, so clicking it did nothing. Buttons now read the action as
links do, and clicking runs it.

Tests: forms push_buttons_read_set_layer_actions and ui-egui
push_buttons_set_layer_visibility.
parity: edit.action-set-ocg-state is partial. The notes say what is
missing: creating the action, bookmarks and other triggers, /Next
chains, JavaScript ocg.state, and the Layers panel keeping radio-button
groups itself.

* French: Set layer visibility

The French catalog is coverage-checked since #291.

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-08 11:27:10 -04:00
pauliusiv123-rgb d7b93b56b5
Forms: run show/hide-a-field button actions (#192)
* Forms: push buttons run their show/hide-a-field action

A push button whose Mouse Up action is a Hide action (ISO 32000-2
§12.6.4.11) did nothing when clicked: the action wasn't read, so the
button counted as having no action at all.

Buttons now read it: /T as a field name, a reference to a field or one
of its widgets (named by its fully qualified name, following /Parent
with a cycle guard), or an array of these; /H false shows instead of
hiding. Clicking hides or shows the named fields and their kids through
the same edit that a script setting field.display uses, so it is one
undo step.

A hidden widget (Hidden or NoView flag) no longer takes clicks, tab
stops, hover frames or field highlights. That also covers fields that a
script hides.

Tests: forms push_buttons_read_hide_actions (names, references, arrays,
/H, a /Parent cycle) and ui-egui hide_actions_hide_and_show_fields.
parity: edit.action-show-hide-field is shipped.

* Forms: push buttons with an action show the pointing hand

Hovering a push button showed the "not allowed" cursor, although
clicking it runs its action. Buttons that aren't read-only and have an
action now show the pointing hand; read-only buttons and buttons with
no action keep the "not allowed" cursor.

Test: hide_actions_hide_and_show_fields checks the cursor (it fails
without this change).

* Clippy: needless borrow in the hide-action test

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-07 21:37:39 -04:00
Brandon Thomas d3db99818f
Rename PrintCraft to PdfCraft (#174)
The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now
PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the
PrintCraft settings and crash-recovery folders to the new name, and both apps read settings
saved under the old storage key, so upgrades keep recent files, preferences and unsaved work.
The signing test fixtures keep their PrintCraft subject names (they are binary).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 16:03:57 -04:00
bflatastic cdb3f3d106 Forms: check box and radio button styles (#94)
Field Properties > Options > Check Box Style (Button Style for radio
buttons): Check, Circle, Cross, Diamond, Square or Star, as Acrobat
offers. The choice is stored in each widget's /MK /CA with Acrobat's
ZapfDingbats codes (4 l 8 u n H), so other viewers draw the same mark,
and PrintCraft draws it as paths, needing no symbol font. Styles set
by other apps are read back too; the defaults stay a check for check
boxes and a circle for radio buttons.

- forms: CheckStyle, check_style(), FieldProps::check_style; set_props
  writes /MK /CA (resolving an indirect /MK so its colours survive) and
  redraws; text and choice fields refuse a style.
- engine: Document::field_check_style.
- automation: form_set_props takes check_style, form_fields reports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:42:39 +07:00
Justin John e59c9a793e
Fix text editing layout and preserve source PDF font styles (#36)
* Comments: a text box's rectangle follows its edited text

Editing a text box (or typewriter or callout) rewrote /Contents but
kept the old /Rect: the appearance clipped the new text to the old box
and silently dropped lines that no longer fit, while shorter text left
an oversized box. set_contents now re-measures the text with the same
Helvetica widths as at creation and re-fits the box: the wrap width and
top edge stay, the height fits the wrapped lines. A callout's leader
line keeps its place and re-attaches to the moved box (that geometry
moved from set_rect into a shared apply_text_box). A locked box keeps
its text editable, so it re-fits too.

Covered by annot tests (grow and shrink with nothing clipped, callout
leader re-attach, locked box) and a comment_edit round trip through the
automation tools; parity entries cite them.

Co-Authored-By: GLM <noreply@z.ai>

* Polish edit text and images tool

* feat(edit-text): select source font style

* fix(edit-text): preserve detected PDF font styles

* feat(edit-text): support Japanese replacements

* fix(forms): escape non-ASCII format script strings

* Forms: unwrap_or instead of map_or with identity (clippy map_or_identity)

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: GLM <noreply@z.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:48:23 -04:00
Brandon Thomas 15f6fa7d4c Never crash: forms scripts can't overflow the stack or the date arithmetic
Two crashes that a PDF can trigger, each with a regression test that failed before this change:
- Deeply nested arguments in a field's format script (`[[[[…` or `new Array(new Array(…`) and
  deeply nested simplified field notation (`((((…` or `----…1`) recursed until the stack
  overflowed, which aborts the whole app. Nesting is now capped at 64 levels; deeper input is
  rejected like any other unparseable script.
- A date with a huge year (`1/1/2147483647`) overflowed `weekday`, and a `DateTime` with month 0
  indexed before the month names. The weekday math is now done in i64, years beyond i32 don't
  parse, and month and day names are looked up with `get`.

Also removes the crate's remaining `expect`/`unreachable!` calls and denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:06:23 -04:00
Brandon Thomas aa194686f1 M6: Prepare a form detects fields (underscore blanks, rules, boxes, check squares) and names them from their labels; form.detect, form_detect_fields
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 05:21:01 -04:00
Brandon Thomas 620ef4fcad M6: Field Properties ▸ Actions tab (six triggers, JavaScript/link/reset/menu/page/show-hide/submit actions); form_actions, form_set_actions tools; signing UI tests get their own temp folders
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 05:08:28 -04:00
Brandon Thomas 06f8d73ec7 M6: Acrobat JavaScript: sandboxed boa engine with the forms object model (printcraft-js); custom keystroke/validate/calculate/format scripts, button scripts, document JavaScripts, JavaScript console (⌘J), Preferences ▸ Enable JavaScript; js_run, js_document_scripts, js_set_document_script, form_set_script, js_enabled tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 04:41:47 -04:00
Brandon Thomas d5c659812c M6: Image fields: Prepare a form ▸ Image field places an icon-only button that asks for a picture when clicked (buttonImportIcon recognised natively); form_set_image
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 05:16:42 -04:00
Brandon Thomas c0f9c73ae9 M6: Prepare a form ▸ Preview (fill the form as a reader would; Edit fields goes back) and Field Properties ▸ Locked (annotation Locked flag; properties greyed out until unlocked, values still fillable); form_set_props takes locked
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 04:28:19 -04:00
Brandon Thomas aac0db461d M6: Duplicate a field across pages (right-click ▸ Duplicate…, all pages or a range): widgets of the same field share one value; a field that is its own widget is split into a field and a widget kid. Prepare mode gets a field context menu (Properties, Duplicate, Delete)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 04:20:26 -04:00
Brandon Thomas d97711302c M6: Order tabs manually: the Fields panel lists fields in tab order and, while preparing a form, moves a field earlier or later on its page (widgets reordered in /Annots, other annotations keep their places, page switches to annotation order); form_tab_order takes field + move
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 03:37:13 -04:00
Brandon Thomas dfae15ec67 M6: Field Properties ▸ Options for every field type: text alignment, default value, scroll long text, rich text, password, file selection, spell check, comb of characters (with its rules); choice defaults, sort items (the list is reordered), custom text, multiple selection, commit immediately; check box / radio checked by default and radios in unison. Reset form restores defaults. form_set_props takes align, default and flags
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 03:28:44 -04:00
Brandon Thomas 92d37ca696 M6.6/M6.8: Field Properties Appearance tab (border/fill colour, thickness, solid/dashed/beveled/inset/underline, text colour, Helvetica/Times/Courier); tab order by rows/columns/structure (page /Tabs) honoured by Tab; push buttons run Reset form, Named (print, page navigation), URI, Go to and common one-line scripts (this.print, this.resetForm, app.alert, pageNum) without a JS engine; form_tab_order tool and appearance in form_set_props (67 tools)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 01:15:02 -04:00
Brandon Thomas 24f5768be4 M6.3/M6.6: Form formats, validation and calculation without a JavaScript engine: Acrobat's AF calls (AFNumber/AFPercent/AFDate/AFTime/AFSpecial format and keystroke, AFRange_Validate, AFSimple_Calculate, simplified field notation) recognised and run natively in Acrobat's event order (keystroke on commit, validate with Acrobat's messages, calculate in /CO order, format for display); Field Properties Format/Validate/Calculate tabs write the same scripts; form_set_props format/validate/calculate, form_fields reports them
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 01:07:37 -04:00
Brandon Thomas f8175fb97e M8.5/M8.6/M8.7: Redact a PDF: new content crate (L2: content-stream operators with spans, inline images, serializer, matrices) and redact crate (L4): Redact annotations; apply removes glyphs (TJ displacement keeps the rest in place; simple, composite/CMap and Type 3 fonts), clears covered image pixels (or removes images it can't re-encode), removes or clips vectors and shadings, rewrites form XObjects copy-on-write, removes overlapping comments, links and fields, draws the boxes with overlay text, then verifies and fails closed; redacted documents save as a full rewrite. Search & Redact text and patterns (phone, e-mail, credit card, SSN, dates). UI: Redact tool (text or box), Redact pages, Find text and redact, Set properties, Clear all / Redact all with confirmation; redact_mark/redact_apply/redact_clear tools (57 tools). Fix: deleting a field when /AcroForm is a direct dictionary
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:24:29 -04:00
Brandon Thomas d8209fcbd2 M6.6: Prepare a form: add text, date, check box, radio (new group or join the selected one), drop-down, list box, button and signature fields with Acrobat's names (Text1, Check Box1, Group1…) by click or drag; fields shown with blue fill and name tags, select/move/resize/Delete; Field Properties (General, Appearance font size, Position, Options); engine AddField/SetFieldProps/DeleteField; form_add_field/form_set_props/form_delete_field tools (54 tools). Fix: check box appearance states were direct streams that read back as null after saving
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:04:25 -04:00
Brandon Thomas 67ff9303c8 M6.1/M6.2: form filling: new fonts (L2) and forms (L3) crates; field tree with inheritance, widgets, options; set_value with validation and regenerated appearances (auto-size, comb, multiline, password, quadding, /MK, combo, list selection), check/radio states (own AP when missing), reset to /DV; engine SetFieldValue/ResetForm with fill-forms permission; UI in-place editor (Enter/click-away commit, Escape, Tab with select-all), check/radio toggle, choice popup, Clear form; tools form_fields/form_fill/form_reset. Perf: named destinations indexed once (190 MB manual opens in 1.8 s instead of 172 s); comment and form edits refresh view data from the object graph instead of a full re-inspection (6x faster per edit on large files; equivalence-tested). annot uses the shared fonts helpers. Docs, parity, roadmap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 22:37:35 -04:00